Looking at the rails source for ActiveModel::Validations, the HelperMethods module is both included and extended in the underlying model:
module Validations
extend ActiveSupport::Concern
included do
extend ActiveModel::Callbacks
extend ActiveModel::Translation
extend HelperMethods
include HelperMethods
...
Theoretically, this allows you to not only call helper methods like validates_presence_of as class macros (as shown in all the standard examples), but to also call these helper methods directly on the model instance:
myobject.validates_presence_of :name
Where I can see this being useful is in the context of a custom validator that wants to leverage some of the existing helper methods:
class CustomValidator < ActiveModel::Validator
def validate(record)
record.validates_presence_of :name
record.validates_acceptance_of :terms
...
end
end
And whether or not this is encouraged behavior, it did seem to work until some changes with specific validators in Rails 4.1. So my question is if rails does not support calling these helper methods in this fashion (as instance methods on the model), why are they included in the model instead of just extended?
So according to this commit from roughly 4 years ago, there was indeed the intention to make these validation helpers callable from both the class and instance:
https://github.com/rails/rails/commit/9131a88bb8e82f139ec49b4057fb6065ba0a2c6a
I'm evaluating the changes made in 4.1 to see about getting these affected validation methods working again.
Related
I am going through a co-workers code and am not able to find a single tutorial where this has been used. Can someone point me to some resources where this has been used. This has made code very clean but I haven't found any reference to it. This is only part of this class. It includes other some more methods.
class Manager
include ActiveModel::Model
include ActiveModel::Associations
attr_accessor :application_id, :user_id, :user_application_id,.........
belongs_to :application
belongs_to :user_application
belongs_to :user .. more belongs .......
# This method is necessary to enable this ActiveModel Class to be used in views along with Form helpers
def self._reflect_on_association(association) #:nodoc:
_reflections[association.to_sym]
end
def []=(attr, value)
self.send("#{attr}=", value)
end
def [](attr)
multi_attribute_ids = [:some_ids.to_s, :someid2.to_s]
return if multi_attribute_ids.include?(attr)
self.send(attr)
end
def applicant_name
end
-- some more methods
end
What would be the use of such a "manager". What are the two methods that are using self.send doing here. Is this a common pattern in rails.
Yes, with the introduction of ActiveModel in Rails 3, it has become an increasingly common pattern to use domain objects (called a manager in this case) that are not backed by an actual database table but which look and feel like models.
Even though ActiveModel makes it particularly convenient to pick and choose Rails model features to be incorporated into arbitrary classes, this pattern is something Rails pioneers have been encouraging since a long time.
As has been illustrated clearly in the example you posted, this pattern allows us to define virtual models and virtual associations which can easily take advantage of form helpers and other rails niceties written assuming model objects.
I have a helper method, states_list, that is returning an array of US states that I want to access in a few different places of my Rails app including:
User model: validates :state, inclusion: { in: states_list }
User model spec: test for this validation
These will be reused elsewhere in addition to the User model. I am wondering where the proper place to store this helper method is, and how to access it from the model and tests. (My initial thought was in a GeographyHelper file inside the helpers directory, but I read that those are meant specifically to be view helpers...) Thanks!
You'd probably be best served by putting your states_list method in its own module and including it in your User model. The advantage of creating a module is that your concerns are nicely separated and reusable (in case you want to validate states in other models.
1) Create a place to put your module by going into your /lib directory and creating a directory for your custom modules (we'll call it custom_modules here).
2) Create your module file: /lib/custom_modules/States.rb
3) Write your module:
module CustomModules
module States
def states_list
#your logic here
end
end
end
4) Include your new States module in your User model or any other model where you'd like this functionality.
class User < ActiveRecord::Base
include CustomModules::States
validates :state, inclusion: { in: states_list }
end
You can store this method in either application helper or in user model it self.
some of my models has a "company_id" column, that I want to set automatically. So I thought to override some method in activerecord base.
I tried this, in config/initializers, but does not work:
class ActiveRecord::Base
after_initialize :init
def init
if (self.respond_to(:company_id))
self.company_id= UserSession.find.user.company_id
end
end
end
Solution after Simone Carletti answer:
I created a module:
module WithCompany
def initialize_company
self.company_id= UserSession.find.user.company_id
end
end
And included this in the model:
class Exam < ActiveRecord::Base
include WithCompany
after_initialize :init
def init
initialize_company
end
end
Is there something else that I can do?
update 2
Best practices says to do not set session related fields in models. Use controllers for that.
There are two problems here. The first, is that you are injecting a bunch of stuff into all ActiveRecord models, whereas it would be better to add the feature only to the relevant models.
Secondary, you are breaking the MVC pattern trying to inject into the model the session context.
What you should do instead, is to code your feature in a module, and mix the module only in the relevant models. As per the context, rather than overriding the default AR behavior, add a new method where you pass the current session context (dependency injection) and returns the model initialized with the required company, when the session is set properly and the model is company-aware.
The scenario: I have a couple of ActiveRecord models in my rails system that all need to be controlled via an access control list. I have a nice little ACL implementation that does what I want, but right now the check-access calls are all duplicated in each controlled object type (document, user, etc).
My intuition is to pull that shared code into a module and use it with a mixin. I'm not sure this is possible (or what the right syntax is), because the mixed-in module has calls to ActiveRecord::Base methods - there's scope and has_many definitions.
The example of what I'd like to accomplish is here:
class Document < ActiveRecord::Base
include Controlled
end
module Controlled
has_many :acls, as: :controlled
scope :accessible, ->(uid, level){where("BUNCH OF SQL HERE")}
def access_convenience_methods
#stuff to provide easy access to authorization checks
end
end
And then I'd have a few other models that derive from ActiveRecord::Base that include Controlled.
It's the has_many and scope calls in the module that are causing heartache - I can't call them from within the mixed-in module, apparently this context doesn't have access to the outer class methods.
Any advice is welcome.
You are correct in that you can't just call class methods from the module like that.
Nowadays the boilerplate code required to do this has been wrapped into ActiveSupport::Concern; it does exactly what you want.
[EDIT]: I also suggest you should study the boilerplate code itself, as it's pretty short and readable and a good example of Ruby metaprogramming.
Aha, this is clearly a ruby newbie failure here - I need to put the has_many and other one-off calls inside an included block. It seems like ActiveSupport::Concern is precisely the right thing to use here:
module Controlled
extend ActiveSupport::Concern
included do
has_many :acls, as: :controlled
scope :accessible, ->(uid, level){where("BUNCH OF SQL HERE")}
end
def access_convenience_methods
#stuff to provide easy access to authorization checks
end
end
Here's a Ruby OO head scratcher for ya, brought about by this Rails scenario:
class Product < ActiveRecord::Base
has_many(:prices)
# define private helper methods
end
module PrintProduct
attr_accessor(:isbn)
# override methods in ActiveRecord::Base
end
class Book < Product
include PrintProduct
end
Product is the base class of all products. Books are kept in the products table via STI. The PrintProduct module brings some common behavior and state to descendants of Product. Book is used inside fields_for blocks in views. This works for me, but I found some odd behavior:
After form submission, inside my controller, if I call a method on a book that is defined in PrintProduct, and that method calls a helper method defined in Product, which in turn calls the prices method defined by has_many, I'll get an error complaining that Book#prices is not found.
Why is that? Book is a direct descendant of Product!
More interesting is the following..
As I developed this hierarchy PrintProduct started to become more of an abstract ActiveRecord::Base, so I thought it prudent to redefine everything as such:
class Product < ActiveRecord::Base
end
class PrintProduct < Product
end
class Book < PrintProduct
end
All method definitions, etc. are the same. In this case, however, my web form won't load because the attributes defined by attr_accessor (which are "virtual attributes" referenced by the form but not persisted in the DB) aren't found. I'll get an error saying that there is no method Book#isbn. Why is that?? I can't see a reason why the attr_accessor attributes are not found inside my form's fields_for block when PrintProduct is a class, but they are found when PrintProduct is a Module.
Any insight would be appreciated. I'm dying to know why these errors are occurring!
You might have better luck delaying the attr_accessor call in PrintProduct until mixin-time:
module PrintProduct
def self.included(base)
base.attr_accessor :isbn
end
# other instance methods here
end
The problem is likely something to do with timing of the attr_accessor call and how that applies to modules mixed in. I'm not certain that the timing is defined by the Ruby spec, so it might vary betweeen implementations or versions.