Using ActiveSupport::Concern and create accessors and validations dynamically - ruby-on-rails

I want to use ActiveSupport::Concern in order to extend the ActiveRecord::Base funcionality, dynamically.
So I have a class (in app/models/foo.rb)
class Foo < ActiveRecord::Base
end
and in lib/activ_record_extention.rb I have
module ActiveRecordExtension
extend ActiveSupport::Concern
module ClassMethods
c= atrr_name
attr_accessible c.to_sym
end
end
ActiveRecord::Base.send(:include, ActiveRecordExtension)
But when I run the server I get :
undefined method `attr_accessible' for ActiveRecord Extension:Module (NoMethodError)

I guess you need something like this:
module ActiveRecordExtension
extend ActiveSupport::Concern
included do
attr_accessible # Still need to call the API method
def mass_assignment_authorizer
# Here we can access object's attributes
super << some_attr
end
end
end
ActiveRecord::Base.send(:include, ActiveRecordExtension)
I updated my answer.

Related

gem module method cant be included to ActiveRecord::Base

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

ActiveSupport::Concern ClassMethod not being attached to host class

I have this in models/concerns/sluggable.rb:
module Sluggable
extend ActiveSupport::Concern
module ClassMethods
def sluggify(str, type)
# ...
end
end
end
And I've included it in models/slug.rb:
class Slug < ActiveRecord::Base
include Sluggable
# ...
before_save :create_slug
private
def create_slug
self.slug = Slug.sluggify(self.title, 'item')
end
end
I'm under the impression that the sluggify method from the module should now be available as a class method of Slug but it appears to not be the case. I get an undefined method: sluggify error when trying to insert the model into the database.
The Sluggable module is available from the Rails console. And I can confirm that my code is correct:
module Sluggable
def sluggify(str, type)
# ...
end
end
class SluggableTest
extend Sluggable
end
slug = SluggableTest.sluggify('string to sluggify', 'item')
So why can't I get it to work as a concern inside the model?
Try adding the include Sluggable on the top of the Slug class like this:
class Slug < ActiveRecord::Base
include Sluggable
# ...
before_save :create_slug
private
def create_slug
self.slug = Slug.sluggify(self.title, 'item')
end
end
Because, you are right. sluggify method will be attached to the Slug class once you include the Sluggable module in the Slug class and will be available as a class method of the Slug class. I think, because you don't have include Sluggable on top of your Slug class, the sluggify method is being called before the Sluggable module is included into the Slug class and hence it's getting and undefined method error, which makes sense!
you define your method inside nested module, but doesn't include it or extend it. Following is the right template:
module Sluggable
module ClassMethods
end
module InstanceMethods
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end

Wrapping Rails ActiveSupport::Concern into other Concerns

I have created a ActiveSupport::Concern, and inside a ClassMethods method I'm calling the mount_uploader method from Carrierwave
/lib/my_concern.rb
require 'active_support/concern'
module MyConcern
extend ActiveSupport::Concern
included do
end
module ClassMethods
def cover_image
attr_accessible :cover_image
mount_uploader :cover_image, "CoverImageUploader"
end
end
ActiveRecord::Base.send(:include, MyConcern)
/app/models/my_model.rb
class MyModel < ActiveRecord::Base
cover_image
end
It all works well until I change something (I mean anything) in the class that I called the method, then I gives me:
undefined method `cover_image_url' for #<MyModel:0x007fa0b043dbb0>
I believe is something related in how it loads those methods.
Ps: I'm using Thin in my dev enviroment, and every time that gives me the error and I restart the server it come back to work.

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

Error from moving acts_as_taggable into an included module

If I write this, everything works fine:
class A < ActiveRecord::Base
acts_as_taggable
end
But if I take acts_as_taggable and put it into a module that class A includes, I get an error:
module B
def self.included(base)
base.class_eval do
extend ClassMethods
include InstanceMethods
end
end
module ClassMethods
acts_as_taggable
end
module InstanceMethods
end
end
class A < ActiveRecord::Base
include B
The error from the code above is:
undefined local variable or method `acts_as_taggable' for C::ClassMethods:Module
Is it not correct to call acts_as_taggable from an included module?
Does it need to be in the class definition itself?
When Ruby loads the file containing your module B and reaches the acts_as_taggable line, it will try to execute the acts_as_taggable class method of ClassMethods (which doesn't exist because it is actually a class method of ActiveRecord::Base).
You can use the included method to call acts_as_taggable when your module is included though. included is passed the class the module is being included in, so the following will work:
module B
def self.included(base)
base.acts_as_taggable
# ...
end
# ...
end

Resources