I am generating some methods on the fly. The method body varies based on a certain criteria.
I was relying on class_eval to generate conditional code.
%Q{
def #{name}
#{
(name != "password") ? "attributes[:#{name}]" :
"encrypt(attributes[:#{name}])"
}
end
}
Recently I have started using define_method. How do I generate conditional code blocks while using define_method?
Edit 1
Here are the possible approaches that I have considered:
1) Checking the name on during run time:
define_method(name) do
if name == password
decrypt(attributes[name])
else
attributes[name]
end
end
This is not a preferred method as the check is done during run time.
2) Conditionally defining the entire method.
if (name == "password")
define_method(name) do
decrypt(attributes[name])
end
else
define_method(name) do
attributes[name]
end
end
This approach has the disadvantage of having to repeat the code block just change a small part (as my actual method has several lines of code).
I think because of closures you can do something like this:
define_method name do
if name=='password'
decrypt(attributes[name])
else
attributes[name]
end
end
But the issue there is that the if will be evaluated on each call to the method.
If you wanted to avoid that you'd need to pass different blocks to define_method for different behavior. e.g.
if name=='password'
define_method(name) { decrypt(attributes[name]) }
else
define_method(name) { attributes[name] }
end
alternately you could pass a lambda chosen by the if statement.
define_method(name, name=='password' ? lambda { decrypt(attributes[name]) } : lambda { attributes[name] }
One thing to think about, define_method can be slower than using eval.
Related
I have two methods.
def response_code_description(code)
#response_code_description ||= current_account.one_call_center.response_codes_repository_class.new.to_api_collection
#response_code_description.find {|k| k['code'] == code}.try(:[], 'description')
end
def ticket_response_code_with_description(ticket_response)
#ticket_response_code_with_description ||= ticket_response.ticket.one_call_center.response_codes_repository_class.new.to_api_collection
#ticket_response_code_with_description.find { |k| k['code'] == ticket_response.code }.try(:[], 'description')
end
I think I can combine them.
So.
def response_code_with_description(one_call_center, code)
#ticket_response_code_with_description ||= one_call_center.response_codes_repository_class.new.to_api_collection
#ticket_response_code_with_description.find { |k| k['code'] == code }.try(:[], 'description')
end
and call this method so
response_code_with_description(current_account.one_call_center, ticket_response.code)
response_code_with_description(ticket_response.ticket.one_call_center, code)
what do you think?
The primary difference between these two methods seems to be this one part:
k['code'] == code
k['code'] == ticket_response.code
So in other words you either compare to the argument directly, or the code method called on the argument. Address that problem by making the argument adaptive:
def to_description(code)
code = code.code if (code.respond_to?(:code))
# ... Rest of code presuming `code` is the thing to compare against.
end
This eliminates the difference between the two.
I'd strongly encourage you to revisit the names used in your code here s they are unreasonably verbose.
I have approx 11 functions that look like this:
def pending_acceptance(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
pending_acceptance?; collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
def pending_start(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
pending_start?; collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
The iteration is always the same, but next unless conditions are different. In case you wonder: it's next unless and ; in it because RuboCop was complaining about it. Is there a solution to implement it better? I hate this spaghetti code. Something like passing the condition into "iterate_it" function or so...
edit: Cannot just pass another parameter because the conditions are double sometimes:
def picked_up(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless
order_fulfillment.handed_over_late? && order_fulfillment.
fulfillment_time_calculator.pending_handover?
collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
edit2: One question yet: how could I slice a symbol, to get a user role from a status? Something like:
:deliverer_started => :deliverer or 'deliverer'?
You can pass another parameter when you use that parameter to decide what condition to check. Just store all possible conditions as lambdas in a hash:
FULFILLMENT_ACTIONS = {
pending_acceptance: lambda { |fulfillment| fulfillment.fulfillment_time_calculator.pending_acceptance? },
pending_start: lambda { |fulfillment| fulfillment.fulfillment_time_calculator.pending_acceptance? },
picked_up: lambda { |fulfillment| fulfillment.handed_over_late? && fulfillment.fulfillment_time_calculator.pending_handover? }
}
def process_fulfillments(type, order_fulfillments)
condition = FULFILLMENT_ACTIONS.fetch(type)
order_fulfillments.each do |order_fulfillment|
next unless condition.call(order_fulfillment)
collect_fulfillments(order_fulfillment.status, order_fulfillment)
end
end
To be called like:
process_fulfillments(:pending_acceptance, order_fulfillments)
process_fulfillments(:pending_start, order_fulfillments)
process_fulfillments(:picked_up, order_fulfillments)
you can make array of strings
arr = ['acceptance','start', ...]
in next step:
arr.each do |method|
define_method ( 'pending_#{method}'.to_sym ) do |order_fulfillments|
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
send('pending_#{method}?'); collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
end
for more information about define_method
While next is handy it comes late(r) in the code and is thus a bit more difficult to grasp. I would first select on the list, then do the action. (Note that this is only possible if your 'check' does not have side effects like in order_fullfillment.send_email_and_return_false_if_fails).
So if tests can be complex I would start the refactoring by expressing the selection criteria and then pulling out the processing of these items (wich also matches more the method names you have given), somewhere in the middle it might look like this:
def pending_acceptance(order_fulfillments)
order_fulfillments.select do |o|
o.fulfillment_time_calculator.pending_acceptance?
end
end
def picked_up(order_fulfillments)
order_fulfillments.select do |order_fulfillment|
order_fulfillment.handed_over_late? && order_fulfillment.
fulfillment_time_calculator.pending_handover?
end
end
def calling_code
# order_fulfillments = OrderFulFillments.get_from_somewhere
# Now, filter
collect_fulfillments(pending_start order_fulfillments)
collect_fulfillments(picked_up order_fulfillments)
end
def collect_fullfillments order_fulfillments
order_fulfillments.each {|of| collect_fullfillment(of) }
end
You'll still have 11 (+1) methods, but imho you express more what you are up to - and your colleagues will grok what happens fast, too. Given your example and question I think you should aim for a simple, expressive solution. If you are more "hardcore", use the more functional lambda approach given in the other solutions. Also, note that these approaches could be combined (by passing an iterator).
You could use something like method_missing.
At the bottom of your class, put something like this:
def order_fulfillment_check(method, order_fulfillment)
case method
when "picked_up" then return order_fulfillment.handed_over_late? && order_fulfillment.fulfillment_time_calculator.pending_handover?
...
... [more case statements] ...
...
else return order_fulfillment.fulfillment_time_calculator.send(method + "?")
end
end
def method_missing(method_name, args*, &block)
args[0].each do |order_fulfillment|
next unless order_fulfillment_check(method_name, order_fulfillment);
collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
Depending on your requirements, you could check if the method_name starts with "pending_".
Please note, this code is untested, but it should be somewhere along the line.
Also, as a sidenote, order_fulfillment.fulfillment_time_calculator.some_random_method is actually a violation of the law of demeter. You might want to adress this.
I'm trying to create a custom matcher for my tests in RoR using RSpec.
define :be_accessible do |attributes|
attributes = attributes.is_a?(Array) ? attributes : [attributes]
attributes.each do |attribute|
match do |response|
response.class.accessible_attributes.include?(attribute)
end
description { "#{attribute} should be accessible" }
failure_message_for_should { "#{attribute} should be accessible" }
failure_message_for_should_not { "#{attribute} should not be accessible" }
end
end
I want to be able to write something like the following in my tests:
...
should be_accessible(:name, :surname, :description)
...
but with the matcher defined above, I must pass an array of symbols instead of symbols separated by commas otherwise the test examines only the first symbol.
Any ideas?
I made it work this way :
RSpec::Matchers.define :be_accessible do |*attributes|
match do |response|
description { "#{attributes.inspect} be accessible" }
attributes.each do |attribute|
failure_message_for_should { "#{attribute} should be accessible" }
failure_message_for_should_not { "#{attribute} should not be accessible" }
break false unless response.class.accessible_attributes.include?(attribute)
end
end
end
I inverted the match and the each loop. I think this is the way Rspec expect it to be, as the block given to the match method is the one executed by Rspec abstract matcher (I guess).
By defining the block with |*attributes|, it takes the list of parameters and turn it into an Array.
So calling should be_accessible(:name, :surname, :description) will work.
By the way, if you just want to check for the existence of attributes, a simple
should respond_to(:name, :surname, :description)
works as well. But it does not looks like for mass-assignement aspect.
I have a class that I use to contain select menu options for property types. It works fine. However, I need to be able to verify the selection and perform specific logic based on the selected option. This needs to happen in my Ruby code and in JavaScript.
Here is the class in question:
class PropertyTypes
def self.[](id)
##types[id]
end
def self.options_for_select
##for_select
end
private
##types = {
1 => "Residential",
2 => "Commercial",
3 => "Land",
4 => "Multi-Family",
5 => "Retail",
6 => "Shopping Center",
7 => "Industrial",
8 => "Self Storage",
9 => "Office",
10 => "Hospitality"
}
##for_select = ##types.each_pair.map{|id, display_name| [display_name, id]}
end
What is the best way to verify the selection? I need to perform specific logic and display user interface elements based on each type of property type.
Since I am storing the id, I would be verifying that the id is a particular property type. Something like:
PropertyTypes.isResidential?(id)
Then this method would look like this:
def self.isResidential?(id)
##types[id] == "Residential"
end
But now I am duplicating the string "Residential".
For JavaScript, I assume I would make an ajax call back to the model to keep the verification code DRY, but this seems like over kill.
Do I need to manually create a verification method for each property type or can I use define_method?
This seems so basic yet I am confused and burned out on this problem.
Thanks
===
Here's my solution:
class << self
##types.values.each do |v|
# need to remove any spaces or hashes from the found property type
v = v.downcase().gsub(/\W+/, '')
define_method "is_#{v}?", do |i|
type_name = ##types[i]
return false if type_name == nil #in case a bogus index is passed in
type_name = type_name.downcase().gsub(/\W+/, '')
type_name == v
end
end
end
It sounds like you can benefit from some Ruby meta-programming. Try googling "ruby method_missing". You can probably do something quick & dirty along the lines of:
class PropertyTypes
def method_missing(meth, *args, &block)
if meth.to_s =~ /^is_(.+)\?$/
##types[args.first] == $1
else
super
end
end
end
On the ruby side you could also use something like this to define dynamically these methods:
class << self
##types.values.each do |v|
define_method "is_#{v}?", do |i|
##types[i] == v
end
end
end
i want to do
current_user.allow_????? = true
where ????? could be whatever I wanted it to be
I've seen it done before.. just don't remember where, or what the thing is called.
foo = "bar"
current_user.send("allow_#{foo}=", true)
EDIT:
what you're asking for in the comment is another thing. If you want to grab a constant, you should use for instance
role = "admin"
User.const_get(role)
That's a "magic method" and you implement the method_missing on your current_user object. Example from Design Patterns
#example method passed into computer builder class
builder.add_dvd_and_harddisk
#or
builder.add_turbo_and_dvd_dvd_and_harddisk
def method_missing(name, *args)
words = name.to_s.split("_")
return super(name, *args) unless words.shift == 'add'
words.each do |word|
#next is same as continue in for loop in C#
next if word == 'and'
#each of the following method calls are a part of the builder class
add_cd if word == 'cd'
add_dvd if word == 'dvd'
add_hard_disk(100000) if word == 'harddisk'
turbo if word == 'turbo'
end
end