How to call local variable value in a method through a parameter? - ruby-on-rails

In my Rails app I have this (rather silly) method:
def my_method(param)
foo = "hey"
bar = "ho"
if param == :foo
return foo
elsif param == :bar
return bar
end
end
I don't like the if/else block, though.
Is there a simpler way to return the value of the local variable foo if :foo is provided as a parameter?
Or will I have to use an array or a hash here?

If you're using the very latest Ruby, you can use binding.local_variable_get(param). A hash seems cleaner to me, but your mileage may vary.

This should look simpler, don't think introducing a new data structure is required:
def my_method(param)
return 'hey' if param == :foo
return 'ho' if param == :bar
end

You can use a Hash:
def my_method(param)
objs = {
foo: "hey",
bar: "ho"
}
objs[param]
end

This is really a good time to use a case statement:
def my_method(param)
case param
when :foo
'hey'
when :bar
'ho'
else
# what do you want to do here?
end
end
Something to consider is, you're using an if/elseif, but what happens if neither of those hit? Do you want to return nil, or trap an error? As you look around in other people's code, you'll sometimes find long chains of if/elseif tests, with no final else, which opens up a potential logic error and can result in a hard-to-find bug.

Related

Ruby - Code someone explain the following code

I have the following piece of code in a library, Could someone explain what does the code ("#{k}=") means in the following piece of code?
if respond_to?("#{k}=")
public_send("#{k}=", v)
else
raise UnknownAttributeError.new(self, k)
end
I understand respond_to is a default function in Ruby but there is no definition/explanation given for this syntax, please help us.
Edited :
I get the exception (unknown attribute 'token' for PersonalAccessToken. (ActiveModel::UnknownAttributeError)) for the above code
/opt/gitlab/embedded/lib/ruby/gems/2.5.0/gems/activemodel-5.0.7.1/lib/active_model/attribute_assignment.rb:40:in `block in _assign_attributes'
/opt/gitlab/embedded/lib/ruby/gems/2.5.0/gems/activemodel-5.0.7.1/lib/active_model/attribute_assignment.rb:48:in `_assign_attribute': unknown attribute 'token' for PersonalAccessToken. (ActiveModel::UnknownAttributeError)
so considering k as 'token', In which scenario will I get the exception (In which scenario It goes to the else state?)
This code public_send("#{k}=", v) dynamically calls a setter for what is stored in k variable. Consider the following example:
class FooBarBaz
attr_accessor :foo, :bar, :baz
def set_it what, value
public_send("#{what}=", value)
end
end
It’s roughly an equivalent to:
def set_it what, value
case what
when "foo" then public_send("foo=", value)
when "bar" then public_send("bar=", value)
when "baz" then public_send("baz=", value)
end
end
It’s roughly an equivalent to:
def set_it what, value
case what
when "foo" then self.foo=(value)
...
end
end
It’s roughly an equivalent to:
def set_it what, value
case what
when "foo" then self.foo = value
...
end
end
respond_to? is called in advance to check if the setter in indeed defined for this k on this instance, to prevent somewhat like:
FooBarBaz.new.set_it :inexisting, 42
#⇒ NoMethodError: undefined method `inexisting=' for #<FooBarBaz:0x0056247695a538>
A modified, correct version of the class in this answer:
class FooBarBaz
attr_accessor :foo, :bar, :baz
def set_it what, value
public_send("#{what}=", value) if respond_to?("#{what}=")
end
end
It does not throw an exception.
FooBarBaz.new.set_it :inexisting, 42
#⇒ nil
"#{}" is string interpolation in ruby. so for example:
k = 'world'
puts "hello #{k}"
# hello world
So in your example it looks like it is creating a string with the value of k and =
e.g.
k = 'something'
"#{k}="
# something=
If you want to know what k is, you can add puts k.to_s in the line above then run the code and check your console.
Better still if you are using something like RubyMine, just use the debugger and stick a breakpoint on that line.
I think that you saw the _assign_attribute method in this docs
k is the abbreviation meaning of 'key', v is same thing meaning of 'value'
"#{k}=" is some kind of method name to dynamic method by string literal.
It may be "update" or "create", "split", anything else.
Equal symbol("=") is meaning of they are methods to assign something like this user.attributes = { :username => 'Phusion', :is_admin => true }
In the above case, "k" is .attributes and "k=" is attributes= and "v" is { :username => 'Phusion', :is_admin => true }
And public_send is method to send a method in public scope.
As a result public_send("#{k}=", v) is meaning of call a method having a name as "k" in public method, and this method will be assigned to "v" as the value.
I hope that this explain help to you.
Add some example for comment
k is a input from programmer, therefore it does not match method name in Class or Module.
In Reality, there are common mistake case.
class User
# this attribute can be called from a instance of User
attr_accessor :name
end
# this causes a error when call assign_attribute
User.new.wrong_name
# this is fine
User.new.name
assignment method reference

method.to_proc doesn't return from enclosed function

I was trying to DRY up a Rails controller by extracting a method that includes a guard clause to return prematurely from the controller method in the event of an error. I thought this may be possible using a to_proc, like this pure Ruby snippet:
def foo(string)
processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
My thinking was that having called to_proc on the breaker method, calling the early return statement in the rescue clause should escape the execution of foo. However, it didn't work:
2.4.0 :033 > foo('bar')
This step should not be executed in the event of an error
=> "BAR"
2.4.0 :034 > foo(2)
Well you messed that up, didn't you?
This step should not be executed in the event of an error
=> nil
Can anyone please
Explain why this doesn't work
Suggest a way of achieving this effect?
Thanks in advance.
EDIT: as people are wondering why the hell I would want to do this, the context is that I'm trying to DRY up the create and update methods in a Rails controller. (I'm trying to be agressive about it as both methods are about 60 LoC. Yuck.) Both methods feature a block like this:
some_var = nil
if (some complicated condition)
# do some stuff
some_var = computed_value
elsif (some marginally less complicated condition)
#error_msg = 'This message is the same in both actions.'
render partial: "show_user_the_error" and return
# rest of controller actions ...
Hence, I wanted to extract this as a block, including the premature return from the controller action. I thought this might be achievable using a Proc, and when that didn't work I wanted to understand why (which I now do thanks to Marek Lipa).
What about
def foo(string)
processed = breaker(string)
puts "This step should not be executed in the event of an error"
processed
rescue ArgumentError
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
raise ArgumentError.new("could not call upcase! on #{string.inspect}")
end
string
end
After all this is arguably a pretty good use case for an exception.
It seems part of the confusion is that a Proc or lambda for that matter are distinctly different than a closure (block).
Even if you could convert Method#to_proc to a standard Proc e.g. Proc.new this would simply result in a LocalJumpError because the return would be invalid in this context.
You can use next to break out of a standard Proc but the result would be identical to the lambda that you have now.
The reason Method#to_proc returns a lambda is because a lambda is far more representative of a method call than a standard Proc
For Example:
def foo(string)
string
end
bar = ->(string) { string } #lambda
baz = Proc.new {|string| string }
foo
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
bar.()
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
baz.()
#=> nil
Since you are converting a method to a proc object I am not sure why you would also want the behavior to change as this could cause ambiguity and confusion. Please note that for this reason you can not go in the other direction either e.g. lambda(&baz) does not result in a lambda either as metioned Here.
Now that we have explained all of this and why it shouldn't really be done, it is time to remember that nothing is impossible in ruby so this would technically work:
def foo(string)
# place assignment in the guard clause
# because the empty return will result in `nil` a falsey value
return unless processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
Example

How to check a key/method on a object which can be hash as well as another class

Lets say we have two classes:
class Test
end
class Test2
def a
end
end
and say we have a variable var which can point to an object of these class or a Hash, so:
var = Test.new
or
var = Test1.new
or
var = {"a" => 1}
Now we need to ensure that we call 'a' on 'var' only if its allowed (i.e. it should not be called in case it points to Test).
So, if I do:
var.a
it gives an error in case var points to Test object. Is there any way we can prevent the error? (Any other solution apart from doing .is_a? test ?)
You could use respond_to? to check if you can call the method on the object:
var.a if var.respond_to? :a
Strange questions lead to nasty answers. But at least it's working:
def a(test)
(test.respond_to?(:a) ? test.a : nil) ||
(test.respond_to?('has_key?') && test.has_key?('a') ? test['a'] : nil)
end
You can rescue from error, and return a default value, something like this
result = var.a rescue ""
Here blank string is used as default for illustration purposes, you can pick a reasonable default for your case

Ruby syntax: break out from 'each.. do..' block

I am developing a Ruby on Rails app. My question is more about Ruby syntax.
I have a model class with a class method self.check:
class Cars < ActiveRecord::Base
...
def self.check(name)
self.all.each do |car|
#if result is true, break out from the each block, and return the car how to...
result = SOME_CONDITION_MEET?(car) #not related with database
end
puts "outside the each block."
end
end
I would like to stop/break out from the each block once the result is true (that's break the each block if car.name is the same as the name parameter once) AND return the car which cause the true result. How to break out in Ruby code?
You can break with the break keyword. For example
[1,2,3].each do |i|
puts i
break
end
will output 1. Or if you want to directly return the value, use return.
Since you updated the question, here the code:
class Car < ActiveRecord::Base
# …
def self.check(name)
self.all.each do |car|
return car if some_condition_met?(car)
end
puts "outside the each block."
end
end
Though you can also use Array#detect or Array#any? for that purpose.
I provide a bad sample code. I am not directly find or check something
from database. I just need a way to break out from the "each" block if
some condition meets once and return that 'car' which cause the true
result.
Then what you need is:
def check(cars, car_name)
cars.detect { |car| car.name == car_name }
end
If you wanted just to know if there was any car with that name then you'd use Enumerable#any?. As a rule of thumb, use Enumerable#each only to do side effects, not perform logic.
you can use include? method.
def self.check(name)
cars.include? name
end
include? returns true if name is present in the cars array else it returns false.
You can use break but what your are trying to do could be done much easier, like this:
def self.check(name)
return false if self.find_by_name(name).nil?
return true
end
This uses the database. You are trying to use Ruby at a place the database can deal with it better.
You can also use break conditional:
break if (car.name == name)
I had to do this exact same thing and I was drawing a blank. So despite this being a very old question, here's my answer:
Note: This answer assumes you don't want to return the item as it exists within the array, but instead do some processing on the item and return the result of that instead. That's how I originally read the question, I realise now that was incorrect - though this approach can be easily modified for that effect (break item insead of break output)
Since returning from blocks is dodgy (nobody likes it, and I think the rules are about to change which makes it even more fraught) this is a much nicer option:
collection.inject(nil) do |_acc, item|
output = expensive_operation(item)
break output if output
end
Note that there are lots of variants; for example, if you don't want an incidental variable, and don't mind starting a second loop in some circumstances, you can invert it like this:
collection.inject(nil) do |acc, item|
break acc if acc
expensive_operation(item)
end

If string is empty then return some default value

Often I need to check if some value is blank and write that "No data present" like that:
#user.address.blank? ? "We don't know user's address" : #user.address
And when we have got about 20-30 fields that we need to process this way it becomes ugly.
What I've made is extended String class with or method
class String
def or(what)
self.strip.blank? ? what : self
end
end
#user.address.or("We don't know user's address")
Now it is looking better. But it is still raw and rough
How it would be better to solve my problem. Maybe it would be better to extend ActiveSupport class or use helper method or mixins or anything else. What ruby idealogy, your experience and best practices can tell to me.
ActiveSupport adds a presence method to all objects that returns its receiver if present? (the opposite of blank?), and nil otherwise.
Example:
host = config[:host].presence || 'localhost'
Phrogz sort of gave me the idea in PofMagicfingers comment, but what about overriding | instead?
class String
def |(what)
self.strip.blank? ? what : self
end
end
#user.address | "We don't know user's address"
Since you're doing this in Ruby on Rails, it looks like you're working with a model. If you wanted a reasonable default value everywhere in your app, you could (for example) override the address method for your User model.
I don't know ActiveRecord well enough to provide good code for this; in Sequel it would be something like:
class User < Sequel::Model
def address
if (val=self[:address]).empty?
"We don't know user's address"
else
val
end
end
end
...but for the example above this seems like you'd be mixing view logic into your model, which is not a good idea.
Your or method might have some unwanted side-effects, since the alternative (default) value is always evaluated, even if the string is not empty.
For example
#user.address.or User.make_a_long_and_painful_SQL_query_here
would make extra work even if address is not empty. Maybe you could update that a bit (sorry about confusing one-liner, trying to keep it short):
class String
def or what = ""
self.strip.empty? ? block_given? ? yield : what : self
end
end
#user.address.or "We don't know user's address"
#user.address.or { User.make_a_long_and_painful_SQL_query_here }
It is probably better to extend ActiveRecord or individual models instead of String.
In your view, you might prefer a more explicit pattern like
#user.attr_or_default :address, "We don't know the user's address"
Ruby:
unless my_str.empty? then my_str else 'default' end
RoR:
unless my_str.blank? then my_str else 'default' end
I recommend to use options.fetch(:myOption, defaultValue) because it works great with boolean flags like the ones mentioned above and therefore seems better to use in general.
Examples
value = {}
puts !!(value.fetch(:condition, true)) # Print true
value = {}
value[:condition] = false
puts !!(value.fetch(:condition, true)) # Print false
value = {}
value[:condition] = true
puts !!(value.fetch(:condition, true)) # Print true
value = {}
value[:condition] = nil
puts !!(value.fetch(:condition, true)) # Print false

Resources