Fred Willmore Fred Willmore - 2 months ago 8
Ruby Question

reopening class in initializer breaks attribute accessors

I'm trying to use an initializer in Rails 4.2 to add a method to an ActiveRecord model. However, simply reopening the class in the initializer breaks the existing attribute accessors.

model app/models/thing.rb:

class Thing < ActiveRecord::Base
end


migration db/migrate/20160914144414_create_things.rb:

class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.integer :test_field
t.timestamps null: false
end
end
end


initializer config/initializers/thing.rb:

class Thing
def self.new_method
"hello"
end
end


test file test/models/thing_test.rb:

require File.expand_path("../../test_helper", __FILE__)

class ThingTest < ActiveSupport::TestCase
test "the truth" do
thing = Thing.new
thing.test_field = 1
puts thing.new_method
end
end


When I run my test here's what I get:

❯❯❯ rtest test/models/thing_test.rb
1) Error:
ThingTest#test_the_truth:
NoMethodError: undefined method `test_field=' for #<Thing:0x00000101464658>
test/models/thing_test.rb:6:in `block in <class:ThingTest>'


If I use this alternate syntax to add the method to the class, it works:

Thing.class_eval do
def self.new_method
"hello"
end
end


I guess I'm happy to do it this way, but I would like to know why it doesn't work just reopening the class.

Answer

The key thing is that you're not reopening the class: you're defining a new one. In development and in tests your classes are loaded when they are first used. By defining Thing in your initializer you prevent your thing.rb file from ever being loaded.

When you use Thing.class_eval you're not defining a new class so rails loads Thing from thing.rb: at this point your are adding to Thing rather than replacing it.

Comments