DRYing Models via Acts As
ActsAs is an idiom familiar to every Rails developer, which makes it a good candidate for a shared functionality between models. Using it as early in the game as possible allows one to work on its functionality without a need to touch the code in multiple models. Let's look at a couple of examples.
Acts As Unique
I have some models that I want to have uniqueness across my application. I use some UUID mechanism (initially, a db call) to set a field (:token) after creation. Since I have multiple models, I decide to extract it the code for uniqueness setting to acts_as_unique. After refactoring, my model Fruit looks like:
# create_table :fruits do |t|
# t.column :name, :string
# t.column :token, :string
# end
end
My acts_as_unique might look like:
module Acts; end; end
base.extend(ClassMethods)
end
validates_uniqueness_of field
before_validation_on_create do |o|
o.send(" =", connection.select_one('SELECT UUID() AS UUID', " UUID generated")['UUID'])
end
end
end
end
ActiveRecord::Base.send(:include, ActiveRecord::Acts::ActsAsUnique) ;
Let's try it:
>> f = Fruit.create(:name => 'apple')
>> p f.token
"0a4d7c46-4df0-102a-a4b9-59b995bffdb7"
Now I can work on acts_as_unique to replace the DB call with a UUID gem or some other implementation without affecting the rest of the code.
Acts As Trackable
I have some models for which I want to keep track of when instances are created or updated. I have a polymorphic Event model for storage of such events. Since there are multiple models I want to track, I extract the functionality to acts_as_trackable. After refactoring, my models look like:
# create_table :fruits do |t|
# t.column :name, :string
# end
end
# create_table :events do |t|
# t.column "action", :string
# t.column "created_at", :datetime, :null => false
# t.column "trackable_type", :string
# t.column "trackable_id", :integer
# end
end
module Acts; end; end
base.extend(ClassMethods)
end
has_many :events, :as => :trackable, :dependent => :destroy
after_update { |o| o.events.create(:action => 'updated') }
after_create { |o| o.events.create(:action => 'created') }
end
end
end
ActiveRecord::Base.send(:include, ActiveRecord::Acts::ActsTrackable)
;
Let's see what we got:
>> f = Fruit.create(:name => 'apple')
>> p f.events.collect(&:action)
["created"]
>> f.name = 'passionfruit'
>> f.save!
>> p f.events.collect(&:action)
["created", "updated"]
The Event model is likely to evolve but it would be easier to support it since the only place where I need to reflect the changes is acts_as_trackable. The goal is achieved.
1 comment:
Thanks for great example.
I am very admired by your blog. Nice topics, nice writing.
Thanks,
Post a Comment