Rails - generating actions dynamically - ruby-on-rails

In a controller there is a code
def action1
generic_call __method__
end
def action2
generic_call __method__
end
#......
def action_n
generic_call __method__
end
private
def generic_call method_name
#..........
end
To get rid off repeating, would it be better to generate actions dynamically? Would it be more costly compared to static definition? How can I do that?

The major cost is actually having to repeat all the code yourself, remember that the philosophy on rails is DRY (Don't repeat yourself).
If there is an overhead at all by defining the methods with metaprogramming, you won't notice it at all, you can however run some benchmarks yourself just to be sure, but even the rails source code is full of metaprogramming and dynamic methods all over the place, in special with ActiveRecord.
class MyController < ActionController::Base
[:action1, :action2, :action3].each do |method_name|
send :define_method, method_name do
generic_call __method__
end
end
end

Related

Decorating object on method missing: yay or nay?

One of the reasons I don't really use the decorator pattern is having to call .decorate every time I need access to the decorator methods, which is quite often.
Would it be a good practice decorating on method missing, eg:
class User < ApplicationRecord
def foo
"foo!"
end
def method_missing(m, *args, &block)
if UserDecorator.instance_methods.include? m
UserDecorator.new( self ).decorate.send(m, args)
end
end
end
class UserDecorator < WhateverDecorator
def bar
"bar!"
end
end
If it's not a good practice, could you be so kind as to explain why?

Encapsulating controller logic in rails

I have 2 controllers in rails with different authentications schemes,
but they do almost the same.
What is the best way in rails to encapsulate
the logic of a controller in another class or helper?
Sample:
def ControllerA < BasicAuthController
def create
blablacode
end
end
def ControllerB < TokenAuthController
def create
blablacode
end
end
Whats the proper way to do this? create a model with the code?
Create a helper? other?
The simplest thing is to make a module and then include it into the other controllers:
module ControllerMixin
def create
blablacode
end
end
The remaining question, though, is where to put this code such that it is works with Rails autoloader, since it needs to be loaded before the controllers. One way to do it would be to write the module to a file in the lib/ directory, then add that to the autoload paths (see auto-loading-lib-files-in-rails-4
Why don't you enable both schemes for a single controller? Especially if the only difference is Authentication. You could have two app/controllers/concerns to encapsulate both authentication methods and include Auth1 and include Auth2 for a single controller who is only responsible for whatever resource it manages.
Otherwise, services are the best approach to encapsulate controller logic.
Create a folder called services in your app folder and write PORO classes here. Say you have a few places in your app where you want to pay for stuff via make Stripe.
# app/services/stripe_service.rb
module StripeService
def customer(args)
...
end
def pay(amount, customer)
...
end
def reverse(stripe_txn_id)
...
end
end
# controller
StripeService.customer(data)
=> <#Stripe::Customer>
Or if you only need to do one thing.
# app/services/some_thing.rb
module SomeThing
def call
# do stuff
end
end
# controller
SomeThing.call
=> # w/e
If you need an object with multiple reponsibilities you could create a class instead.
class ReportingService
def initialize(args)
...
end
def query
...
end
def data
...
end
def to_json
...
end
end
https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
I do it something like this:
#app/services/my_app/services/authentication.rb
class MyApp::Services::Authentication
class < self
def call(params={})
new(params).call
end
end # Class Methods
#==============================================================================================
# Instance Methods
#==============================================================================================
def initialize(params)
#params = params
end
def call
... do a lot of clever stuff
... end by returning true or false
end
private
def params() #params end
end
Then:
class FooController < ApplicationController
before_action :authenticate
def authenticate
redirect_to 'some_path' unless MyApp::Services::Authenticate.call(with: 'some_params')
end
end
Short answer, i choose to create a Helper.
From all the suggestions in the answers
Create a Module:
Seems correct but it didnt feel right to have logic outside
the app directory. This wasnt an external module or library but
something very related to the logic of my app.
Integrate diferents authentications in one controller:
Was a good suggestion but i have to change all the logic of my app.
Create a Helpers:
It seems to me the better solution, i had the code on a helper, and
is inside the app directory, very near from the other logic.

Access variables outside a class method

How do I access things outside of a class method in rails? I get an error like undefined method do_something_else
module Thing
def self.do_something
do_something_else
end
def do_something_else
end
end
Here's a good reference that shows the difference between class_methods/singleton_methods and instance_methods.
In your case, you cannot access the instance method(do_something_else) without an instance.
To solve this, you have to include the module in a class and use an instance of that class.
module Thing
def self.do_something
Logic.new.do_something_else
end
def do_something_else
#perform the logic and actions here
end
end
class Logic
include Thing
end
If you would like to think of it differently though, here's what I'd propose:
module Thing
def self.do_something_else
# perform your logic and actions here
end
def do_something
# this is possible because do_something_else is defined on the module Thing
Thing.do_something_else
end
end
Try this
def self.do_something
Thing.new.do_something_else
end

Changing respond_to url for an ActiveModel object

Summary:
How do I customize the path that respond_to generates for an ActiveModel object?
Update: I'm looking for a hook, method override, or configuration change to accomplish this, not a workaround. (A workaround is easy but not elegant.)
Context & Example:
Here is an example to illustrate. I have a model, Contract, which has a lot of fields:
class Contract < ActiveRecord::Base
# cumbersome, too much for a UI form
end
To make the UI code easier to work with, I have a simpler class, SimpleContract:
class SimpleContract
include ActiveModel::Model
# ...
def contract_attributes
# convert SimpleContract attributes to Contract attributes
end
def save
Contract.new(contract_attributes).save
end
end
This works well, but I have a problem in my controller...
class ContractsController < ApplicationController
# ...
def create
#contract = SimpleContract.new(contract_params)
flash[:notice] = "Created Contract." if #contract.save
respond_with(#contract)
end
# ...
end
The problem is that respond_with points to simple_contract_url, but I want it to point to contract_url instead. What is the best way to do that? (Please note that I'm using ActiveModel.)
(Note: I'm using Rails 4 Beta, but that isn't central to my problem. I think a good answer for Rails 3 will work as well.)
Sidebar: if this approach to wrapping a model in a lightweight ActiveModel class seem unwise to you, please let me know in the comments. Personally, I like it because it keeps my original model simple. The 'wrapper' model handles some UI particulars, which are intentionally simplified and give reasonable defaults.
First, here is an answer that works:
class SimpleContract
include ActiveModel::Model
def self.model_name
ActiveModel::Name.new(self, nil, "Contract")
end
end
I adapted this answer from kinopyo's answer to Change input name of model.
Now, for the why. The call stack of respond_to is somewhat involved.
# Start with `respond_with` in `ActionController`. Here is part of it:
def respond_with(*resources, &block)
# ...
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
# That takes us to `call` in `ActionController:Responder`:
def self.call(*args)
new(*args).respond
end
# Now, to `respond` (still in `ActionController:Responder`):
def respond
method = "to_#{format}"
respond_to?(method) ? send(method) : to_format
end
# Then to `to_html` (still in `ActionController:Responder`):
def to_html
default_render
rescue ActionView::MissingTemplate => e
navigation_behavior(e)
end
# Then to `default_render`:
def default_render
if #default_response
#default_response.call(options)
else
controller.default_render(options)
end
end
And that is as far as I've gotten for the time being. I have not actually found where the URL gets constructed. I know that it happens based on model_name, but I have not yet found the line of code where it happens.
I'm not sure that I fully understand your problem, but could you do something like this?
class SimpleContract
include ActiveModel::Model
attr_accessor :contract
# ...
def contract_attributes
# convert SimpleContract attributes to Contract attributes
end
def save
self.contract = Contract.new(contract_attributes)
contract.save
end
end
-
class ContractsController < ApplicationController
# ...
def create
#simple_contract = SimpleContract.new(contract_params)
flash[:notice] = "Created Contract." if #simple_contract.save
respond_with(#simple_contract.contract)
end
# ...
end
I may be way off base. Hopefully that at least triggers an idea for you.

where to put helper methods for controllers only?

I am looking to write certain methods for processing strings, and other tasks that take place in numerous of my controllers. I know its bad practice to include helpers in your controller, so I was just wondering, where is the best place to put application wide methods used in controllers?
I realize some of you will say to put them in models, but you have to realize that not all my controllers have an associated model. Any and all input would be appreciated.
I tend to put them into helpers. The fact that they are included in views
automatically haven't been a problem for me. You can also place them into
something like app/concerns/ or lib/
I don't like cluttering ApplicationController with private methods
because this often becomes a mess.
Example:
module AuthenticationHelper
def current_user
#current_user # ||= ...
end
def authenticate!
redirect_to new_session_url unless current_user.signed_in?
end
end
module MobileSubdomain
def self.included(controller)
controller.before_filter :set_mobile_format
end
def set_mobile_format
request.format = :mobile if request.subdomain == "m"
end
end
class ApplicationController
include AuthenticationHelper
include MobileSubdomain
end
If you need to use a method in the application scope then I would suggest that you keep those methods inside the application controller and in order to use them inside views.. declare those as helper methods.
For example,
class ApplicationController < ActionController::Base
helper_method :current_user, :some_method
def current_user
#user ||= User.find_by_id(session[:user_id])
end
def some_method
end
end
I would suggest to put them in lib folder. So for example I have:
lib/utils/string_utils
module StringUtils
def foo
...
end
end
class BarController < ActionController::Base
include StringUtils
end
This demonstrates good methodology called Fat model, Thin controller, in this case we are using Mixins instead of Models to separate logic but idea is same. You want your controllers as simple as possible.
It all depends on your needs. I will provide here 2 examples. Both of them are just a custom libraries, located under lib directory.
First example - "custom string processing"
# lib/filters.rb
module Filters
# Converts value to canonical view
def self.phone(value)
# remove all non-digits
clean_value = value.gsub(/\D/, '')
country_codes = configus.phone.country_codes
area_code = configus.phone.defaults.area_code
case clean_value.length
when 7
"#{area_code}#{clean_value}"
when 11
# remove country code only if phone starts with the allowed country code
if country_codes.include?(clean_value[0].to_i)
clean_value[1..-1]
else
clean_value
end
else clean_value
end
end
# usage
# app/api/phones_controller.rb
class Api::PhonesController < Api::ApplicationController
def exists
if params[:q]
clean_value = Filters.phone(params[:q])
...
end
end
end
Second example - helper for flash messages
# lib/flash_helper.rb
module FlashHelper
def flash_translate(key, options = {})
scope = [:flash, :controllers]
scope += params[:controller].split('/')
scope << params[:action]
t(key, {:scope => scope}.merge(options))
end
end
# app/application_controller.rb
class ApplicationController < ActionController::Base
include FlashHelper
end
# usage
# app/your_controller.rb
class YourController < ApplicationController
def create
#object = Object.new(params[:object])
if #object.save
flash[:success] = flash_translate(:success)
...
end
end
end
Note: do not forget to add lib dir to the autoload paths. In config/application.rb add/modify this line config.autoload_paths += %W(#{config.root}/lib).
So for me the answer is lib directory.
Starting from Rails 4 there is a dedicated folder for it app/controllers/concerns. So you can create a module there and then include it in a specific controller(s) or in ApplicationController if you need it available throughout all your controllers.
In case those methods are used in numerous controllers, I would define them in application_controller.rb. Every controller will inherits from it and will be capable to use any method defined there

Resources