Get the name of the currently if condition declaration - ruby-on-rails

I was wondering if it was possible to pass as parameter the if condition statement as string or symbol. Because method name or if statement name could change and if I need it to refacto things it's better to work with a variable,
here an example inside a simple update method.
#within any controller
class FooController < ApplicationController
include RedirectAfterFooUpdate
# other methods
def update
#foo.update(place_params)
if #foo.save
action_after_update_foo(some_parameters)
else
# error redirection...
end
end
end
#within a module need to set correct action after update foo
module RedirectAfterFooUpdate
def action_after_update_foo(some_parameters)
if condiction_1
do_something(condiction_1.to_s.to_sym) #do_something(:condiction_1)
elsif condiction_2
do_something_else(condiction_2.to_s.to_sym) #do_something_else(:condiction_2)
elsif condiction_3
do_something_else_again(condiction_3.to_s.to_sym) #do_something_else_again(:condiction_3)
else
#casual code
end
end
end
the code above is clearly simplified and i actually have many other paramaters, but the thing is really focus on the "if statement" => condiction_1 or condiction_2 or condiction_3. How could we get the name of it .
The question Get the name of the currently executing method is not really helpful in that case because I do not need the root method name action_after_update_foo.

If the conditions are just method invocations, you could use the following approach that uses Ruby's send method to evaluate each condition:
module RedirectAfterFooUpdate
def action_after_update_foo(some_parameters)
# Declare the conditions that represents method invocations
conditions = [
:only_refund_changed,
:another_condition_x,
:another_condition_y
]
conditition_executed = false
conditions.each do |condition|
# Executes each if / elsif block
if !conditition_executed && send(condition)
# Invoke do_something with the condition as a symbol
do_something(condition)
conditition_executed = true
end
end
# Executes the else block
if !conditition_executed
#casual code
end
end
end

Related

Ruby - How to create a class variables to access in function

I want to create a function to set the instance variable like attr_reader
class Base
def exec
# get all functions to check
# if all functions return true
# I will do something here
end
end
And then I have a class inherit Base.
class SomeClass < Base
check :check_1
check :check_2
def check_1
# checking
end
def check_2
# checking
end
end
class Some2Class < Base
check :check_3
check :check_4
def check_3
# checking
end
def check_4
# checking
end
end
Because I only need 1 logic for executing in all classes but I have a lot the different checks for each class, I need to do it flexibly.
Please, give me a keyword for it.
Many thanks.
In order to have check :check_1 you need to define check as a class method:
class Base
def self.check(name)
# ...
end
end
Since you want to call the passed method names later on, I'd store them in an array: (provided by another class method checks)
class Base
def self.checks
#checks ||= []
end
def self.check(name)
checks << name
end
end
This already gives you:
SomeClass.checks
#=> [:check_1, :check_2]
Some2Class.checks
#=> [:check_3, :check_4]
Now you can traverse this array from within exec and invoke each method via send. You can use all? to check whether all of them return a truthy result:
class Base
# ...
def exec
if self.class.checks.all? { |name| send(name) }
# do something
end
end
end
SomeClass.new.exec # doesn't do anything yet
The self.class part is needed because you are calling the class method checks from the instance method exec.

Rails 4 ActionMailer around_action | access action information (action_name & parameters)

I'm building an around_action for my customer_mailer class so that I don't have to wrap begin and rescue around every time I call deliver_now
class CustomerMailer < ApplicationMailer
around_action :rescue_error
def send_email(customer)
...
end
def invite_friend(customer, invitee_email)
...
end
private
def rescue_error
yield
rescue => e
msg = "Caught exception! #{e} | #{action_name}"
puts msg
raise
end
end
So in the rescue, I want to log the message with information such as which action was called, I managed to find the method action_name to show which action was called, but I couldn't find a way to retrieve the parameters that were passed into the action, any ideas?
Thanks!
Before I answer your question: would using Bugsnag or something similar work in your case? Alternatively would rescue_from Exception, with: :exception_handler work for you? (it won't allow you to reraise the exception though)
I dug into Rails source code and it seems that parameters are not stored anywhere. They are just passed as a splat to an instance method defined in your mailer class. However, there is a way to store them (without monkey-patching).
Mailers inherit from AbstractController::Base. Looking at the snippet below:
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
# is the intended way to override action dispatching.
#
# Notice that the first argument is the method to be dispatched
# which is *not* necessarily the same as the action name.
def process_action(method_name, *args)
send_action(method_name, *args)
end
# Actually call the method associated with the action. Override
# this method if you wish to change how action methods are called,
# not to add additional behavior around it. For example, you would
# override #send_action if you want to inject arguments into the
# method.
alias send_action send
we can see that we can override #send_action and make it store the arguments. Add the following to your ApplicationMailer:
class ApplicationMailer < ActionMailer::Base
def send_action(method_name, *args)
#action_args = args
super
end
end
The arguments will be available as #action_args in all your mailers.
Just store the parameters with which the action has been called to an instance variable, say #params. Then these parameters will be accessible in rescue_error via #params. As per your example:
class CustomerMailer < ApplicationMailer
around_action :rescue_error
def send_email(customer)
#params = { customer: customer }
...
end
def invite_friend(customer, invitee_email)
#params = { customer: customer, invitee_email: invitee_email }
...
end
private
def rescue_error
begin
yield
rescue => e
msg = "Caught exception! #{e} | #{action_name} | #{#params.inspect}"
puts msg
raise
end
end
end
You can make the assignment to #params a bit cleaner by using hash parameters in your actions, e.g.
def invite_friend(options = {})
#params = params
...
end
Of course, this requires accessing the parameters via options, such as options[:customer] to access customer, and options[:invitee_email] to access invitee_email.
The action name have to be yielded , it depends on the way you use your rescue_error .
Define a variable in the block that will be yielded
or raise specifics errors (maybe your custom exception class )
this way you'll retrieve invormation via "e"
post an exemple use case of rescue_error.

setter not setting session variable in rails

I have a controller that counts the number of times the user has been to a page. I'm trying to extract that count to a getter and setter which set a session variable. Getting works, but setting doesn't. This is the controller:
class StoreController < ApplicationController
def index
#products = Product.order(:title)
v = store_visits + 1
store_visits = v # tests fail if I do it like this
# store_visits += 1 # Undefined method '+' for NilClass if i do it like this
#visits = store_visits
end
def store_visits
if session[:store_counter].nil?
session[:store_counter] = 0
end
session[:store_counter]
end
def store_visits=(value)
session[:store_counter] = value
end
end
And here's a failing test:
require 'test_helper'
class StoreControllerTest < ActionController::TestCase
test "should count store visits" do
get :index
assert session[:store_counter] == 1
get :index
assert session[:store_counter] == 2
end
end
Why isn't it setting, and why is store_visits returning nil if I use += ? Any help is appreciated.
Note: Originally I extracted the methods to a concern, but I've edited this to remove the concern, because the problem isn't with the concern, it's with the setter and/or getter.
Update: After adding logging statments, it's obvious that the inside of the store_visits=() method is never reached (but somehow an error is not thrown). However, if I rename it to assign_store_visits(), it does get called, and does update the session variable. So I'm guessing this is either a bug where setter methods don't work in controllers (this is Rails 4.0.0) or they're intentionally blocked (in which case, an exception would be nice).
try switch to include ActiveSupport::Concern
this will provide instance methods instead class methods
You need to wrap the methods inside of your concern inside of an included block like:
module Visits
extend ActiveSupport::Concern
included do
#private
def store_visits
if session[:store_counter].nil?
session[:store_counter] = 0
end
session[:store_counter]
end
def store_visits=(value)
session[:store_counter] = value
end
# private
end
end
end
Doing that will make those methods available as instance methods inside of your controller.

Ruby Converting a variable into a constant at runtime

I'm trying to create a module that will be included in many different classes. It needs to record the caller's path to the class file
so I can reference the path in later code. This code tries to add a method to the calling class, but fails because it just returns the current value of ##x.
# /home/eric/FindMe.rb
class FindMe
include GladeGUI
end
# /home/eric/GladeGUI.rb
module GladeGUI
def self.included(obj)
##x, = caller[0].partition(":") # this works ##x = "/home/eric/FindMe.rb"
obj.class_eval do
def my_class_file_path
return ????? # I want to return "/home/eric/FindMe.rb"
end
end
end
end
The GladeGUI module will be "included" in many different classes, so I can't just add code to the calling class. I need a way to make ##x compile into a constant value, so the method stored in the class looks like this:
def my_class_file_path
return "/home/eric/FindMe.rb"
end
How do I convert a variable to a constant in code?
Thanks.
It seems like you don't actually need it to be a "constant" - you just need some way to make the method return the correct value all the time and not allow other code to come along and change the value (with the current ##x solution, someone can just modify ##x and it will break)
The solution is to store the data in a local variable instead of a class or instance variable, and then access that local variable via a closure.
No other code will have scope to 'see' the local variable and thus it cannot be changed.
But then the problem becomes that when you use def inside a class_eval, the scope of the caller isn't captured, so the code can't see your local variable. You can use define_method instead
Here's an example
# /home/eric/GladeGUI.rb
module GladeGUI
def self.included(obj)
caller_file_path = caller[0].split(":").first
obj.class_eval do
define_method :my_class_file_path do
return caller_file_path
end
end
end
end
# /home/eric/FindMe.rb
class FindMe
include GladeGUI
end
puts FindMe.new.my_class_file_path # prints the correct path
But - what if you want my_class_file_path to be a class method rather than an instance method - use define_singleton_method instead:
module GladeGUI
def self.included(obj)
caller_file_path = caller[0].split(":").first
obj.class_eval do
define_singleton_method :my_class_file_path do
return caller_file_path
end
end
end
end
...
puts FindMe.my_class_file_path
Interesting side note: This is how you can fake "private variables" in javascript :-)

Ruby syntax, semantic questions def status=(status)

I was looking at this code and was trying to figure what def status=(status) means. I have never seen that before.
class Tweet
attr_accessor :status
def initialize(options={})
self.status = options[:status]
end
def public?
self.status && self.status[0] != "#"
end
def status=(status)
#status = status ? status[0...140] : status
end
end
I'll try answering this in layman's terms, since I didn't understand this when starting out.
Let's say you want the Tweet class to have an attribute status. Now you want to change that attribute, well you can't since it's hidden inside the class. The only way you can interact with anything inside a class is by creating a method to do so:
def status=(status)
#status = status # using # makes #status a class instance variable, so you can interact with this attribute in other methods inside this class
end
Great! Now I can do this:
tweet = Tweet.new
tweet.status = "200" # great this works
# now lets get the status back:
tweet.status # blows up!
We can't access the status variable since we haven't defined a method that does that.
def status
#status # returns whatever #status is, will return nil if not set
end
Now tweet.status will work as well.
There are shorthands for this:
attr_setter :status #like the first method
attr_reader :status # like the second one
attr_accessor :status # does both of the above
That is a setter - the method to be called when you say thing.status = whatever.
Without such a method, saying thing.status = whatever would be illegal, since that syntax is merely syntactic sugar for calling the setter.
It means exactly the same thing that def foo always means: define a method named foo.
def initialize
Defines a method named initialize.
def public?
Defines a method named public?
def status=
Defines a method named status=
That's it. There's absolutely nothing special going on here. There is no magic when defining a method whose name ends in an = sign.
The magic happens when calling a method whose name ends in an = sign. Basically, you are allowed to insert whitespace in between the = sign and the rest of the method name. So, instead of having to call the method like this
foo.status= 42
You can call it like this:
foo.status = 42
Which makes it look like an assignment. Note: it is also treated like an assignment in another way; just like with all other forms of assignments, assignment expressions evaluate to the value that is being assigned, which means that the return value of the method is ignored in this case.

Resources