Here's some simple code that isn't working:
module ApplicationHelper
def industries
industries = ['Agriculture','Food', etc.]
end
end
class User < ActiveRecord::Base
include ApplicationHelper
validates_inclusion_of :industries, :in => ApplicationHelper.industries
...
end
This code is failing when a user action is run with undefined method 'industries'. Do you know how I can reference the helper (or preferably just industries) the right way?
Edit
Based on the code in Mauricio's answer below, how can INDUSTRIES be accessed in a controller and a view? I'm having real trouble making industries accessible everywhere, but it really needs to be for my application.
You should never do something like this. ApplicationHelper is not supposed to be included in a model. Best solution would be:
class User < ActiveRecord::Base
INDUSTRIES = ['Agriculture','Food']
validates_inclusion_of :industries, :in => INDUSTRIES
end
module ApplicationHelper
def industries
User::INDUSTRIES
end
end
And now you have it done in a simple and nice way.
EDIT
Anywhere you need to access industries you should use:
User::INDUSTRIES
And that's it.
Related
We have a multi-tenant application where validation differs for each account. We could easily achieve this for presence validation like the below,
module CommonValidator
def add_custom_validation
required_fields = get_required_fields
return if required_fields.blank?
validates_presence_of required_fields.map(&:to_sym)
end
end
class ApplicationRecord < ActiveRecord::Base
include Discard::Model
include CommonValidator
end
Then we have to add uniqueness validation based on account, so tried like the same. but getting undefined method error. Is there any way that I could get this work?
module CommonValidator
def add_custom_validation
unique_fields = ['first_name']
validates_uniqueness_of unique_fields.map(&:to_sym) if unique_fields.present?
end
end
validates_uniqueness_of is actually a class method (defined in ActiveRecord::Validations::ClassMethods), hence you are not able to call it from the context of the object.
Whereas validates_presence_of is both a helper method, and a class method (defined in ActiveModel::Validations::HelperMethods and ActiveRecord::Validations::ClassMethods).
If you want to use the uniqueness validator, you can define the add_custom_validation as a class method as well and then you should be able to use it. Something like,
require 'active_support/concern'
module CommonValidator
extend ActiveSupport::Concern
included do
add_custom_validation
end
class_methods do
def add_custom_validation
required_fields = get_required_fields # This will also need to be a class method now
return if required_fields.blank?
validates_uniqueness_of required_fields.map(&:to_sym)
end
end
end
Is there a guideline to follow in regard to space for class variables, instance variables, etc? For example
class MyModel < ApplicationRecord
belongs_to :something
has_many: :something_elses
validates: :property, presence: true
after_save :do_something
end
In this case I'm using as example a model record, but I would like to understand the standard style for everything. I'm using Rubocop and it doesn't tell me anything about this.
Thanks.
anothermh shared the link above (and for the record, amazing that their profile photo is of Robocop when this question is about Rubocop) ... but here's what the Rubocop guidelines for classes suggest:
class Person
# extend and include go first
extend SomeModule
include AnotherModule
# inner classes
CustomError = Class.new(StandardError)
# constants are next
SOME_CONSTANT = 20
# afterwards we have attribute macros
attr_reader :name
# followed by other macros (if any)
validates :name
# public class methods are next in line
def self.some_method
end
# initialization goes between class methods and other instance methods
def initialize
end
# followed by other public instance methods
def some_method
end
# protected and private methods are grouped near the end
protected
def some_protected_method
end
private
def some_private_method
end
end
As a personal note: although having consistent styling makes it quicker and easier to read code and for others to scan what you're written, keep in mind that these are just recommendations of "best practices". At the end of the day whatever works best FOR YOU should be your new best practice.
The default Rails 4 project generator now creates the directory "concerns" under controllers and models. I have found some explanations about how to use routing concerns, but nothing about controllers or models.
I am pretty sure it has to do with the current "DCI trend" in the community and would like to give it a try.
The question is, how am I supposed to use this feature, is there a convention on how to define the naming / class hierarchy in order to make it work? How can I include a concern in a model or controller?
So I found it out by myself. It is actually a pretty simple but powerful concept. It has to do with code reuse as in the example below. Basically, the idea is to extract common and / or context specific chunks of code in order to clean up the models and avoid them getting too fat and messy.
As an example, I'll put one well known pattern, the taggable pattern:
# app/models/product.rb
class Product
include Taggable
...
end
# app/models/concerns/taggable.rb
# notice that the file name has to match the module name
# (applying Rails conventions for autoloading)
module Taggable
extend ActiveSupport::Concern
included do
has_many :taggings, as: :taggable
has_many :tags, through: :taggings
class_attribute :tag_limit
end
def tags_string
tags.map(&:name).join(', ')
end
def tags_string=(tag_string)
tag_names = tag_string.to_s.split(', ')
tag_names.each do |tag_name|
tags.build(name: tag_name)
end
end
# methods defined here are going to extend the class, not the instance of it
module ClassMethods
def tag_limit(value)
self.tag_limit_value = value
end
end
end
So following the Product sample, you can add Taggable to any class you desire and share its functionality.
This is pretty well explained by DHH:
In Rails 4, we’re going to invite programmers to use concerns with the
default app/models/concerns and app/controllers/concerns directories
that are automatically part of the load path. Together with the
ActiveSupport::Concern wrapper, it’s just enough support to make this
light-weight factoring mechanism shine.
I have been reading about using model concerns to skin-nize fat models as well as DRY up your model codes. Here is an explanation with examples:
1) DRYing up model codes
Consider a Article model, a Event model and a Comment model. An article or an 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. Using concerns we can extract this common code in a separate module Commentable.
For this create a commentable.rb file in app/models/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
2) Skin-nizing Fat Models.
Consider a Event model. A event has many attenders and comments.
Typically, the event model might look like this
class Event < ActiveRecord::Base
has_many :comments
has_many :attenders
def find_first_comment
# for the given article/event returns the first comment
end
def find_comments_with_word(word)
# for the given event returns an array of comments which contain the given word
end
def self.least_commented
# finds the event which has the least number of comments
end
def self.most_attended
# returns the event with most number of attendes
end
def has_attendee(attendee_id)
# returns true if the event has the mentioned attendee
end
end
Models with many associations and otherwise have tendency to accumulate more and more code and become unmanageable. Concerns provide a way to skin-nize fat modules making them more modularized and easy to understand.
The above model can be refactored using concerns as below:
Create a attendable.rb and commentable.rb file in app/models/concerns/event folder
attendable.rb
module Attendable
extend ActiveSupport::Concern
included do
has_many :attenders
end
def has_attender(attender_id)
# returns true if the event has the mentioned attendee
end
module ClassMethods
def most_attended
# returns the event with most number of attendes
end
end
end
commentable.rb
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments
end
def find_first_comment
# for the given article/event returns the first comment
end
def find_comments_with_word(word)
# for the given event returns an array of comments which contain the given word
end
module ClassMethods
def least_commented
# finds the event which has the least number of comments
end
end
end
And now using Concerns, your Event model reduces to
class Event < ActiveRecord::Base
include Commentable
include Attendable
end
* While using concerns its advisable to go for 'domain' based grouping rather than 'technical' grouping. Domain Based grouping is like 'Commentable', 'Photoable', 'Attendable'. Technical grouping will mean 'ValidationMethods', 'FinderMethods' etc
It's worth to mention that using concerns is considered bad idea by many.
like this guy
and this one
Some reasons:
There is some dark magic happening behind the scenes - Concern is patching include method, there is a whole dependency handling system - way too much complexity for something that's trivial good old Ruby mixin pattern.
Your classes are no less dry. If you stuff 50 public methods in various modules and include them, your class still has 50 public methods, it's just that you hide that code smell, sort of put your garbage in the drawers.
Codebase is actually harder to navigate with all those concerns around.
Are you sure all members of your team have same understanding what should really substitute concern?
Concerns are easy way to shoot yourself in the leg, be careful with them.
This post helped me understand concerns.
# app/models/trader.rb
class Trader
include Shared::Schedule
end
# app/models/concerns/shared/schedule.rb
module Shared::Schedule
extend ActiveSupport::Concern
...
end
I felt most of the examples here demonstrated the power of module rather than how ActiveSupport::Concern adds value to module.
Example 1: More readable modules.
So without concerns this how a typical module will be.
module M
def self.included(base)
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end
def instance_method
...
end
module ClassMethods
...
end
end
After refactoring with ActiveSupport::Concern.
require 'active_support/concern'
module M
extend ActiveSupport::Concern
included do
scope :disabled, -> { where(disabled: true) }
end
class_methods do
...
end
def instance_method
...
end
end
You see instance methods, class methods and included block are less messy. Concerns will inject them appropriately for you. That's one advantage of using ActiveSupport::Concern.
Example 2: Handle module dependencies gracefully.
module Foo
def self.included(base)
base.class_eval do
def self.method_injected_by_foo_to_host_klass
...
end
end
end
end
module Bar
def self.included(base)
base.method_injected_by_foo_to_host_klass
end
end
class Host
include Foo # We need to include this dependency for Bar
include Bar # Bar is the module that Host really needs
end
In this example Bar is the module that Host really needs. But since Bar has dependency with Foo the Host class have to include Foo (but wait why does Host want to know about Foo? Can it be avoided?).
So Bar adds dependency everywhere it goes. And order of inclusion also matters here. This adds lot of complexity/dependency to huge code base.
After refactoring with ActiveSupport::Concern
require 'active_support/concern'
module Foo
extend ActiveSupport::Concern
included do
def self.method_injected_by_foo_to_host_klass
...
end
end
end
module Bar
extend ActiveSupport::Concern
include Foo
included do
self.method_injected_by_foo_to_host_klass
end
end
class Host
include Bar # It works, now Bar takes care of its dependencies
end
Now it looks simple.
If you are thinking why can't we add Foo dependency in Bar module itself? That won't work since method_injected_by_foo_to_host_klass have to be injected in a class that's including Bar not on Bar module itself.
Source: Rails ActiveSupport::Concern
In concerns make file filename.rb
For example I want in my application where attribute create_by exist update there value by 1, and 0 for updated_by
module TestConcern
extend ActiveSupport::Concern
def checkattributes
if self.has_attribute?(:created_by)
self.update_attributes(created_by: 1)
end
if self.has_attribute?(:updated_by)
self.update_attributes(updated_by: 0)
end
end
end
If you want to pass arguments in action
included do
before_action only: [:create] do
blaablaa(options)
end
end
after that include in your model like this:
class Role < ActiveRecord::Base
include TestConcern
end
I got a little problem to understand where should I add an method that all the models can have access to it. I read other similar posts but it's not very clear where to add it. Some of them said about add it on "/lib" as a module an then include it in the model class (Already try this without luck). So what it's the best practice for add this?
I'm trying the following:
My module on: /lib/search.rb
module Search
def self.search(params,columns_search)
srch = params[:search]
if srch.blank?
scoped
else
search= []
#Add conditions for the search
columns_search.map do |column|
search << (sanitize_sql_for_conditions ["LOWER(CAST(#{column} as TEXT)) LIKE ?", "%#{srch.downcase}%"])
end
where("(#{conditions.join(" and ")})")
end
end
On my model cars.rb
class Cars < ActiveRecord::Base
include Search
attr_accessible :name
end
But i'm getting the following error on my console:
Started GET "/cars" for 127.0.0.1 at 2012-08-01 11:56:54 -0500
ActionController::RoutingError (uninitialized constant Car::Search):
app/models/car.rb:2:in `'
Any help will be appreciated! :)
The technique you mention seems like a reasonable approach - create a module (which will probably live in /lib) that defines the methods you want the models to have, and then include it in each model that needs it.
For instance, my current project has Images, Videos and Audios, all of which have certain method that should be available to them because they're all types of media.
I define a module in lib/media.rb:
module Media
def self.included(base)
# Associations etc. go here
base.has_many :things
base.mount_uploader :image, ImageUploader
base.attr_accessible :image
base.before_save :do_something
end
def do_something(argument=nil)
#stuff
end
end
And then in each of the media models, I include it:
class Video < ActiveRecord::Base
include Media
end
I can then call Video.first.do_something, just as though the method were defined in the model.
Hey all -- this question is specifically about a gender validation, but I'm interested in hearing how you've all handled similar situations with much larger collections (Country selection, for example.)
I'm working on a system that lets athletes register for various events, and am currently working on a good gender validation. My question is, what's the best, most DRY way to run the same validation on many different models?
Let's say I want to validate the gender property of Event and User. I can create a helper for validates_each that checks values for inclusion in the very short array of ["male", "female"] before updating the gender attribute. But what if I want to access this same gender array in a form_for block, say, as an input to collection_select?
I have it working for one model -- I declare a GENDERS constant in Event, and have a short class method
def self.genders
GENDERS
end
for access by forms. But where should I store the array if multiple models need access?
EDIT: One idea would be to use a class method in the application controller. Any thoughts on how appropriate this approach is would be great.
Here's my solution. I like to go with the standard plugin-style libraries. I'd put this in lib/acts_as_gendered:
module ActsAsGendered
GENDERS = ['male', 'female']
def self.included(base)
base.extend(ActsAsGenderedMethods)
end
module ActsAsGenderedMethods
def acts_as_gendered
extend ClassMethods
include InstanceMethods
validates_inclusion_of :gender, :in => GENDERS
end
end
module ClassMethods
def is_gendered?
true
end
end
module InstanceMethods
def is_male
gender = 'male'
end
def is_female
gender = 'female'
end
def is_male?
gender == 'male'
end
def is_female?
gender == 'female'
end
end
end
Yeah, it might be overkill for simple genders, but you can see where all the pieces go - the GENDERS constant, the acts_as_gendered ActiveRecord hook, which then includes the class and instance methods and the validation.
Then, in config/initializers/gender.rb:
require 'acts_as_gendered'
ActiveRecord::Base.send(:include, ActsAsGendered)
Then, for the grand finale, the model:
class User < ActiveRecord::Base
acts_as_gendered
end
This pattern may seem overly complicated, but that's how most libraries end up eventually :)
UPDATE: To answer your comment, this is how I'd modify the acts_as_gendered method to make validations optional on a per-model basis:
def acts_as_gendered options={}
config = {:allow_nil => false}
config.merge(options) if options.is_a?(Hash)
extend ClassMethods
include InstanceMethods
if config[:allow_nil]
validates_inclusion_of :gender, :in => (GENDERS + nil)
else
validates_inclusion_of :gender, :in => GENDERS
end
end
Now you can call it in the User model like this:
class User < ActiveRecord::Base
acts_as_gendered :allow_nil => true
end
I could have made it a simple parameter you pass in, but I like the clarity of passing in a hash. And it sets you up for adding other options down the road.
You've got the right idea by storing it in a constant. The only thing I would do differently is put it in an initializer file so that it's not tied to any particular model like it is in your example. If you're worried about potential name collisions at the top level, you could put it in a module in the lib directory and include the module only in the places you intend to use it.
I agree with putting this in a constant. I'd also put the strings themselves in constants because (1) they can change, and (2) when used in a conditional, the system will catch if you mistype them. E.g., in your environment.rb:
MALE = 'male'
FEMALE = 'female'
GENDERS = [MALE, FEMALE]
And then in your code, you only ever refer to these constants, e.g.:
def male?
return gender == MALE
end
I would write a custom validation plugin (say, validates_gender). Then you would call:
class Event < ActiveRecord::Base
validates_gender :gender
end
Grab a copy of my validates_as_email plugin and use that, replacing value =~ EMAIL_ADDRESS_RE with your own logic.