Does a helper class in Rails 5 (for example UserHelper) have access to the model type it is related to (User)?
I have some shared logic AddFilter that requires the model type to work propery. I now enforce a method filter_for to specify which model is used:
module AddFilter
def filter_for
raise "filter_for not implemented"
end
#...other code
end
Currently I include that logic in many of my helpers:
module UserHelper
include AddFilter
def filter_for
User
end
end
It is possible to retrieve the model type in the AddFilter directly?
Apparently since helpers are included in controllers, a feature from controllers can be used:
# add_filter.rb
def filter_for
controller_path.classify.constantize
end
This retrieves the controller path, classifies the name, then tries to find the constant specified.
Still feels rather hacky, better solutions appreciated!
Related
I have a method current_org that's defined simply as:
def current_org
Organization.find_by(subdomain: Apartment::Tenant.current)
end
It's always the same, whether it's in a view, controller, a model, or even a service. Since the current tenant is derived from the database connection, I shouldn't have to worry about it being properly scoped. And I find myself using it everywhere.
What's the best way to define a global method in Rails so I can just call current_org from anywhere? Currently my best solution is defining a module in /lib and calling it with CustomHelperMethods.current_org. But I'm looking for something a little cleaner.
I'd put it as a class method in an Organiation model or create a special service/class that fetches it.
class Organization < ApplicationRecord
def self.current_org
find_by(subdomain: Apartment::Tenant.current)
end
end
or
# e.g. in app/services/
class CurrentOrganization
def self.current_org
Organization.find_by(subdomain: Apartment::Tenant.current)
end
end
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
In my case I get an instance of a resource and I have to find out, whether it's model class represents a singular or plural resource.
I digged through Rails.application.routes and it's instance variables but did not find any possibility to solve this problem.
Update:
I am using polymorphic_path helpers with anonymous model instances in a gem that includes some modules into models and controllers. The final goal is to provide some automatisms for REST controllers and models and provide pathes / routes of the current model/instance as well as it's ancestors.
My latest attempts looked something like that:
Rails.application.routes.instance_variable_get(:#router).instance_variable_get(:#routes).instance_variable_get(:#named_routes).delete_if{|k,v| !k.match(/my_model_name_as_downcased_string_here/)}.values.map{|v| v.instance_variable_get(:#defaults)}.map{|h| h[:action]}
In the hope it would bring up action :index for plural models and :show for singular. There MUST be a way to make use of introspection to find out whether a model is a singular one, I simply do not find the catch.
Since I did not find any way to find out, whether a resource is singular or plural, I build something on my own. The most secure way IMHO is to hook into route creation direcly. Here's what I did in case anyone encounters the same problem:
# alias :resource method of Rails routing mapper and remember which resourses
# are setup as singular
module ActionDispatch::Routing::Mapper::Resources
def resource_with_singular_recognizer *res, &block
Rails.application.instance_variable_set(:#sr, ((Rails.application.instance_variable_get(:#sr) || []).push res.first).uniq)
resource_without_singular_recognizer *res, &block
end
alias_method_chain :resource, :singular_recognizer
end
# define a model wide method :singular_resource? that checks the previously
# setup singular resource flag
module ClassMethods
def singular_resource?
Rails.application.instance_variable_get(:#sr).include? self.to_s.downcase.to_sym
end
end
# include :singular_resource? method into all model classes
module Mongoid::Document
def self.included base
base.class_eval do
extend ClassMethods
end
end
end
I know :alias_method_chain is not the best choice here, feel free to find a better solution.
I'm doing a bit of metaprogramming in Ruby. I'm writing a library to meta-define some methods for me, specifically in the controller (automate some find_by methods that I have to write for my applications).
Currently I generate these methods by having to pass the name of the model for a particular controller into my meta-programming method. Is there a method in a controller that is tied to an ActiveRecord model.
So, here is a poor example
module AwesomeGem
module ClassMethods
def write_some_methods_for(model)
raise "Class #{model.class} does not inherit ActiveRecord::Base" unless model < ActiveRecord::Base
define_method "money_remaining" do |id=nil|
moolah = id ? model.find(id).money : model.find(params[:id]).money
render text: moolah
end
define_method "money_remaining_poller" do |id=nil|
obj = id ? model.find(id) : model.find(params[:id])
# composes some ajax
render js: moneyjs
moneyjs
end
end
end
end
So, to use this method, I plan to
GamblerController < ApplicationController
write_some_methods_for Gambler
end
Again, how could I make it so I don't have to pass the Gambler class to my method? Is there some sort of method or attribute that I could just call the model directly? eg. self.send(:model)
A simple question with a complex explanation.
Controllers are not tied to a particular model by default. You can have a controller playing with several different models, or even a controller using no model at all.
If you still want your code to work automatically in "classic" cases, you could look at the controller's name, and look for a model with the same name (following rails naming conventions).
I'm in the process of writing an Importable concern for my rails project. This concern will provide a generic way for me to import a csv file into any model that includes Importable.
I need a way for each model to specify which field the import code should use to find existing records. Are there any recommended ways of adding this type of configuring for a concern?
A slightly more "vanilla-looking" solution, we do this (coincidentally, for the exactly some csv import issue) to avoid the need for passing arguments to the Concern. I am sure there are pros and cons to the error-raising abstract method, but it keeps all the code in the app folder and the models where you expect to find it.
In the "concern" module, just the basics:
module CsvImportable
extend ActiveSupport::Concern
# concern methods, perhaps one that calls
# some_method_that_differs_by_target_class() ...
def some_method_that_differs_by_target_class()
raise 'you must implement this in the target class'
end
end
And in the model having the concern:
class Exemption < ActiveRecord::Base
include CsvImportable
# ...
private
def some_method_that_differs_by_target_class
# real implementation here
end
end
Rather than including the concern in each model, I'd suggest creating an ActiveRecord submodule and extend ActiveRecord::Base with it, and then add a method in that submodule (say include_importable) that does the including. You can then pass the field name as an argument to that method, and in the method define an instance variable and accessor (say for example importable_field) to save the field name for reference in your Importable class and instance methods.
So something like this:
module Importable
extend ActiveSupport::Concern
module ActiveRecord
def include_importable(field_name)
# create a reader on the class to access the field name
class << self; attr_reader :importable_field; end
#importable_field = field_name.to_s
include Importable
# do any other setup
end
end
module ClassMethods
# reference field name as self.importable_field
end
module InstanceMethods
# reference field name as self.class.importable_field
end
end
You'll then need to extend ActiveRecord with this module, say by putting this line in an initializer (config/initializers/active_record.rb):
ActiveRecord::Base.extend(Importable::ActiveRecord)
(If the concern is in your config.autoload_paths then you shouldn't need to require it here, see the comments below.)
Then in your models, you would include Importable like this:
class MyModel
include_importable 'some_field'
end
And the imported_field reader will return the name of the field:
MyModel.imported_field
#=> 'some_field'
In your InstanceMethods, you can then set the value of the imported field in your instance methods by passing the name of the field to write_attribute, and get the value using read_attribute:
m = MyModel.new
m.write_attribute(m.class.imported_field, "some value")
m.some_field
#=> "some value"
m.read_attribute(m.class.importable_field)
#=> "some value"
Hope that helps. This is just my personal take on this, though, there are other ways to do it (and I'd be interested to hear about them too).