rails - Executing multiple model methods in a begin/rescue - ruby-on-rails

I have multiple model methods and I want to loop and execute through each of them. How would I perform this in rails 2.3.11? Preferably in a begin/rescue.
Edit:
Thanks maprihoda, I used your example and was able to apply it with the begin/rescue:
class MyModel
def method_1
puts 'In method_1'
end
def method_2
puts 'In method_2'
end
def method_3
%w( method_1 method_2).each { |m|
begin
self.send(m)
rescue => e
puts "#{e.message}"
end
}
end
end

Something like this?
class MyModel
def method_1
puts 'In method_1'
end
def method_2
puts 'In method_2'
end
def method_3
%w( method_1 method_2).each { |m| self.send(m) }
end
end
my_model = MyModel.new
my_model.method_3

Related

Is there any way in Ruby/Rails to add a pre/post execution hook on all method calls within a method

For example lets say I have the following code
def my_method
do_work_1
5.times do
puts "This is a test"
end
do_work_2 "Hello"
do_work_3 do
puts "Inside block"
do_something_else
end
end
What I would like is to have the following logs:
do_work_1 called with no params
do_work_1 finished
do_work_2 called with param "Hello"
do_work_2 finished
do_work_3 called with a block param
do_work_3 finished
I could create a helper method and do something like the following
def my_method
log_call method(:do_work_1)
5.times do
puts "This is a test"
end
log_call method(:do_work_2) "Hello"
log_call method(:do_work_3) do
puts "Inside block"
do_something_else
end
end
def log_call(m, params)
puts("#{m.name} called with params #{params}")
m.call(params)
puts("#{m.name} finished")
end
But this isn't very pretty and would require us to use log_call on every method we call that we want logging. Is there a better way to go about achieving this goal?
In production code, I would use something explicit, similar to the log_call solution you already have. I would probably use something even more basic. Yes, it's a lot of typing, but everyone on your team will understand it.
In non-production code, or as a temporary measure, we can consider Module.prepend.
require 'minitest/autorun'
class Worker
def do_work_1(*); end
def do_work_3(*); end
def my_method
do_work_1
2.times { puts "This is a test" }
do_work_3 { puts "Inside block" }
end
end
module WorkLogging
%i[do_work_1 do_work_3].each do |m|
define_method(m) do |*args, **kwargs, &block|
puts format('%s called with: %s', m, [args, kwargs, block&.source_location].compact)
super(*args, **kwargs, &block)
puts format('%s finished', m)
end
end
end
Worker.prepend(WorkLogging)
class MyTest < Minitest::Test
def test_1
expected = <<~EOS
do_work_1 called with: [[], {}]
do_work_1 finished
This is a test
This is a test
do_work_3 called with: [[], {}, [\"derp.rb\", 10]]
do_work_3 finished
EOS
assert_output(expected) { Worker.new.my_method }
end
end
The question has been simplified trivially in the interest of brevity. Exact output format is left as an exercise to the reader.

Ruby metaprogramming to achieve dynamic methods?

Want to achieve the following code using metaprogramming.
#resource = {}
#voters = {}
#is_upvoted = {}
def resource(comment)
#resource[comment.id]
end
def voters(comment)
#voters[comment.id]
end
def is_upvoted(comment)
#is_upvoted[comment.id]
end
How can I create these methods using ruby metaprogramming and access the hash?
Can you tell me what is wrong in my code ?
['resource', 'voters', 'is_upvoted'].each do |attribute|
define_method("#{attribute}") do |comment|
instance_variable_set("##{attribute}", comment.id)
end
end
This bit seems redundant:
#resource = {}
#voters = {}
#is_upvoted = {}
Since you're already looping an array to do your metaprogramming.
You might try something like:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |comment|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
instance_variable_get("##{attr_sym}")[comment.id]
end
end
end
Which I believe will give you methods roughly like:
class Foo
def resource(comment)
#resource ||= {}
#resource[comment.id]
end
end
Personally, it seems not great to me to have comment.id in your method. Because what if someday you want to use a different attribute (or something else altogether) as the key?
So, I think I would do:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |key|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
instance_variable_get("##{attr_sym}")[key]
end
end
end
Now, it seems like you're going to want an easy way to set key-value pairs on your instance variable, so I guess I would try something like:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |key=nil|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
hsh = instance_variable_get("##{attr_sym}")
return hsh[key] if key
hsh
end
end
end
In which case you should be able to do (assuming you have a #comment variable that responds to id):
#comment.id
=> 1
foo = Foo.new
=> #<Foo:0x000056536d7504b0>
foo.resource
=> {}
foo.resource[#comment.id] = :bar
=> :bar
foo.resource
=> {1=>:bar}
foo.resource[#comment.id]
=> :bar
Can you tell me what is wrong in my code ?
It's doing the equivalent of this:
def resource(comment)
#resource = comment.id
end
instance_variable_get would be a better choice.
This is how I used it and it works
['resource', 'voters', 'is_upvoted'].each do |attribute|
define_method("#{attribute}") do |comment|
instance_variable_get("##{attribute}")[comment.id]
end
end

How to forward a block to a method that generates methods

In my current project, I am having the following repetitive pattern in some classes:
class MyClass
def method1(pars1, ...)
preamble
# implementation method1
rescue StandardError => e
recovery
end
def method2(pars2, ...)
preamble
# implementation method2
rescue StandardError => e
recovery
end
# more methods with the same pattern
end
So, I have been thinking about how to dry that repetive pattern. My goal is to have something like this:
class MyClass
define_operation :method1, pars1, ... do
# implementation method1
end
define_operation :method2, pars2, ... do
# implementation method2
end
# more methods with the same pattern but generated with define_wrapper_method
end
I have tried to implement a kind of metagenerator but I have had problems for forwarding the block that would receive the generator. This is more or less what I have tried:
def define_operation(op_name, *pars, &block)
define_method(op_name.to_s) do |*pars|
preamble
yield # how can I do here for getting the block? <-----
rescue StandardError => e
recovery
end
end
Unfortunately, I cannot find a way for forwarding block to the define_method method. Also, it is very possible that the parameters, whose number is variable, are being passed to define_method in a wrong way.
I would appreciate any clue, help, suggestion.
You do not need metaprogramming to achieve this. Just add a new method that wraps the common logic like below:
class MyClass
def method1(param1)
run_with_recovery(param1) do |param1|
# implementation method1
end
end
def method2(param1, param2)
run_with_recovery(param1, param2) do |param1, param2|
# implementation method2
end
end
private
def run_with_recovery(*params)
preamble
yield(*params)
rescue StandardError => e
recovery
end
end
Test it here: http://rubyfiddle.com/riddles/4b6e2
If you really wanted to do metaprogramming, this will work:
class MyClass
def self.define_operation(op_name)
define_method(op_name.to_s) do |*args|
begin
puts "preamble"
yield(args)
rescue StandardError => e
puts "recovery"
end
end
end
define_operation :method1 do |param1|
puts param1
end
define_operation :method2 do |param1, param2|
puts param1
puts param2
end
end
MyClass.new.method1("hi")
MyClass.new.method2("hi", "there")
Test this here: http://rubyfiddle.com/riddles/81b9d/2
If I understand correctly you are looking for something like:
class Operation
def self.op(name,&block)
define_method(name) do |*args|
op_wrap(block.arity,*args,&block)
end
end
def op_wrap(arity=0,*args)
if arity == args.size || (arrity < 0 && args.size >= arity.abs - 1)
begin
preamble
yield *args
rescue StandardError => e
recovery
end
else
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{arity < 0 ? (arity.abs - 1).to_s.+('+') : arity })"
end
end
def preamble
puts __method__
end
def recovery
puts __method__
end
end
So your usage would be
class MyClass < Operation
op :thing1 do |a,b,c|
puts "I got #{a},#{b},#{c}"
end
op :thing2 do |a,b|
raise StandardError
end
def thing3
thing1(1,2,3)
end
end
Additionally this offers you both options presented as you could still do
def thing4(m1,m2,m3)
#m1 = m1
op_wrap(1,'inside_wrapper') do |str|
# no need to yield because the m1,m2,m3 are in scope
# but you could yield other arguments
puts "#{str} in #{__method__}"
end
end
Allowing you to pre-process arguments and decide what to yield to the block
Examples
m = MyClass.new
m.thing1(4,5,6)
# preamble
# I got 4,5,6
#=> nil
m.thing2('A','B')
# preamble
# recovery
#=> nil
m.thing3
# preamble
# I got 1,2,3
#=> nil
m.thing1(12)
#=> #<ArgumentError: wrong number of arguments (given 1, expected 3)>

Create automatically methods

Lets say I have this House class:
class House
def self.building_steps
[
[Worker, :buy_material],
[Worker, :blend_material],
[Truck, :remove_ground],
......
#more Tasks defined by [CLASS,METHOD]
]
end
def self.buy_material
check_process(Worker, __method__)
end
def self.blend_material
check_process(Worker, __method__)
end
def self.remove_ground
check_process(Truck, __method__)
end
............
#More Methods that have same Method names like the building steps
end
As you can see in my code I have a lot of repetitions.
My question is how i can automatically define the class methods from the building_steps list.
So that I dont have to add the methods manually!
I search something like:
House.building_steps.each do |step|
define_house_method_with_name( step[1] )
in this method do
check_process(step[0], step[1])
end
end
Is something like this possible? Thanks!
You can do this using define_singleton_method:
class Worker; end
class Truck; end
class House
def self.building_steps
[
[Worker, :buy_material],
[Worker, :blend_material],
[Truck, :remove_ground]
]
end
def self.check_process(klass, method)
"executing #{method}"
end
building_steps.each do |klass, method|
define_singleton_method(method) do
check_process(klass, method)
end
end
end
puts House.buy_material #=> executing buy_material

Rails - how to enforce one method call per method, like controllers do with `render`

So in Rails, you get an error if you try to call render multiple times within a controller action.
I have another Ruby class that I'm writing, and I'd like to try to do something similar (make sure that my own respond_with method is only called once.
So for example, this would be fine:
def my_method
if (my_value == true)
...
respond_with(:a, :b, :c)
else
...
respond_with(:x, :y, :z)
end
end
But this would raise an error if my_value == 4
def my_method
if (my_value >= 4)
...
respond_with(:a, :b, :c)
end
if (my_value <= 4)
...
respond_with(:d, :e, :f)
else
...
respond_with(:x, :y, :z)
end
end
Any thoughts on how to best accomplish that?
class MyBaseClass
def respond_with(arguments)
if #rendered
raise DoubleRenderError #or whatever
end
#rendered = true
#whatever the respond_with function should do
end
end
Here's one way I could think of - in your Ruby class, define a #responded attribute by doing attr_accessor :responded. In your respond_with method, add the following lines:
def respond_with
raise DoubleRenderError if self.responded
# do stuff
self.responded = true
end
The above code should raise an error if you call respond_with twice on the same object.

Resources