gem module method cant be included to ActiveRecord::Base - ruby-on-rails

I have a module in my gem
module joinSelect
def self.with
puts 'with called'
end
ActiveRecord::Base.send :include, self
end
but I am unable to access method with in any of model classes
irb(main):015:0> User.with
NoMethodError: undefined method `with' for User (call 'User.connection' to establish a connection):Class
I have tried putting
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include JoinSelect
#or
extend JoinSelect
end
``
doesen't work. How can I get "with" accessible on ApplicationRecord ?
Thanks in advance.

I'll recommend to include the module with your code only in the classes that will need that functionality. Including your code in ActiveRecord::Base is really not recommended, other gems you may use may conflict with it.
If you need your code to be available on all your ActiveRecord models, then define it in your ApplicationRecord. Since all your models will inherit from it, all will gain the functionality.
If you want to add a class method in your AR class, create a module with the function and extend it from your class:
module A
def foo
"Hi"
end
end
class User < ApplicationRecord
extend A
end
User.foo # => "Hi"
If you need to do more things, like declaring scopes, using ActiveRecord hooks, etc. then you'll need to use concerns, see here

define it without self in module
module JoinSelect
def with
puts 'with called'
end
end
and in ApplicationRecord use extend to include it as a class method
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
extend JoinSelect
end

Related

Undefined method belongs_to usign Rails concern, why?

I'm trying to extend a rails model from a gem.
Using concern I've been able to extend class methods but I cannot extend associations. included do returns undefined method belongs_to. I think Rails cannot load the class properly...
The model is in a engine and I'm trying to access it from my gem.
Here is the code:
# mygem/config/initializers/mymodel_extension.rb
require 'active_support/concern'
module MymodelExtension
extend ActiveSupport::Concern
# included do
# belongs_to :another
# end
class_methods do
def swear
return "I'm not doing it again"
end
end
end
class Myengine::Mymodel
include MymodelExtension
end
From command line:
Myengine::Mymodel.swear
# => "I'm not doing it again"
If I uncomment the included do I get this undefined method 'belongs_to' for Myengine::Mymodel:Class (NoMethodError) error.
Myengine::Mymodelclass should inherit from ActiveRecord::Base to have belongs_to method defined.
ActiveRecord::Base includes bunch of modules, one of which is Associations, where belongs_to association is defined.

How can I extend ActiveRecord relations with new method?

Let's imagine that I have simple ActiveRecord models like this:
class Post < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :posts
has_many :published_posts, -> { where(:published => true) }
end
And I want to create module Reindexable that will add a method called reindex into a base class. I want to be able to call this method in the 3 following ways:
Place.reindex
Place.reindex(Place.where(:published => true))
Place.where(:published => true).reindex
Category.first.places.reindex
Inside of this method I should be able to do something like this:
Reindexer.new(relation).reindex # how can I get relation here?
What is a correct way to do it in Ruby-on-Rails? How can I access current relation in all this situation?
I have tried to include code into ActiveRecord_Relation class that is dynamically added into any AR classes.
In case you need to add .to_csv to all calls like MyModel.all.to_csv:
Model:
class MyModel < ActiveRecord::Base
include ToCsv
end
You module that you want to include
module ToCsv
extend ActiveSupport::Concern
# Dynamically including our Relation and extending current class
#
def self.included(child_class)
child_class.const_get('ActiveRecord_Relation').include(RelationMethods)
child_class.extend(ExtendMethods)
end
# Instance Method
#
def to_csv(opts = {})
end
# Module containing methods to be extended into the target
#
module ExtendMethods
# Allowing all child classes to extend ActiveRecord_Relation
#
def inherited(child_class)
super + (child_class.const_get('ActiveRecord_Relation').include(RelationMethods) ? 1 : 0 )
end
end
# Holds methods to be added to relation class
#
module RelationMethods
extend ActiveSupport::Concern
# Relation Method
#
def to_csv(opts = {})
end
end
end

What is the preferred way to add some method in active record base?

I want to create a module which provides some common methods to the classes which are inherited from active record base.
Following is the two-way we can achieve it.
1)
module Commentable
def self.extended(base)
base.class_eval do
include InstanceMethods
extend ClassMethods
end
end
module ClassMethods
def test_commentable_classmethod
puts 'test class method'
end
end
module InstanceMethods
def test_commentable_instance_method
puts 'test instance method'
end
end
end
ActiveRecord::Base.extend(Commentable)
2)
module Commentable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def test_commentable_classmethod
puts 'test class method'
end
end
def test_commentable_instance_method
puts 'test instance methods'
end
end
ActiveRecord::Base.send(:include, Commentable)
Which one is the preferred way to handle this?
And
What to use when?
As of Rails 5, the recommended way is to make a module and include it in the models where it is needed, or everywhere using ApplicationRecord, which all models inherit from. (You can easily implement this pattern from scratch in older versions of Rails.)
# app/models/concerns/my_module.rb
module MyModule
extend ActiveSupport::Concern
module ClassMethods
def has_some_new_fancy_feature(options = {})
...
end
end
end
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include MyModule
end
Modules are a form of multiple-inheritance, and sometimes add unnecessary complexity. Check if a decorator, service, or other kind of object makes more sense first. Not everything needs be a fancy macro that adds 50 callbacks to your model. You will hate your life if you do this too much.
If you want to monkey-patch (DON'T DO THIS), here is my old answer:
# config/initializers/activerecord_extensions.rb
ActiveRecord::Base.send(:include, MyModule)
Or without monkey-patching (see Mori's response):
# app/models/base_model.rb
class BaseModel < ActiveRecord::Base
self.abstract_class = true
include MyModule
end
Edit: Several months down the road in a large project, I have realized its better to have every model inherit from a new base model class, as Mori explains. The problem with including modules directly into ActiveRecord::Base is this can interfere with third-party code that also relies on ActiveRecord. It is just better not to monkey-patch when you don't have to. In this case, creating a new base class can end up being simpler in the long run.
Another way is make your own base class by inheriting from ActiveRecord::Base and then letting your models inherit from that base class. This has the advantage of making it clear that your models aren't running on vanilla ActiveRecord:
class MyBase < ActiveRecord::Base
self.abstract_class = true
def self.a_class_method
end
def an_instance_method
end
end
class Foo < MyBase
end
Foo.a_class_method
Foo.new.an_instance_method
reffering with Mori's answer...you can do something like:-
Module ActiveRecordUtilities
class MyBase < ActiveRecord::Base
self.abstract_class = true
def self.a_class_method
end
def an_instance_method
end
end
end##class ends
end##module ends
and can use it ...suppose in user.rb
include ActiveRecordUtilities::MyBase
User.a_class_method
#user.instance_method
============================OR====================
module MyUtils
def do_something_funky
# Some exciting code
end
end
class Account < ActiveRecord::Base
belongs_to :person, :extend => MyUtils
end
And then call it like this:
#account = Account.first
#account.person.do_something_funky

Rails extending ActiveRecord::Base

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.
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
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
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

Where to put common code found in multiple models?

I have two models that contain the same method:
def foo
# do something
end
Where should I put this?
I know common code goes in the lib directory in a Rails app.
But if I put it in a new class in lib called 'Foo', and I need to add its functionality to both of my ActiveRecord models, do I do that like this:
class A < ActiveRecord::Base
includes Foo
class B < ActiveRecord::Base
includes Foo
and then both A and B will contain the foo method just as if I had defined it in each?
Create a module, which you can put in the lib directory:
module Foo
def foo
# do something
end
end
You can then include the module in each of your model classes:
class A < ActiveRecord::Base
include Foo
end
class B < ActiveRecord::Base
include Foo
end
The A and B models will now have a foo method defined.
If you follow Rails naming conventions with the name of the module and the name of the file (e.g. Foo in foo.rb and FooBar in foo_bar.rb), then Rails will automatically load the file for you. Otherwise, you will need to use require_dependency 'file_name' to load your lib file.
You really have two choices:
Use a module for common logic and include it in A & B
Use a common class C that extends ActiveRecord and have A & B extend C.
Use #1 if the shared functionality is not core to each class, but applicable to each class. For example:
(app/lib/serializable.rb)
module Serializable
def serialize
# do something to serialize this object
end
end
Use #2 if the shared functionality is common to each class and A & B share a natural relationship:
(app/models/letter.rb)
class Letter < ActiveRecord::Base
def cyrilic_equivilent
# return somethign similar
end
end
class A < Letter
end
class B < Letter
end
Here's how I did it... First create the mixin:
module Slugged
extend ActiveSupport::Concern
included do
has_many :slugs, :as => :target
has_one :slug, :as => :target, :order => :created_at
end
end
Then mix it into every model that needs it:
class Sector < ActiveRecord::Base
include Slugged
validates_uniqueness_of :name
etc
end
It's almost pretty!
To complete the example, though it's irrelevant to the question, here's my slug model:
class Slug < ActiveRecord::Base
belongs_to :target, :polymorphic => true
end
One option is to put them in a new directory, for example app/models/modules/. Then, you can add this to config/environment.rb:
Dir["#{RAILS_ROOT}/app/models/modules/*.rb"].each do |filename|
require filename
end
This will require every file in in that directory, so if you put a file like the following in your modules directory:
module SharedMethods
def foo
#...
end
end
Then you can just use it in your models because it will be automatically loaded:
class User < ActiveRecord::Base
include SharedMethods
end
This approach is more organized than putting these mixins in the lib directory because they stay near the classes that use them.
If you need ActiveRecord::Base code as a part of your common functionalities, using an abstract class could be useful too. Something like:
class Foo < ActiveRecord::Base
self.abstract_class = true
#Here ActiveRecord specific code, for example establish_connection to a different DB.
end
class A < Foo; end
class B < Foo; end
As simple as that. Also, if the code is not ActiveRecord related, please find ActiveSupport::Concerns as a better approach.
As others have mentioned include Foo is the way to do things... However it doesn't seem to get you the functionality you want with a basic module. The following is the form a lot of Rails plugins take to add class methods and new callbacks in addition to new instance methods.
module Foo #:nodoc:
def self.included(base) # :nodoc:
base.extend ClassMethods
end
module ClassMethods
include Foo::InstanceMethods
before_create :before_method
end
module InstanceMethods
def foo
...
end
def before_method
...
end
end
end

Resources