How to Create a Rails 4 Concern That Takes an Argument

How to create a Rails 4 Concern that takes an argument

If I understand your question correctly this is about how to write such a concern, and not about the actual return value of restricted_data. I would implement the concern skeleton as such:

require "active_support/concern"

module Restrictable
extend ActiveSupport::Concern

module ClassMethods
attr_reader :restricted

private

def restrictable(except: []) # Alternatively `options = {}`
@restricted = except # Alternatively `options[:except] || []`
end
end

def restricted_data
"This is forbidden: #{self.class.restricted}"
end
end

Then you can:

class C
include Restrictable
restrictable except: [:this, :that, :the_other]
end

c = C.new
c.restricted_data #=> "This is forbidden: [:this, :that, :the_other]"

That would comply with the interface you designed, but the except key is a bit strange because it's actually restricting those values instead of allowing them.

How to use concerns in Rails 4

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.

Rails 4 routing concern with url parameter

As the error describes, you need to specify the :action to hit. The following should work

concern :votable do
get 'vote/:vote_type', action: :vote
end

resources :parking_locations, concerns: :votable

This will hit the vote action of the ParkingLocationsController and you'll have access to the vote_type param.

Use of concern in rails 4

Use the Rails built in I18n functionality instead. Doing localization in the model layer is just wrong. Models should only be concerned with data and business logic - not how data (like dates) are presented.

Rails 4: around_action with parameter

There are some clues:

  1. around_action receives a callback and a block as params, if we send a function as the 1st param, that function mustn't have any parameter!
  2. We can send a block instead (like you did) but we must pass the current given block to that block as well, your code misses the passing block, that is why the exception raised.
  3. In protect_from_exception_with, I can call block_given?, it returns true, but I don't know how to get the block out!

This works:

module CatchException
extend ActiveSupport::Concern

module ClassMethods
def protect_from_exception_with(failure_template, params)
around_action -> { catch_exception_with(failure_template) }, params
end
end

private

def log_error(e)
# Many things happen here
end

def catch_exception_with(failure_template)
self.send(params[:action])
rescue => e
log_error(e)
render failure_template
end
end

Thankfully, we still have the params in catch_exception_with, make it easy, call the action back to the controller!

Rails concerns: class method with block

The problem was solved with call method.
I rewrote instance_method as follows:

  def instance_method
class_attrs = self.class.display_attrs.map { |n| n[:attr] }
class_blocks = self.class.display_attrs.map { |n| n[:block].call(self) if n[:block] }
(class_attrs + class_blocks).compact!
end

module ClassMethods
#...
display_attrs << { block: block} if block_given?
#...
end


Related Topics



Leave a reply



Submit