@ledsun blog

無味の味は佳境に入らざればすなわち知れず

ActiveRecordでsendカラムは使えるのか?

ActiveRecordで動的に生成されるsendメソッドとObject#sendは衝突したらどうなるのか?という話です。 結論は、つかえます。

実際に試してみます。 Railsアプリケーションを準備します。

rbenv local 3.3.4
bundle init
bundle add rails
bundle exec rails new .
bundle update
bin/rails g model WebSocket send:string
bin/rails db:migrate

動かしてみましょう。

ledsun@MSI:~/rails_7_1►bin/rails c
Loading development environment (Rails 7.2.0)
rails71(dev)> WebSocket.new.send
=> nil
rails71(dev)> WebSocket.new.send(:send)
(rails71):2:in `<main>': wrong number of arguments (given 1, expected 0) (ArgumentError)

sendという引数0個のメソッドがあります。 sendカラムに対応したメソッドです。 Object#sendは呼べません。

せっかくなので、sendメソッドを定義してる場所を探しましょう。

rails71(dev)> WebSocket.new.method(:send).source_location
=> ["/home/ledsun/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/attribute_methods.rb", 269]

rails/activemodel/lib/active_model/attribute_methods.rb at main · rails/rails · GitHub

こんな感じの関数が定義されています。

def define_attribute_methods(*attr_names)
  ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
    attr_names.flatten.each do |attr_name|
      define_attribute_method(attr_name, _owner: owner)
      aliases_by_attribute_name[attr_name.to_s].each do |aliased_name|
        generate_alias_attribute_methods owner, aliased_name, attr_name
      end
    end
  end
end

binding.breakをいれて止めてみましょう。

ledsun@MSI:~/rails_7_1►bin/rails c
Loading development environment (Rails 7.2.0)
rails71(dev)> WebSocket.new
[263, 272] in ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/attribute_methods.rb
   263|       #       def clear_attribute(attr)
   264|       #         send("#{attr}=", nil)
   265|       #       end
   266|       #   end
   267|       def define_attribute_methods(*attr_names)
=> 268|         binding.break
   269|         ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
   270|           attr_names.flatten.each do |attr_name|
   271|             define_attribute_method(attr_name, _owner: owner)
   272|             aliases_by_attribute_name[attr_name.to_s].each do |aliased_name|
=>#0    ActiveModel::AttributeMethods::ClassMethods#define_attribute_methods(attr_names=[["id", "send", "created_at", "updated_at...) at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/attribute_methods.rb:268
  #1    block in define_attribute_methods at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:115
  # and 46 frames (use `bt' command for all frames)

btコマンドでバックトレースを見てみます。

(rdbg) bt    # backtrace command
=>#0    ActiveModel::AttributeMethods::ClassMethods#define_attribute_methods(attr_names=[["id", "send", "created_at", "updated_at...) at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/attribute_methods.rb:268
  #1    block in define_attribute_methods at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:115
  #2    [C] Monitor#synchronize at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:108
  #3    ActiveRecord::AttributeMethods::ClassMethods#define_attribute_methods at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:108
  #4    ActiveRecord::Core#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/core.rb:789
  #5    ActiveRecord::Persistence#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/persistence.rb:817
  #6    ActiveModel::Dirty#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/dirty.rb:366
  #7    ActiveRecord::AttributeMethods::Dirty#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods/dirty.rb:197
  #8    ActiveRecord::Timestamp#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/timestamp.rb:103
  #9    ActiveRecord::Associations#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/associations.rb:76
  #10   ActiveRecord::AutosaveAssociation#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/autosave_association.rb:279
  #11   ActiveRecord::Transactions#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/transactions.rb:434
  #12   ActiveRecord::TouchLater#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/touch_later.rb:50
  #13   ActiveRecord::Core#initialize(attributes=nil) at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/core.rb:455
  #14   [C] Class#new at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/inheritance.rb:76
  #15   ActiveRecord::Inheritance::ClassMethods#new(attributes=nil, block=nil) at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/inheritance.rb:76

newメソッドまで辿れます。 ActiveRecordを継承したクラスを最初にnewしたときにsendメソッドが定義されるようです。

ここまでシュッと試せるRubyRailsの開発者サポートの充実っぷりがすごいと思いました。