I have a list of methods who if any of them evaluated to true, I have to trigger an action on the model, in this case it's model audit.
So for example :
def a?
false # in reality this is some code
end
def b?
true # in reality this is some code
end
def c?
true # in reality this is some code
end
Now I can group this into like a parent method like :
def parent_method
a? || b? || c?
end
This will short-circuit the code, and c? will never be executed which is great. I can execute my .audit method.
But if I wanted to pass in custom message to my .audit method, and I wanted to have different message for every method how can I do that?
My first though to have a hash with keys being the methods and values being the messages or something along that line. But in that case the short circuiting doesn't work as all methods are evaluated in advance. How can I make this work better and more efficient/elegant?
Instead of true your method could return a trueish value like a symbol.
def a?
false # in reality this is some code
end
def b?
:b # in reality this is some code
end
def c?
:c # in reality this is some code
end
That you still allow to short-circuit the code in
def parent_method
a? || b? || c?
end
But now parent_method will not only return true or false but it would return a symbol that your allow returning a message that might be stored in a hash:
key = parent_method
audit(key) if key
You can always break these out into a simple array if you're not passing in any arguments. A minimal example looks like:
TESTS = [ :a?, :b?, :c? ]
def parent_method
failed = TESTS.find do |test|
!send(test)
end
if (failed)
# The failed variable contains the name of the method that failed
# to return `true`. Do whatever you need to do here.
errors << "Test #{failed} returned false!"
false
else
true
end
end
if I wanted to pass in custom message to my .audit method, and I wanted to have different message for every method how can I do that?
You could use a case expression:
case
when a? then audit(:a)
when b? then audit(:b)
when c? then audit(:c)
end
Related
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.
I'm working on a method that will allow me to add in a "word" and its "definition", into a hash.
Here's what I have:
class Dictionary
def entries
#entries ||= {}
end
def add word, definition = nil
entries[word] = definition
"#{entries}"
end
end
Note: I want the definition parameter to be optional, hence my initialization to nil. However, for some reason that is showing up in my output.
Example: Passing in "fish" and "aquatic animal":
My output: {{"fish"=>"aquatic animal"}=>nil}
Desired output: {"fish"=>"aquatic animal"}
It seems like the problem is that it's putting both values that I pass to the method into the first key in the hash, and is putting that "nil" value into that key's value. Where am I making an error?
Edit: Adding the relevant RSpec block that is doing the method call so that I can better understand exactly how RSpec is making this call:
describe Dictionary do
before do
#d = Dictionary.new
end
it 'is empty when created' do
#d.entries.should == {}
end
it 'can add whole entries with keyword and definition' do
#d.add('fish' => 'aquatic animal')
#d.entries.should == {'fish' => 'aquatic animal'}
#d.keywords.should == ['fish']
end
Thanks!
If you want to optionally accept a hash entry...
def add word, definition = nil
if word.class == Hash
entries.merge!(word)
else
entries[word] = definition
end
"#{entries}"
end
You don't want to do
#d.add('fish' => 'aquatic animal')
You want to do...
#d.add('fish', 'aquatic animal')
As it is, you're passing a hash as the first argument, second argument is empty.
Your RSpec is wrong.
Change #d.add('fish' => 'aquatic animal') to #d.add('fish', 'aquatic animal')
Your #add method is accepting 2 parameters, with one being optional. With your current code, you're passing in a single hash 'fish' => 'aquatic animal'. Therefor setting word to the hash, and def to nil.
My goal is to replace methods in the String class with other methods that do additional work (this is for a research project). This works for many methods by writing code in the String class similar to
alias_method :center_OLD, :center
def center(args*)
r = self.send(*([:center_OLD] + args))
#do some work here
#return something
end
For some methods, I need to handle a Proc as well, which is no problem. However, for the scan method, invoking it has the side effect of setting special global variables from the regular expression match. As documented, these variables are local to the thread and the method.
Unfortunately, some Rails code makes calls to scan which makes use of the $& variable. That variable gets set inside my version of the scan method, but because it's local, it doesn't make it back to the original caller which uses the variable.
Does anyone know a way to work around this? Please let me know if the problem needs clarification.
If it helps at all, all the uses I've seen so far of the $& variable are inside a Proc passed to the scan function, so I can get the binding for that Proc. However, the user doesn't seem to be able to change $& at all, so I don't know how that will help much.
Current Code
class String
alias_method :scan_OLD, :scan
def scan(*args, &b)
begin
sargs = [:scan_OLD] + args
if b.class == Proc
r = self.send(*sargs, &b)
else
r = self.send(*sargs)
end
r
rescue => error
puts error.backtrace.join("\n")
end
end
end
Of course I'll do more things before returning r, but this even is problematic -- so for simplicity we'll stick with this. As a test case, consider:
"hello world".scan(/l./) { |x| puts x }
This works fine both with and without my version of scan. With the "vanilla" String class this produces the same thing as
"hello world".scan(/l./) { puts $&; }
Namely, it prints "ll" and "ld" and returns "hello world". With the modified string class it prints two blank lines (since $& was nil) and then returns "hello world". I'll be happy if we can get that working!
You cannot set $&, because it is derived from $~, the last MatchData.
However, $~ can be set and that actually does what you want.
The trick is to set it in the block binding.
The code is inspired by the old Ruby implementation of Pathname.
(The new code is in C and does not need to care about Ruby frame-local variables)
class String
alias_method :scan_OLD, :scan
def scan(*args, &block)
sargs = [:scan_OLD] + args
if block
self.send(*sargs) do |*bargs|
Thread.current[:string_scan_matchdata] = $~
eval("$~ = Thread.current[:string_scan_matchdata]", block.binding)
yield(*bargs)
end
else
self.send(*sargs)
end
end
end
The saving of the thread-local (well, actually fiber-local) variable seems unnecessary since it is only used to pass the value and the thread never reads any other value than the last one set. It probably is there to restore the original value (most likely nil, because the variable did not exist).
One way to avoid thread-locals at all is to create a setter of $~ as a lambda (but it does create a lambda for each call):
self.send(*sargs) do |*bargs|
eval("lambda { |m| $~ = m }", block.binding).call($~)
yield(*bargs)
end
With any of these, your example works!
I wrote simple code simulating the problem:
"hello world".scan(/l./) { |x| puts x }
"hello world".scan(/l./) { puts $&; }
class String
alias_method :origin_scan, :scan
def scan *args, &b
args.unshift :origin_scan
#mutex ||= Mutex.new
begin
self.send *args do |a|
break if !block_given?
#mutex.synchronize do
p $&
case b.arity
when 0
b.call
when 1
b.call a
end
end
end
rescue => error
p error, error.backtrace.join("\n")
end
end
end
"hello world".scan(/l./) { |x| puts x }
"hello world".scan(/l./) { puts $& }
And found the following. The change of containment of the variable $& became inside a :call function, i.e. on 3-rd step before :call $& contains a valid value, but inside the block it becomes the invalid. I guess this become due to the singularity stack and variable restoration during the change process/thread context, because, probably, :call function can't access the :scan local state.
I see two variants: the first is to avoid to use global variables in the specific function redefinitions, and second, may to dig sources of ruby more deeply.
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
I am using Ruby on Rails 3 and I would like to use a case statement that even after matching a when statement can continue to checks other when statement until the last else.
For example
case var
when '1'
if var2 == ...
...
else
...
puts "Don't make nothig but continue to check!"
# Here I would like to continue to check if a 'when' statement will match 'var' until the 'else' case
end
when '2'
...
...
else
put "Yeee!"
end
Is it possible in Ruby? If so, how?
Most of the code I see coming from ruby is done with if elsif else but you can mimic switch logical expressions similar to other languages like this:
case var
when 1
dosomething
when 2..3
doSomethingElse
end
case
when var == 1
doSomething
when var < 12
doSomethingElse
end
This came from this SO Question. Like I said this is usually done with if elsif else in ruby such as:
if my_number == "1"
#do stuff when equals 1
elsif my_number == "e"
#same thing here
else
#default, no case found
end
Ruby doesn't have any sort of fall-through for case.
One alternative be a series of if statements using the === method, which is what case uses internally to compare the items.
has_matched? = false
if '2' === var
has_matched? = true
# stuff
end
if '3' === var
has_matched? = true
# other stuff
end
if something_else === var
has_matched? = true
# presumably some even more exciting stuff
end
if !has_matched?
# more stuff
end
This has two glaring issues.
It isn't very DRY: has_matched? = true is littered all over the place.
You always need to remember to place var on the right-hand-side of ===, because that's what case does behind the scenes.
You could create your own class with a matches? method that encapsulates this functionality. It could have a constructor that takes the value you'll be matching against (in this case, var), and it could have an else_do method that only executes its block if its internal #has_matched? instance variable is still false.
Edit:
The === method can mean anything you want it to mean. Generally, it's a more "forgiving" way to test equivalency between two objects. Here's an example from this this page:
class String
def ===(other_str)
self.strip[0, other_str.length].downcase == other_str.downcase
end
end
class Array
def ===(str)
self.any? {|elem| elem.include?(str)}
end
end
class Fixnum
def ===(str)
self == str.to_i
end
end
Essentially, when Ruby encounters case var, it will call === on the objects against which you are comparing var.