First time using concerns and just looking for some advice.
I have a Rails model called location.rb and would like to include an AlbumDefineConcern and then query which model class we are currently in (in this class Location).
I have:
class Location < ActiveRecord::Base
include AlbumDefineConcern
...
and
module AlbumDefineConcern
extend ActiveSupport::Concern
albums={}
albums_locations = ["LocationHomeFeed","LocationProfile","LocationMobileMain"]
albums[:locations]=albums_locations
# I'd like it to output location or Location
puts "here is class_name #{self.name}"
How would I get "location" as the class name?
# puts "here is class_name #{self.parent.name}" => Object
module AlbumDefineConcern
extend ActiveSupport::Concern
included do
puts self
end
end
I have a code of the type
Module Themes
class NewTheme < Themes::Base
def1
end
def2
end
end
end
when i try to create an instance of new theme with Themes::NewTheme.instance the instance is empty, what is wrong here? Also what is ::Base? I can't find a defincition anywhere
You should be saying NewTheme.new instead of Themes::NewTheme to instanciate your class.
As for a base class, it's just a sort of 'final' parent class - a class that has no parent class, but has children classes (perhaps just a few, or many). Here's an example from ActiveRecord::Base
class Song < ActiveRecord::Base
# Uses an integer of seconds to hold the length of the song
def length=(minutes)
write_attribute(:length, minutes.to_i * 60)
end
def length
read_attribute(:length) / 60
end
end
In this case, the class Song is getting all of the methods defined in ActiveRecord::Base 'for free'
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
In my class, I want to include multiple modules. Each module can
define its own property to persist in couchDB.
Here is an example:
module Owner
property :name
end
module Animal
property :type
end
class Cat
include Owner
include Animal
end
This doesn't work. I got this error: "undefined method `property'".
I tried added CouchRest::Model::Embeddable but it won't work for
module either. All the examples I am seeing are extending from
CouchRest::Model::Base. However, I won't be able to use this approach
because Ruby doesn't support multiple inheritance.
I won't be able to change the underlying JSON format. My desired format is {"name":"tom","type":"cat"}.
Any help would be appreciated. Thanks!
According to http://www.couchrest.info/model/embedding.html I think your example would be:
class Owner < CouchRest::Model::Base
include CouchRest::Model::Embeddable
property :name
end
class Animal < CouchRest::Model::Base
include CouchRest::Model::Embeddable
property :type
end
class Cat
property :owner, Owner
property :animal, Animal
end
This question is related to extending class methods in Ruby, perhaps more specifically in the way that permalink_fu does so.
It appears that has_permalink on a model will not be available in a derived model. Certainly I would expect anything defined in a class to be inherited by its derived classes.
class MyScope::MyClass < ActiveRecord::Base
unloadable
self.abstract_class = true
has_permalink :name
end
class MyClass < MyScope::MyClass
unloadable
#has_permalink :name # This seems to be required
end
Is there something in the way permalink_fu mixes itself in that causes this issue?
I'm using the permalink-v.1.0.0 gem http://github.com/goncalossilva/permalink_fu
After investigating this, I can now see that the problem is related to how permalink_fu verifies it it should create a permalink or not. It verifies this by checking if the permalink_field of the class is blank or not.
What's the permalink_field? When you do
class Parent < ActiveRecord::Base
has_permalink :name
end
class Child < Parent
end
you can access the permalink by writing Parent.new.permalink or Child.new.permalink. This method name can be changed by writing
class Parent < ActiveRecord::Base
has_permalink :name 'custom_permalink_name'
end
If so, the permalink will be accessible by writing Parent.new.custom_permalink_name (or Child.new.custom_permalink_name).
What's the problem with this? The permalink_field accessor methods are defined on Parent's metaclass:
class << self
attr_accessor :permalink_field
end
When you run the has_permalink method, it calls Parent.permalink_field = 'permalink'.
The problem is that although the permalink_field method is available on all subclasses, its value is stored on the class it was called. This means that the value is not propagated to the subclasses.
So, as the permalink_field is stored on the Parent class, the Child does not inherit the value, although it inherits the accessor methods. As Child.permalink_field is blank, the should_create_permalink? returns false, and Child.create :name => 'something' does not create a permalink.
A possible solution would be to replace the attr_acessors on the metaclass with cattr_accessors on the class (lines 57 to 61 on the permalink_fu.rb file).
Replace
class << base
attr_accessor :permalink_options
attr_accessor :permalink_attributes
attr_accessor :permalink_field
end
with
base.cattr_accessor :permalink_options
base.cattr_accessor :permalink_attributes
base.cattr_accessor :permalink_field
Note that this will invalidate any possible customization on the subclass. You will no longer be able to specify different options for the subclasses, as these three attributes are shared by Parent and all its subclasses (and subsubclasses).