Rails ActiveSuppport:Concern and Private Methods - ruby-on-rails

This is a great idea about concern in rails: http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns
And it's also a good idea to make very small methods that are not part of a public API. Without using concerns, those become private methods in a ruby class.
Does it makes sense to create private methods inside of a Rails ActiveSupport::Concern module? If so, does private work both for regular instance methods and class methods in the concern definition?

Does it makes sense to create private methods inside of a Rails ActiveSupport::Concern module?
Considering that concerns are smart modules that will eventually be included in other classes — yes, it does. It's just a portable code, extractable behavior and I'd like to consider it as part of my controller (or model, etc.) as I'm writing it. So basically you just declare methods private or protected as you normally would.
Maybe the post you linked have been updated since 2013, but DHH does exactly that in the one of the examples there:
module Dropboxed
extend ActiveSupport::Concern
included do
before_create :generate_dropbox_key
end
def rekey_dropbox
generate_dropbox_key
save!
end
private # <- Let's list some privates
def generate_dropbox_key
self.dropbox_key = SignalId::Token.unique(24) do |key|
self.class.find_by_dropbox_key(key)
end
end
end
As to private class methods, I agree with #Hugo and never used them myself, but here's how you can achieve this:
module Dropboxed
extend ActiveSupport::Concern
included do
private_class_method :method_name
end
module ClassMethods
def method_name
end
end
end

It's just my opinion but right now I'm scratching my head about private class method, what are they good for? Anyway, if you really need them refer to this post: How to create a private class method?
It does make sense to have private instance methods in a concern module and will work fine. Private class methods will work fine as well but following the above stated post.

Related

Add method to a class which can only be accessed inside specific class

I have a class in initializers in which I use Hash class and I would like to add 2 methods to Hash class. I know how to add methods to the class but I don't want to make the Hash class "dirty".
Is there a way that I can extend the Hash class with those two methods but only inside the class where I use them?
You could use refinements for this:
Due to Ruby's open classes you can redefine or add functionality to existing classes. This is called a “monkey patch”. Unfortunately the scope of such changes is global. All users of the monkey-patched class see the same changes. This can cause unintended side-effects or breakage of programs.
Refinements are designed to reduce the impact of monkey patching on other users of the monkey-patched class. Refinements provide a way to extend a class locally. Refinements can modify both classes and modules.
Something like this:
module HashPatches
refine Hash do
def new_hash_method
# ...
end
end
end
and then:
class YourClass
using HashPatches
def m
{}.new_hash_method
end
end
That would let you call YourClass.new.m (which would use new_hash_method) but it wouldn't pollute Hash globally so outside YourClass, some_hash.new_hash_method would be a NoMethodError.
Reading:
Official Refinements docs
Refinements spec
A less hacky way could be to use SimpleDelegator.
class Foo
class SuperHash < SimpleDelegator
def new_method
# do something with hash
# you can use __getobj__() or super
end
end
private_constant :SuperHash
def initialize
#hash = SuperHash.new({})
end
end
https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html

Rails - where to put model helper methods

I want to hide certain implementations from the main Model methods, because of clean code reasons. I don't want my Model to contain a lot of huge methods, only the clearest and verbose functionalities.
For example:
class SomeModel
#included stuff
#fields & attrs
def modelMethod
variable = functionality1(functionality2)
if some condition
functionality3
else
functionality4
end
end
Should I put my functionality methods under a private or protected part at the end of the same model file, or should I put them into a helper file?
If I'm right, the codes in the helpers are only used for the View. What is the convention for this?
Having private or protected has nothing to do with the type of cleanup you're trying to do.
This is related to inheritance method visibility/access (though inheritance can obviously be used for reusability).
Methods will depends on reusability. Why not leverage concerns? Say we have SomeModel and want multiple models to implement suspensions.
# app/models/some_model.rb
class SomeModel
include Suspendable
end
Then add your model concern.
# app/models/concerns/suspendable.rb
module Suspendable
extend ActiveSupport::Concern
included do
has_one :suspension
scope :active, -> { joins('LEFT OUTER JOIN suspensions').where(suspension: {id: nil} }
end
end
Or if this only really applies to a single model, but want to keep the model strictly DB manipulations (not Business oriented), then you could have namespaced concerns.
# app/models/concerns/some_model/availability.rb
module SomeModel::Availability
extend ActiveSupport::Concern
module ClassMethods
def availabilities_by_some_logic
end
end
end
http://api.rubyonrails.org/v5.0/classes/ActiveSupport/Concern.html
If you have a method or set of methods that are used in various models:
Rails Concerns
This is different from private/protected and you can have private/protected methods in a concern. This is just how to extract out duplication.
If you have a method that is needed by the model, and only the model (not subclasses of the model, and never called outside the class:
private
If you have a method that is needed by the model and its subclasses but not from outside the model:
protected
If you need to be able to call the method from outside the class:
neither
this answer goes into better detail on those

Difference between adding instance methods with Active Concern and a regular ruby module?

What is the difference between adding instance methods whith active concern and through the normal ruby def keyword on the module?
module MonsterConcern
extend ActiveSupport::Concern
included do
def engage_rage
end
def chew_bones
end
end
end
and
module MonsterConcern
def engage_rage
end
def chew_bones
end
end
As far as I know there is no difference if the only thing you are interested in is instance methods.
The ActiveSupport::Concern advantage is the ability to define class methods, and to handle some nasty module interdependencies better (related to calling class methods in the included block).
You can read more here: http://api.rubyonrails.org/classes/ActiveSupport/Concern.html

Helper methods for models in Rails

Is there a proper place for helper methods for models in Rails? There are helper methods for controllers and views, but I'm not sure where the best place to put model helper methods. Aside from adding a method to ActiveRecord::Base, which I'd prefer not to.
UPDATE: It seems Concerns make a lot of sense. Here's an example of what I want. Certain models can never be deleted, so I add a callback that always throws an exception:
before_destroy :nope
def nope
raise 'Deleting not allowed'
end
With concerns, I could do something like this?
class MyModel < ActiveRecord::Base
include Undeletable
end
module Undeletable
extend ActiveSupport::Concern
included do
before_destroy :nope
end
def nope
raise 'Deleting not allowed'
end
end
Is this the Rails way of doing this?
If you want to use a helper_method my_helper_method inside a model, you can write
ApplicationController.helpers.my_helper_method
If you need a bit more flexibility, for example if you also need to override some methods, you can do this:
class HelperProxy < ActionView::Base
include ApplicationController.master_helper_module
def current_user
#let helpers act like we're a guest
nil
end
def self.instance
#instance ||= new
end
end
and then use with
HelperProxy.instance.my_helper_method
If you have strong nerves, you can also try to include the ApplicationController.master_helper_module directly into your model.
via : makandracards's post.
For your reference: http://railscasts.com/episodes/132-helpers-outside-views
If what you are asking is where to put code that is shared across multiple models in rails 4.2, then the standard answer has to be to use Concerns: How to use concerns in Rails 4
However, there are some good arguments (e.g. this) to just using standard rails module includes, and extends as marek-lipka suggests.
I would strongly recommend NOT using ApplicationController helper methods in a model, as you'll be importing a lot unnecessary baggage along with it. Doing so is usually a bad smell in my opinion, as it means you are not separating the MVC elements, and there is too much interdependency in your app.
If you need to modify a model object by adding a method that is just used within a view, then have a look at decorators. For example https://github.com/drapergem/draper

rails application design: static methods

Which would be the most elegant way to define static methods such as "generate_random_string", "generate_random_user_agent", which are called from different libraries?
What are the best practices?
Best practice as I've seen would include:
Put them in a module in /lib/
Include them as mixins in the rest of your application code.
Make sure they are thoroughly tested with their own rspecs (or whatever test tool you user).
Plan them as if you may at some point want to separate them out into their own gem, or potentially make them available as a service at some point. That doesn't mean design them as separate services from the beginning, but definitely make sure they have no dependencies on any other code in your application.
Some basic code might be something like:
module App::Services
def generate_random_string
# ...
end
def generate_random_user_agent
# ...
end
end
Then in your model or controller code (or wherever), you could include them like this:
class MyModelClass < ActiveRecord::Base
include App::Services
def do_something_here
foo = random_string
# whatever...
end
def random_string
generate_random_string
end
end
Notice I isolated the generate_random_string call in its own method so it can be used in the model class, but potentially be switched out for some other method easily. (This may be a step more than you want to go.)

Resources