I've done some reading about how to extend ActiveRecord:Base class so my models would have some special methods. What is the easy way to extend it (step by step tutorial)?
There are several approaches :
Using ActiveSupport::Concern (Preferred)
Read the ActiveSupport::Concern documentation for more details.
Create a file called active_record_extension.rb
in the lib
directory.
require 'active_support/concern'
module ActiveRecordExtension
extend ActiveSupport::Concern
# add your instance methods here
def foo
"foo"
end
# add your static(class) methods here
class_methods do
#E.g: Order.top_ten
def top_ten
limit(10)
end
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
Create a file in the config/initializers
directory called extensions.rb
and add the following line to the file:
require "active_record_extension"
Inheritance (Preferred)
Refer to Toby's answer.
Monkey patching (Should be avoided)
Create a file in the config/initializers
directory called active_record_monkey_patch.rb
.
class ActiveRecord::Base
#instance method, E.g: Order.new.foo
def foo
"foo"
end
#class method, E.g: Order.top_ten
def self.top_ten
limit(10)
end
end
The famous quote about Regular expressions by Jamie Zawinski can be re-purposed to illustrate the problems associated with monkey-patching.
Some people, when confronted with a problem, think “I know, I'll use monkey patching.” Now they have two problems.
Monkey patching is easy and quick. But, the time and effort saved is always extracted back sometime in the future; with compound interest. These days I limit monkey patching to quickly prototype a solution in the rails console.
class MyActiveRecordExtensions
instead of module MyActiveRecordExtensions
. –
Stannite require
the file at the end of environment.rb
. I have added this extra step to my answer. –
Bohner ImprovedActiveRecord
and inherit from that, when you are using module
, you are updating the definition of the class in question. I used to use inheritance(cause of years of Java/C++ experience). These days I mostly use modules. –
Bohner Refinements
which addresses most of the issues with monkey patching(yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice). Sometimes a feature is there just to compel you to tempt fate. And sometimes you do. –
Bohner ActiveSupport::Concern
, then mixing it in to a class. Maybe it's possible to use that here? –
Dead list
method it gets 10 rows from database in the module, then I don't need to copy it in every subclass. –
Hypostyle top_ten
. You can chain this method with other AR methods eg: Order.where('qty > ?', 10).top_ten
–
Bohner serializable_hash
you should use the exclude
parameter of the function. If you are trying to target just one class then it might be better to override the method in that class. Here is an example: robots.thoughtbot.com/better-serialization-less-as-json –
Bohner class_methods do
instead of module ClassMethods
right? –
Restate require 'active_support/concern'
to the lib/active_record_extension.rb
file. See ActiveSupport Concern documentation –
Ho You can just extend the class and simply use inheritance.
class AbstractModel < ActiveRecord::Base
self.abstract_class = true
end
class Foo < AbstractModel
end
class Bar < AbstractModel
end
abstract_models
. Where should I put it? –
Extremadura self.abstract_class = true
to your AbstractModel
. Rails will now recognize the model as an abstract model. –
Bohner AbstractModel
in the database. Who knew a simple setter would help me DRY things up! (I was starting to cringe...it was bad). Thanks Toby and Harish! –
Whorton You can also use ActiveSupport::Concern
and be more Rails core idiomatic like:
module MyExtension
extend ActiveSupport::Concern
def foo
end
module ClassMethods
def bar
end
end
end
ActiveRecord::Base.send(:include, MyExtension)
[Edit] following the comment from @daniel
Then all your models will have the method foo
included as an instance method and the methods in ClassMethods
included as class methods. E.g. on a FooBar < ActiveRecord::Base
you will have: FooBar.bar
and FooBar#foo
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
InstanceMethods
is deprecated since Rails 3.2, just put your methods into the module body. –
Toehold ActiveRecord::Base.send(:include, MyExtension)
in an initializer and then this worked for me. Rails 4.1.9 –
Cardoso With Rails 4, the concept of using concerns to modularize and DRY up your models has been in highlights.
Concerns basically allow you to group similar code of a model or across multiple models in a single module and then use this module in the models. Here is a example:
Consider a Article model, a Event model and a Comment Model. A article or A event has many comments. A comment belongs to either article or event.
Traditionally, the models may look like this:
Comment Model:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
Article Model:
class Article < ActiveRecord::Base
has_many :comments, as: :commentable
def find_first_comment
comments.first(created_at DESC)
end
def self.least_commented
#return the article with least number of comments
end
end
Event Model
class Event < ActiveRecord::Base
has_many :comments, as: :commentable
def find_first_comment
comments.first(created_at DESC)
end
def self.least_commented
#returns the event with least number of comments
end
end
As we can notice, there is a significant piece of code common to both Event and Article Model. Using concerns we can extract this common code in a separate module Commentable.
For this create a commentable.rb file in app/model/concerns.
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
# for the given article/event returns the first comment
def find_first_comment
comments.first(created_at DESC)
end
module ClassMethods
def least_commented
#returns the article/event which has the least number of comments
end
end
end
And Now your models look like this :
Comment Model:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
Article Model:
class Article < ActiveRecord::Base
include Commentable
end
Event Model
class Event < ActiveRecord::Base
include Commentable
end
One point I will like to highlight while using Concerns is that Concerns should be used for 'domain based' grouping rather than 'technical' grouping. For example, a domain grouping is like 'Commentable', 'Taggable' etc. A technical based grouping will be like 'FinderMethods', 'ValidationMethods'.
Here is a link to a post that I found very useful for understanding concerns in Models.
Hope the writeup helps :)
Step 1
module FooExtension
def foo
puts "bar :)"
end
end
ActiveRecord::Base.send :include, FooExtension
Step 2
# Require the above file in an initializer (in config/initializers)
require 'lib/foo_extension.rb'
Step 3
There is no step 3 :)
Rails 5 provides a built-in mechanism for extending ActiveRecord::Base
.
This is achieved by providing additional layer:
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
# put your extensions here
end
and all models inherit from that one:
class Post < ApplicationRecord
end
See e.g. this blogpost.
With Rails 5, all models are inherited from ApplicationRecord & it gives nice way to include or extend other extension libraries.
# app/models/concerns/special_methods.rb
module SpecialMethods
extend ActiveSupport::Concern
scope :this_month, -> {
where("date_trunc('month',created_at) = date_trunc('month',now())")
}
def foo
# Code
end
end
Suppose the special methods module needs to be available across all models, include it in application_record.rb file. If we wants to apply this for a particular set of models, then include it in the respective model classes.
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include SpecialMethods
end
# app/models/user.rb
class User < ApplicationRecord
include SpecialMethods
# Code
end
If you want to have the methods defined in the module as class methods, extend the module to ApplicationRecord.
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
extend SpecialMethods
end
Hope it help others !
Just to add to this topic, I spent a while working out how to test such extensions (I went down the ActiveSupport::Concern
route.)
Here's how I set up a model for testing my extensions.
describe ModelExtensions do
describe :some_method do
it 'should return the value of foo' do
ActiveRecord::Migration.create_table :test_models do |t|
t.string :foo
end
test_model_class = Class.new(ActiveRecord::Base) do
def self.name
'TestModel'
end
attr_accessible :foo
end
model = test_model_class.new(:foo => 'bar')
model.some_method.should == 'bar'
end
end
end
I have
ActiveRecord::Base.extend Foo::Bar
in an initializer
For a module like below
module Foo
module Bar
end
end
© 2022 - 2024 — McMap. All rights reserved.