Ruby. Calling Super from the overriden method - ruby-on-rails

I am trying to override a the redirect_to method to add an additional param to the get requests (if thats present)
The redirect_to method is here
module ActionController
...................
module Redirecting
extend ActiveSupport::Concern
include AbstractController::Logger
...........................
def redirect_to(options = {}, response_status = {}) #:doc:
............................
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
self.response_body = "<html><body>You are being redirected.</body></html>"
end
end
end
Here is how I am trying to override
module ActionController
module Redirecting
def redirect_to(options = {}, response_status = {})
if options
if options.is_a?(Hash)
options["custom_param"] = #custom_param
else
if options.include? "?"
options = options + "&custom_param=true"
else
options = options + "?custom_param=true"
end
end
end
super
end
end
end
I am apparently doing it wrong, and the super method call fails to work the way I wanted it. Hope someone could help.

I believe the problem here is that you are redefining the redirect_to method, not defining in a new place. super cannot call the original because it no longer exists.
The method you are looking for is alias_method_chain
module ActionController
module Redirecting
alias_method_chain :redirect_to, :extra_option
def redirect_to_with_extra_option(options = {}, response_status = {})
if options
...
end
redirect_to_without_extra_option(options, response_status)
end
end
end
Though, I think the more Rails friendly way would be to override redirect_to in your ApplicationController
class ApplicationController
....
protected
def redirect_to(...)
if options
....
end
super
end
end
The benefits to this approach are that you aren't patching rails and the application specific param is now set in your application controller.

Related

How can I redirect from a Module?

I tried TO Google Can I redirect_to in rails modules but couldn't come up with anything. Basically, I have a method that I am going to use across a couple of Controllers.
lib/route_module.rb
module RouteModule
def self.user_has_active_chocolate(c_id, u_id)
chocolate_id = c_id
user_id = u_id
unless UserChocolate.where(id: chocolate_id).empty?
if UserChocolate.where(id: chocolate_id).last.active?
true
else
false
# BREAKS OVER HERE...
redirect_to "/user/new-chocolate/#{user_id}"
end
else
false
redirect_to "/admin"
end
end
end
app/controllers/user_controllers.rb
include RouteModule
before_filter :thingz, only: [:display_user_chocolate]
# private
def thingz
RouteModule.user_has_active_chocolate(params["chocolate_id"], params["user_id"])
end
But... whenever I run this... It will break as soon as it hit's redirect_to.
undefined method `redirect_to' for RouteModule:Module
My other option is use ActiveSupport::Concerns but I just trouble converting this Module into a Concern.
When you include a module, it acts as a mixin. That said, you include and get all the methods of the module in the context of your class. The proper way would be:
module RouteModule
def user_has_active_chocolate(c_id, u_id) # NO self
...
end
end
And in the class:
include RouteModule
def thingz
# NO module method call (NO RouteModule)
user_has_active_chocolate(params["chocolate_id"], params["user_id"])
end

Storing current optional route scope before calling deliver_later on rails mailer

Rails 4.2
Ruby 2.3
I have two optional routing scopes relating to locale information. They are set in a before_action in the application_controller which configures the default_url_options method. i.e.
# app/controllers/application_controller
# simplified version, usually has two locale values,
# locale_lang and locale_city
before_action :redirect_to_locale_unless_present
private
# If params[:locale] is not set then
# redirect to the correct locale base on request data
def redirect_to_locale_unless_present
unless params[:locale].present?
redirect_to url_for(locale: request.location.country_code)
end
end
def default_url_options(options = {}
{ locale_lang: params[:locale_lang] }.merge(options)
end
The scopes are locale_lang and locale_city which end up looking something like http://localhost:3000/fr/ or http://localhost:3000/en/
This all works as intended in the browser, however I would like to utilize ActionMailer::DeliveryJob to send emails in background processes. The obvious issue to this is that ActionMailer::DeliveryJob doesn't store the value of params[:locale].
I would like to be able to call SomeMailer.generic_email(options).deliver_later and have this send the current default_url_options to the ActionMailer::DeliveryJob which would then pass those along the chain and use them when actually processing the mail. I could of course define default_url_options as a parameter for each Mailer method but I would much rather set up the app so it was automatically included.
Have you ever encountered this issue or have any suggestions on how to approach the task. Keep in mind that it should also be thread safe.
My currently failing approach is to save the current request in Thread.current and then retrieve those when enqueue_delivery is called via .deliver_later. I then wanted to override ActionMailer::DeliveryJob's perform method to accept the url_options and use class_eval to define the default_url_options method within the current mailer class. However, perform doesn't seem to even be called when using deliver_later any ideas?
class ApplicationController
before_action :store_request
private
def store_request
Thread.current['actiondispatch.request'] = request
end
end
module DeliverLaterWithLocale
module MessageDeliveryOverrides
def enqueue_delivery(delivery_method, options={})
args = [
#mailer.name,
#mail_method.to_s,
delivery_method.to_s,
url_options,
*#args
]
ActionMailer::DeliveryJob.set(options).perform_later(*args)
end
private
def url_options
options = {}
request = Thread.current["actiondispatch.request"]
if request
host = request.host
port = request.port
protocol = request.protocol
lang = request.params[:locale_lang]
city = request.params[:locale_city]
standard_port = request.standard_port
options[:protocol] = protocol
options[:host] = host
options[:port] = port if port != standard_port
options[:locale_lang] = lang
options[:locale_city] = city
end
ActionMailer::Base.default_url_options.merge(options)
end
end
module DeliveryJobOverrides
def perform(mailer, mail_method, delivery_method, url_options, *args)
mailer = mailer.constantize.public_send(mail_method, *args)
Kernel.binding.pry
mailer.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def default_url_options_with_options(*args)
default_url_options_without_current_request(*args).merge(url_options)
end
alias_method_chain :default_url_options, :options
RUBY
mailer.send(delivery_method)
end
end
end
Incase anyone else wants to do this. I fixed it by adding
class ApplicationController
before_action :store_request
private
def store_request
Thread.current['actiondispatch.request'] = request
end
end
module DeliverLaterWithLocale
module MessageDeliveryOverrides
def enqueue_delivery(delivery_method, options={})
args = [
#mailer.name,
#mail_method.to_s,
delivery_method.to_s,
url_options,
*#args
]
ActionMailer::DeliveryJob.set(options).perform_later(*args)
end
private
def url_options
options = {}
request = Thread.current["actiondispatch.request"]
if request
host = request.host
port = request.port
protocol = request.protocol
lang = request.params[:locale_lang]
city = request.params[:locale_city]
standard_port = request.standard_port
options[:protocol] = protocol
options[:host] = host
options[:port] = port if port != standard_port
options[:locale_lang] = lang
options[:locale_city] = city
end
ActionMailer::Base.default_url_options.merge(options)
end
end
module DeliveryJobOverrides
def perform(mailer, mail_method, delivery_method, url_options, *args)
mailer = mailer.constantize
mailer.default_url_options = url_options
mailer.public_send(mail_method, *args).send(delivery_method)
end
end
end
And then prepend these to the respective classes in an initializer

Code duplication in a module methods of class and instance levels

I have Memoize module, that provides methods for caching of class and instance methods.
module Memoize
def instance_memoize(*methods)
memoizer = Module.new do
methods.each do |method|
define_method method do
#_memoized_results ||= {}
if #_memoized_results.include? method
#_memoized_results[method]
else
#_memoized_results[method] = super()
end
end
end
end
prepend memoizer
end
def class_memoize(*methods)
methods.each do |method|
define_singleton_method method do
#_memoized_results ||= {}
if #_memoized_results.include? method
#_memoized_results[method]
else
#_memoized_results[method] = super()
end
end
end
end
end
This is an example of how I use it:
class Foo
extend Memoize
instance_memoize :instance_method1, :instance_method2
class_memoize :class_method1, :class_method2
...
end
Please advice how to avoid code duplication in this module.
One might define a lambda:
λ = lambda do
#_memoized_results ||= {}
if #_memoized_results.include? method
#_memoized_results[method]
else
#_memoized_results[method] = super()
end
end
And. then:
define_method method, &λ
Please be aware of an ampersand in front of λ, it is used to notice define_method about it’s receiving a block, rather than a regular argument.
I did not get what failed with this approach on your side, but here is a bullet-proof version:
method_declaration = %Q{
def %{method}
#_memoized_results ||= {}
if #_memoized_results.include? :%{method}
#_memoized_results[:%{method}]
else
#_memoized_results[:%{method}] = super()
end
end
}
methods.each do |method|
class_eval method_declaration % {method: method}
end

What is the best way to specify global and per-class configuration for a Gem that extends ActiveRecord models in Ruby on Rails?

I have a gem that adds a certain functionality to ActiveRecord Models. I want a few parameters of this functionality to be modifiable, both at a global level, and at the time of adding my gem to each model, in case some models need different settings.
I'm doing this:
module Mygem
##config = nil
def self.config(options={})
defaults = {some_key: default_value}
##config ||= defaults
##config = ##config.merge(options)
##config
end
module ClassMethods
def has_something_my_gem_adds(options={}) # method you call to add this functionality to your model
options = Mygem.config.merge(options)
# add functionality to the model
define_method(:my_gem_options) { options } # I don't think this is the best way to store this, but I didn't find a better one.
end
def does_something
if self.my_gem_options[:some_key]
end
end
end
end
I'm not very happy with creating the "my_gem_options" method, but I haven't found a good way to store that configuration information for that model, that would them be available for all the methods in my gem.
What is the best way to do this?
Additionally, I also don't what's the best way to store the "global" config for my gem, which I'm storing in ##config in the module itself. Is there a better way?
If you want to avoid using define_method dynamically, maybe a cleaner version would be:
module Foo
DEFAULTS = {name: "foo"}
def self.configure(options = {})
#config = DEFAULTS.merge(options)
end
def self.config
#config
end
configure({})
def self.included(model)
model.send(:extend, ClassMethods)
end
module ClassMethods
def foo(options)
#foo_config = Foo.config.merge(options)
end
def foo_config
#foo_config
end
end
def name
self.class.foo_config.fetch(:name)
end
end
class Bar
include Foo
foo(name: "bar")
end
puts Bar.new.name
# => "bar"
If you want to avoid storing the configuration in the model itself (which can be a good idea, but you lose some semantics, like inheritance), then you can have a hash in your main module:
module Foo
DEFAULTS = {name: "foo"}
def self.configure(options = {})
#config = DEFAULTS.merge(options)
end
def self.config
#config
end
def self.model_config
#model_config ||= {}
end
configure({})
def self.included(model)
model.send(:extend, ClassMethods)
end
module ClassMethods
def foo(options)
Foo.model_config[self] = Foo.config.merge(options)
end
end
def name
Foo.model_config[self.class].fetch(:name)
end
end
class Bar
include Foo
foo(name: "bar")
end
puts Bar.new.name
# => "bar"

How to add an instance method from inside of a class method which accepts a block to be executed in the context of the instance

I've entirely reworded this question as I feel this more accurately reflects what I wanted to ask the first time in a less roundabout way.
After instantiating a FormObject, calls to dynamically defined methods do not evaluate their block parameter in the context I'm trying for. For example:
#registration = RegistrationForm.new
#registration.user
# undefined local variable or method `user_params' for RegistrationForm:Class
RegistrationForm calls a class method exposing(:user) { User.new(user_params) } which I would like to have define a new method that looks like this:
def user
#user ||= User.new(user_params)
end
My implementation doesn't use #ivar ||= to cache the value (since falsey values will cause the method to be re-evaluated). I borrowed the idea from rspec's memoized_helpers and I 'think' I understand how it works. What I don't understand is what I should replace class_eval with in lib/form_object/memoized_helpers.rb.
Thank you
lib/form_object/base.rb
class FormObject::Base
include ActiveModel::Model
include FormObject::MemoizedHelpers
attr_reader :params, :errors
def initialize(params = {})
#params = ActionController::Parameters.new(params)
#errors = ActiveModel::Errors.new(self)
end
def save
valid? && persist
end
end
lib/form_object/memoized_helpers.rb
module FormObject
module MemoizedHelpers
private
def __memoized
#__memoized ||= {}
end
def self.included(mod)
mod.extend(ClassMethods)
end
module ClassMethods
def exposing(name, &block)
raise "#exposing called without a block" unless block_given?
class_eval do
define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = block.call } }
end
end
end
end
end
app/forms/registration_form.rb
class RegistrationForm < FormObject::Base
exposing(:user) { User.new(user_params) { |u| u.is_admin = true } }
exposing(:tenant) { user.build_tenant(tenant_params) }
validate do
tenant.errors.each do |key, value|
errors.add("#{tenant.class.name.underscore}_#{key}", value)
end unless tenant.valid?
end
validate do
user.errors.each do |key, value|
errors.add("#{user.class.name.underscore}_#{key}", value)
end unless user.valid?
end
private
def persist
user.save
end
def user_params
params.fetch(:user, {}).permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
def tenant_params
params.fetch(:tenant, {}).permit(:name)
end
end
So, I might have simplified this example too much, but I think this is what you want:
module Exposing
def exposing(name, &block)
instance_eval do
define_method(name, block)
end
end
end
class Form
extend Exposing
exposing(:user) { user_params }
def user_params
{:hi => 'ho'}
end
end
Form.new.user
You can fiddle around here: http://repl.it/OCa

Resources