Trying to move model code to shared concerns - ruby-on-rails

I have a model with the following two methods which are required in another model so I thought I'd try sharing them via a concern instead of duplicating the code.
class Region < ActiveRecord::Base
def ancestors
Region.where("lft < ? AND ? < rgt", lft, rgt)
end
def parent
self.ancestors.order("lft").last
end
end
I have created a file in app/models/concerns/sets.rb and my new model reads:
class Region < ActiveRecord::Base
include Sets
end
sets.rb is:
module Sets
extend ActiveSupport::Concern
def ancestors
Region.where("lft < ? AND ? < rgt", lft, rgt)
end
def parent
self.ancestors.order("lft").last
end
module ClassMethods
end
end
Question:
How do I share a method between models when the method references the model such as "Region.where..."

Either by referencing the class of the including model (but you need to wrap the instance methods in an included block):
included do
def ancestors
self.class.where(...) # "self" refers to the including instance
end
end
or (better IMO) by just declaring the method as a class method, in which case you can leave the class itself out altogether:
module ClassMethods
def ancestors
where(...)
end
end

Related

How to properly use base scope in Rails

I have two models that share the behavior. Both Post and Comment can have reactions.
# ./app/models/post.rb
class Post < ApplicationRecord
has_many :reactions, as: :reactionable
end
# ./app/models/comment.rb
class Comment < ApplicationRecord
has_many :reactions, as: :reactionable
end
When I decorate them, I end up with a lot of exact same methods.
# ./app/decorators/post_decorator.rb
class PostDecorator < ApplicationDecorator
delegate_all
def reactions_total_count
object.reactions.count
end
def reactions_type(kind)
object.reactions.collect(&:reaction_type).inject(0) {|counter, item| counter += item == kind ? 1 : 0}
end
def likes_count
reactions_type('like')
end
def hearts_count
reactions_type('heart')
end
def wows_count
reactions_type('wow')
end
def laughs_count
reactions_type('laugh')
end
def sads_count
reactions_type('sad')
end
end
# ./app/decorators/comment.rb
class CommentDecorator < ApplicationDecorator
delegate_all
def reactions_total_count
object.reactions.count
end
def reactions_type(kind)
object.reactions.collect(&:reaction_type).inject(0) {|counter, item| counter += item == kind ? 1 : 0}
end
def likes_count
reactions_type('like')
end
def hearts_count
reactions_type('heart')
end
def wows_count
reactions_type('wow')
end
def laughs_count
reactions_type('laugh')
end
def sads_count
reactions_type('sad')
end
end
I want it to look something like this, but don't know where to put the files, and exactly which technique I should use (include vs. extend).
# ./app/decorators/base.rb
module Base
# methods defined here
end
# ./app/decorators/post.rb
class PostDecorator < ApplicationDecorator
delegate_all
include Base
end
# ./app/decorators/comment.rb
class CommentDecorator < ApplicationDecorator
delegate_all
include Base
end
Please advise. I know there is a better approach that I just can't seem to get right.
First of all, I would figure out a better name for the included module. It could be treated as a role, using which you would enrich classes. This role would have declared a bunch of *_count methods that counts smth in reactions.
So I would name it ReactionsCountable and additionally put it into namespace to distinguish from the decorators: Roles::ReactionsCountable.
Then I would put it into:
/app/decorators
/roles
/reactions_countable.rb
/comment.rb
/post.rb
Other soulution would be to use classic inheritence. Here the Base name would make sense IMO:
class BaseDecorator < ApplicationDecorator
# declare `*_count` methods here
class PostDecorator < BaseDecorator
class CommentDecorator < BaseDecorator
I figured it out with Maicher's answer.
If a CommentDecorator is inside of ./app/decorators/comment_decorator.rb, and we want to include a module, ReactionablesCountable, then we need to create a file inside of ./app/decorators/roles/reactions_countable.rb that has module nesting which reflects the path to the file. For example, the first constant in the include, (Roles) points to the folder Rails will look for it, the second (ReactionsCountable), is the name of the file.
The module needs to be nested within this file as well. I've shown it below.
module Roles
module ReactionsCountable
# methods defined here.
end
end

Calling a helper from a class and instance method in a Rails model

I need to call a helper method within a model, from both a class and an instance method, e.g. Model.method(data) and model_instance.method. However, the class method always returns "NoMethodError: undefined method 'helper_method' for #<Class ...>"
model.rb:
class Model < ActiveRecord::Base
include ModelHelper
def method
helper_method(self.data)
end
def self.method(data)
self.helper_method(data)
end
end
model_helper.rb:
module ModelHelper
def helper_method(data)
# logic here
end
end
I even tried adding def self.helper_method(data) in the helper to no avail.
After quite a bit of seraching, I wasn't able to find anything on how to achieve this, or at least anything that worked.
The answer turned out to be pretty simple, and doesn't require any Rails magic: you just re-include the helper and define the class method within a class block:
class Model < ActiveRecord::Base
include ModelHelper
def method
helper_method(self.data)
end
# Expose Model.method()
class << self
include ModelHelper
def method(data)
helper_method(data)
end
end
end
No changes to the helper needed at all.
Now you can call method on both the class and an instance!
If there's no additional logic in method, then you can simply do:
class Model < ActiveRecord::Base
include ModelHelper
extend ModelHelper
end
And get both the instance (#model.helper_method) and the class (Model.helper_method) methods.
If, for legacy (or other) reasons, you still want to use method as an instance and class method, but method doesn't do anything different than helper_method, then you could do:
class Model < ActiveRecord::Base
include ModelHelper
extend ModelHelper
alias method helper_method
singleton_class.send(:alias_method, :method, :helper_method)
end
And now you can do #model.method and Model.method.
BTW, using modules to include methods in classes is seductive, but can get away from you quickly if you're not careful, leaving you doing a lot of #model.method(:foo).source_location, trying to figure out where something came from. Ask me how I know...
you need to define model_helper.rb as:
module ModelHelper
def self.helper_method(data)
# logic here
end
end
and call this method in model.rb as:
class Model < ActiveRecord::Base
include ModelHelper
def method
ModelHelper.helper_method(self.data)
end
def self.method(data)
ModelHelper.helper_method(data)
end
end

Getting the ActiveRecord_Relation that received the class method call

Having:
class Foo < ApplicationRecord
def self.to_csv
# irrelevant
end
end
Rails allow me to do:
Foo.all.to_csv
But how would I access the collection that received the method call inside to_csv? (all in this case)
This may seem counter intuitive but you can use #all
For example:
class Foo < ActiveRecord::Base
def self.to_csv
all.map(&:convert_to_csv)
end
end
Not only this will work with Foo.all.to_csv but also with Foo.where(...).to_csv
If you look at the source of #all inside ActiveRecord:
def all
if current_scope
current_scope.clone
else
default_scoped
end
end
This means if you have defined a scope with where or limit it will respect it. Or if you're grabbing all records it will just use default_scoped

What is the conventional way to reuse methods across presenters?

What is a convention for reusing methods between presenters?
For example, say an app has the following presenters
class UserPresenter < BasePresenter
end
class AdminPresenter < BasePresenter
end
class EventPresenter < BasePresenter
end
User and Admin both have avatars. What is the correct way to share an avatar method between the User and Admin presenter?
One solution might be inheriting from an AvatarPresenter
class UserPresenter < AvatarPresenter
end
class AdminPresenter < AvatarPresenter
end
class EventPresenter < BasePresenter
end
class AvatarPresenter < BasePresenter
end
Which works OK in this simple example. But what if things become more complex in the future (e.g., an additional method shared between Admin and Event).
I suppose I'm looking to share Concerns between Presenters. Is this a conventional approach, and if so what would a template implementation look like? All my attempts are raising method not found errors.
What you are looking for is traits. In Ruby this takes the form of module mixins.
module Avatar
def self.included(base)
base.extend ClassMethods
base.class_eval do
# in this block you are operating on the singleton class
# where the module is included
end
end
def an_instance_method_from_avatar
end
module ClassMethods
def a_class_method_from_avatar
end
end
end
class UserPresenter
include Avatar
end
class AdminPresenter
include Avatar
end
This lets us create reusable components that can composed in many different ways. ActiveSupport::Concern takes the pattern above and simplifies it:
module Avatar
# modules can be extended by other modules
extend ActiveSupport::Concern
included do
# in this block you are operating on the singleton class
# where the module is included
end
class_methods do
def a_class_method_from_avatar
end
end
def an_instance_method_from_avatar
end
end
Inheritance (class based) on the other hand should only really be used if an object is a true subtype of its parent. While you could argue that an AdminPresenter is a presenter with an avatar this would lead to a really convoluted class diagram down the road if you need to add other functionality.

Common method in model and helper

I have a common method that exists in my model because it is called by my model. Retrospectively, my view also requires this model method. In order to accomplish this, I have:
moved the model method to the application_helper.rb file
my model calls the application_helper method by adding include ApplicationHelper at the top of my ActiveRecord model
Functionality wise, it works. But is this good practice?
My Model looks like this:
class Client < ActiveRecord::Base
include ApplicationHelper
end
Writing include ApplicationHelper in to your model is bad practice because ApplicationHelper is a nice place to put tons of helper functions you need in your views. These functions will end up being imported as instance methods of your model. These functions are mostly unrelated to your model and will not work if they depend on things like params or request. Here are two other options:
Option 1:
You can just define the method inside the Client class, and then call it from the view, like this:
class Client < ActiveRecord::Base
def self.my_class_method
end
def my_instance_method
end
end
And then in your view:
<%= Client.my_class_method %>
<%= #client.my_instance_method %>
Option 2:
Make a separate module in lib and include it in the places you need it. The file name should match the module name for auto-loading to work.
In lib/my_module.rb:
module MyModule
def my_method
end
end
In your model:
class Client < ActiveRecord::Base
include MyModule
def other_method
my_method
end
end
Include the module in ApplicationHelper so it is available to all your views:
module ApplicationHelper
include MyModule
end
Then in your view you can call it easily:
<%= my_method %>
If you do want to move it to a helper, you should move it in to the client_helper, as it is something just for your Client model and not for the whole application.
The method you speak of though, is it a static class method or an instance method? If it's an instance method, then your models (even if they're in views) can call that method. If it's a static class method, then your views can use it too by calling it like any other static class method (i.e, Client.do_method or something).
I don't see any reason why it needs to be in a helper, unless your method has absoloutely nothing to do with your model, in which case that would be a different question.
5 Common method in model and helper:
Option 1:
You can just define the method inside the Client class, and then call it from the view,
like this:
class Client < ActiveRecord::Base
def self.my_class_method
end
def my_instance_method
end
end
And then in your view:
<%= Client.my_class_method %>
<%= #client.my_instance_method %>
Option 2:
module MyModule
def my_method
end
end
Include the module in ApplicationHelper so it is available to all your views:
module ApplicationHelper
include MyModule
end
Then in your view you can call it easily:
<%= my_method %>
option 3:
module MyModule
def my_method(amount)
end
end
In your model:
class Client < ActiveRecord::Base
include MyModule
def self.other_method(amount)
my_method(amount)
end
end
Then in your view you can call it easily:
<%= Client.other_method(amount) %>
Option 4:
Or you can declare the helper method as a class function, and use it like so:
module Myhelper
def self.my_village
"bikharniyan kallan,Rajasthan,India"
end
end
Then in your view you can call it easily:
<%= Myhelper.my_village %>
option 5:
use many helper in controller
helper=>
module Myhelper
def my_info
"my information is"
end
end
module Myhelper1
def my_village
"bikharniyan kallan,Rajasthan,India"
end
end
ApplicationHelper=>
module ApplicationHelper
include Myhelper
include Myhelper1
end
ApplicationController
class ApplicationController < ActionController::Base
include ApplicationHelper
end
YourController
class YourController < ActionController::Base
def action
my_info
my_village
end
end
At this moment, with rails 5 you can simply push your common method into application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.common_class_method
# some awesome implement
end
def common_method
# implement this
end
end
Then in each model class you can call common_class_method by : YourClassName.common_class_method
or call common_method by: YourClassInstance.common_method

Resources