I do not know Ruby very well and was hoping someone could help me understand some of what this bit of code is doing.
newproperty(:value, :array_matching => :all) do
desc 'The value of the setting to be defined.'
def insync?(is)
return true if #should.empty?
return false unless is.is_a? Array
return false unless is.length == #should.length
return (
is & #should == is or
is & #should.map(&:to_s) == is
)
end
I am not sure at all what this line ...
newproperty(:value, :array_matching => :all) do
... is doing. Is defining a function that accepts two parameters: value and array_matching? It is a loop? And I do not understand what the :array_matching => :all is all about.
Next is ...
desc 'The value of the setting to be defined.'
... Is this some sort of built in documentation? Next is this bit:
def insync?(is)
return true if #should.empty?
return false unless is.is_a? Array
return false unless is.length == #should.length
return (
is & #should == is or
is & #should.map(&:to_s) == is
)
I guess that a function called "insync" is being defined. Not sure what the '?' means. Also I guess #should is some global variable declared in the parent scope.
Thanks
I'll try to make assumptions and answer this as best I can from the info provided.
You probably have a function newproperty(x, y) <- accepts 2 arguments, somewhere in the associated model or helper. It's taking those inputs from some user interaction that assigns the :value and the :array_matching => :all is based on that :value.
desc is not a native Ruby function. It must be defined somewhere. For instance, this code would run:
def desc(x)
puts x
end
desc 'The value of the setting to be defined.'
It's a little unconventional but it would work.
The ? in def insync?(is) is part of the function name. Ruby is intended to be a very English-like language and since many functions evaluate to true or false, it's easier to read sometimes when you just make your function a question in itself.
Related
This question already has an answer here:
What is the difference between "be_true" and "be true" in RSpec
(1 answer)
Closed 8 years ago.
I know be false will just return true if the expected value is a pure false value, wherease be_falsey will return true if the expected value is either false or nil.
However, I fail to understand when would I use one or the other. I fail to see an example where be_falsey would be more useful than be false.
There are lots of situations in Ruby where you get a nil result and want to treat it like false. For example, suppose we have a method that handles an options hash, and we want the absence of an option to be the same as the option set to false:
def verbose?(opts)
opts[:verbose]
end
opts = { verbose: false }
expect(verbose?(opts)).to be_falsey # => PASS
opts = { verbose: true }
expect(verbose?(opts)).to be_falsey # => FAIL
opts = {}
expect(verbose?(opts)).to be_falsey # => PASS
Obviously this is a simplistic example (you could argue that verbose? should always return true or false), but similar scenarios are common in Ruby.
One example I can think of is for a unit test for a method. The method would be used in such a way that you are not really interested in the result, but only whether it is truthy/falsey.
Consider a method that checks if there is a record with a given value is in the db:
Class Foo
def self.has_value? value
Foo.find(value: value)
end
This method would be used as:
if Foo.has_value?(value)
...
Now, you could write a test like this:
let(:foo ){ create :foo )
expect(Foo.has_value?(1)).to == foo
But this obscures the intent of the method. The method's intent is not to find a foo. It is to tell you whether a foo with a given value exists or not.
To express the intent of the method it might be better to use
expect(Foo.has_value(1).to be_truthy
expect(Foo.has_value(2).to be_falsey
In Rails, if model.save is often used. This is the same pattern.
My method exists_else takes two parameters: base and fallback. If base is nil, it returns fallback. If it's not nil, it returns base. A call to exists_else(true, false) should return true.
If I use a standard looking if-statement, true is returned like I thought it would be:
def exists_else(base, fallback)
unless base.nil?
base
else
fallback
end
end
a = true
exists_else( a, false )
# => true
If I use the inline implementation shown below, it returns false.
def exists_else(base, fallback)
base unless base.nil? else fallback
end
a = true
exists_else( a, false )
# => false
Why does does it return false in the inline implementation?
Your assertion that
base unless base.nil? else fallback
is supposed to be equivalent to the long-form unless statement is not true; in fact, you cannot use else inside a post condition. Ruby is interpreting the code as:
def exists_else(base, fallback)
base unless base.nil?
else fallback
end
If you type this (or the version without the newline, as in your question) into IRB, Ruby gives the following warning:
warning: else without rescue is useless
That is to say, Ruby is trying to interpret the else as part of exception handling code, as in
def exists_else(base, fallback)
base unless base.nil
rescue ArgumentError => e
# code to handle ArgumentError here
else
# code to handle other exceptions here
end
You can not use an else statement when you're trying to do it in one line. When an else is necessary you must use the extended version.
Ruby thinks that the else in this case is related to error handling. You have to stick to your initial unless-end method.
I prefer this syntax to evaluate true/false checks:
condition(s) ? true_value : false_value
In your case, it would look like:
def exists_else(base, fallback)
base.nil? ? fallback : base
end
a = true
puts exists_else(a, false) # => true
Is there a simple way in Ruby to get a true/false value from something without explicitly evaluating it to true or false
e.g. how would one more succinctly express
class User
def completed_initialization?
initialization_completed == 1 ? true : false
end
end
is there some way to do something along the lines of
initialization_completed.true?
There's obviously not much in it but since I'm in the zen garden of Ruby I might as well embrace it
EDIT (I've updated the example)
This question was extremely badly phrased as was very gently pointed out by #Sergio Tulentsev. The original example (below) does of course evaluate directly to a boolean. I'm still struggling to find an example of what I mean however Sergio's double-negative was in fact exactly what I was looking for.
Original example
class User
def top_responder
responses.count > 10 ? true : false
end
end
> operator already returns boolean value. So it can be just
def top_responder
responses.count > 10
end
To convert arbitrary values to booleans, I offer you this little double-negation trick.
t = 'foo'
!!t # => true
t = 1
!!t # => true
t = 0
!!t # => true
t = nil
!!t # => false
The first negation "casts" value to boolean and inverts it. That is, it will return true for nil / false and false for everything else. We need another negation to make it produce "normal" values.
Can someone help me understand how to write this case statement properly its not working and as a NOOB I have no idea how to fix it:
def hide_link?(link, mailing)
case link
when 'edit' && ['sent', 'sending', 'archived'].include?(mailing.status)
return true
when 'send_schedule' && ['sent', 'sending', 'archived'].include?(mailing.status)
return true
when 'archive' && ['archived'].include?(mailing.status)
puts "I should be in here"
return true
else 'dashboard' && ['sending', 'draft'].include?(mailing.status)
return true
end
end
Basically I want to return true when the link matches certain criteria.
I believe that if link doesn't match these criterias the method should return false. Thus:
def hide_link?(link, mailing)
case link
when 'edit'
['sent', 'sending', 'archived'].include?(mailing.status)
when 'send_schedule'
['sent', 'sending', 'archived'].include?(mailing.status)
when 'archive'
puts "I should be in here"
['archived'].include?(mailing.status)
when 'dashboard'
['sending', 'draft'].include?(mailing.status)
else
false
end
end
The construction [...].include?(mailing.status) has result true or false which will be returned as a result of hide_link? method.
Remove return.
link = "fred"
case link
when "fred"
true
else
false
end
case will return the value itself which will then be passed to the method.
Refactor of megas's version:
def hide_link?(link, mailing)
statuses_to_hide = case link
when 'edit', 'send_schedule'
%w{sent sending archived}
when 'archive'
%w{archived}
when 'dashboard'
%w{sending draft}
else
[]
end
statuses_to_hide.include?(mailing.status)
end
The conditions in the case statement all follow the same form, which suggest that there is an opportunity to eliminate some repetition, and to separate policy from implementation. The policy is the set of conditions under which the link should be hidden:
WHEN_TO_HIDE_LINK = [
['edit', %w(sent sending archived)],
['send_schedule', %w(sent sending archived)],
['archive', %w(archived)],
['dashboard', %w(sending draft)],
]
The implementation is the code that applies the policy:
def hide_link?(link, mailing)
WHEN_TO_HIDE_LINK.any? do |link_value, mailing_statuses|
link_value == link && mailing_statuses.include?(mailing.status)
end
end
Explanations below the fold.
%w
%w is a way to specify a list of strings without typing all those quotes and commas. This:
%w(sent sending archived)
is equivalent to this:
['sent', 'sending', 'archived']
any?
Enumerable#any? passes each element of the array to the block (the bit between the do and the end). If the block ever returns truthy, then the result of any? is true; otherwise, the value of any? is false.
array decomposition
Did you notice that although each element of WHEN_TO_HIDE_LINK is an array, the block passed to any? does not take an array? You might expect that you'd have to do this:
WHEN_TO_HIDE_LINK.any? do |when_to_hide|
link_value = when_to_hide[0]
mailing_statuses = when_to_hide[1]
...
but Ruby will decompose array into parts for you. Here's one way to do it:
WHEN_TO_HIDE_LINK.any? do |when_to_hide|
link_value, mailing_statuses = when_to_hide
...
When there is an array on the right side of the = and comma-separated variables on the left, Ruby decomposes the array into its elements and assigns them to the variables separately.
But Ruby can make things even easier:
WHEN_TO_HIDE_LINK.any? do |link_value, mailing_statuses|
...
This is equivalent to either of the preceding two fragments.
Just wondering what !! is in Ruby.
Not not.
It's used to convert a value to a boolean:
!!nil #=> false
!!"abc" #=> true
!!false #=> false
It's usually not necessary to use though since the only false values to Ruby are nil and false, so it's usually best to let that convention stand.
Think of it as
!(!some_val)
One thing that is it used for legitimately is preventing a huge chunk of data from being returned. For example you probably don't want to return 3MB of image data in your has_image? method, or you may not want to return your entire user object in the logged_in? method. Using !! converts these objects to a simple true/false.
It returns true if the object on the right is not nil and not false, false if it is nil or false
def logged_in?
!!#current_user
end
! means negate boolean state, two !s is nothing special, other than a double negation.
!true == false
# => true
It is commonly used to force a method to return a boolean. It will detect any kind of truthiness, such as string, integers and what not, and turn it into a boolean.
!"wtf"
# => false
!!"wtf"
# => true
A more real use case:
def title
"I return a string."
end
def title_exists?
!!title
end
This is useful when you want to make sure that a boolean is returned. IMHO it's kind of pointless, though, seeing that both if 'some string' and if true is the exact same flow, but some people find it useful to explicitly return a boolean.
Note that this idiom exists in other programming languages as well. C didn't have an intrinsic bool type, so all booleans were typed as int instead, with canonical values of 0 or 1. Takes this example (parentheses added for clarity):
!(1234) == 0
!(0) == 1
!(!(1234)) == 1
The "not-not" syntax converts any non-zero integer to 1, the canonical boolean true value.
In general, though, I find it much better to put in a reasonable comparison than to use this uncommon idiom:
int x = 1234;
if (!!x); // wtf mate
if (x != 0); // obvious
It's useful if you need to do an exclusive or. Copying from Matt Van Horn's answer with slight modifications:
1 ^ true
TypeError: can't convert true into Integer
!!1 ^ !!true
=> false
I used it to ensure two variables were either both nil, or both not nil.
raise "Inconsistency" if !!a ^ !!b
It is "double-negative", but the practice is being discouraged. If you're using rubocop, you'll see it complain on such code with a Style/DoubleNegation violation.
The rationale states:
As this is both cryptic and usually redundant, it should be avoided
[then paraphrasing:] Change !!something to !something.nil?
Understanding how it works can be useful if you need to convert, say, an enumeration into a boolean. I have code that does exactly that, using the classy_enum gem:
class LinkStatus < ClassyEnum::Base
def !
return true
end
end
class LinkStatus::No < LinkStatus
end
class LinkStatus::Claimed < LinkStatus
def !
return false
end
end
class LinkStatus::Confirmed < LinkStatus
def !
return false
end
end
class LinkStatus::Denied < LinkStatus
end
Then in service code I have, for example:
raise Application::Error unless !!object.link_status # => raises exception for "No" and "Denied" states.
Effectively the bangbang operator has become what I might otherwise have written as a method called to_bool.
Other answers have discussed what !! does and whether it is good practice or not.
However, none of the answers give the "standard Ruby way" of casting a value into a boolean.
true & variable
TrueClass, the class of the Ruby value true, implements a method &, which is documented as follows:
Returns false if obj is nil or false, true otherwise.
Why use a dirty double-negation when the standard library has you covered?