Ruby passing symbol(s) as keyword argument(s)? - ruby

I have a method, where I want to use keyword parameter(s) and case/when statements, where I prefered to use symbols:
def navigate_to(page:)
case page
when :inbox
# some code
when :archive
# some code
else
# some code
end
end
Is this the correct syntax when calling the method?
navigate_to page: :inbox
The : : seems a little bit strange.

The keyword argument syntax is:
key: value
If key is page and value is the symbol :inbox, you indeed have:
page: :inbox
You could also use a positional argument instead:
def navigate_to(page)
# same as your code
end
navigate_to :inbox

Yes, the correct syntax is navigate_to page: :inbox.
While this is common Ruby, it is short and equivalent for several different things. First, the braces.
You are actually calling:
navigate_to(page: :inbox)
Second, the keyword argument pattern originates from hashes as arguments. Before there were keyword arguments, a common way would be to pass in a hash[1], like so:
def navigate_to(options)
page = options[:page]
end
navigate_to({ page: :inbox })
But when last argument in a method call is a hash, one can leave out the {}.
And last, the actual keys in the hash. A while ago (1.8 -> 1.9 IIRC) a short version was introduced for the following:
{ :page => 'Some Value' }, namely { page: 'Some Value' }. When Some Value is a symbol, that becomes { page: :inbox }.
So, taking all that:
navigate_to page: :inbox
Orignates from:
navigate_to({ :page => :inbox })
It might make more sense reading it like this, or knowing it comes from that.
And I know Ruby, nor ruby-ist, like braces, (), as can be seen in the mindboggling DSL of for example rspec, but I can advise especially new developers, to add them. It often makes code better understandable.
navigate_to(page: :inbox) is probably easier to understand than navigate_to page: :inbox, especially when you start calling through other methods: navigate_to page page_from_session :user.
[1] But, to stress, that is not really what is happening here. Keyword arguments and hash arguments do differ, now that we have keyword arguments. this just shows why the syntax is this way.

Related

How does [ ] work on a class in Ruby

I see that I can get a list of files in a directory using
Dir["*"]
How am I supposed to read that syntax exactly ? As I know that you can use [ ] to fetch a value from a array or a hash.
How does [ ] work on a call ?
[] is simply a method, like #to_s, #object_id. etc.
You can define it on any object:
class CoolClass
def [](v)
puts "hello #{v}"
end
end
CoolClass.new["John"] # => "hello John"
In your case it's defined as singleton method, in this way:
class Dir
def self.[](v)
...
end
end
From the Ruby Docs, Dir["*"] is equivalent to Dir.glob(["*"]). (As pointed out, it's syntactic sugar)
Dir isn't a call, it's a class, and objects of class Dir are directory streams, which you access like an array.
In your specific case, Dir["*"] will return an array of filenames that are found from the pattern passed as Dir[patternString]. "*" as a pattern will match zero or more characters, in other words, it will match everything, and thus will return an array of all of the filenames in that directory.
For your second question, you can just define it as any other method like so:
class YourClass
def self.[](v)
#your code here
end
end
The method Dir::glob takes an argument, and provides an array of all directories and files nested under the argument. (From there, you can grab the index of the array with [0].) The argument may include a pattern to match, along with flags. The argument (pattern, flags) may be options similar (but not exactly) regular expressions.
From the docs, including a couple of patterns/flags that may be of interest to you:
Note that this pattern is not a regexp, it's closer to a shell glob. See File.fnmatch for the meaning of the flags parameter. Note that case sensitivity depends on your system (so File::FNM_CASEFOLD is ignored), as does the order in which the results are returned.
* - Matches any file. Can be restricted by other values in the glob. Equivalent to / .* /x in regexp.
[set] - Matches any one character in set. Behaves exactly like character sets in Regexp, including set negation ([^a-z]).
The shorthand of Dir::glob() is Dir[], although I prefer the long form. As you saw above, using brackets denotes a special pattern/flag for the argument. Here are some examples (from the docs) that may better explain this:
Dir["config.?"] #=> ["config.h"]
Dir.glob("config.?") #=> ["config.h"]
Dir.glob("*.[a-z][a-z]") #=> ["main.rb"]
Dir.glob("*") #=> ["config.h", "main.rb"]
It is possible for you to redefine the [] method for Dir, but I will not show how -- many (and myself) do not recommend monkey-patching core Ruby classes and modules. However, you can create the method in a class of your own. See the following:
class User
# Class method => User.new[arg]
def self.[](arg)
end
# Instance method => #user[arg]
def [](arg)
end
end
Dir is an object just like any other object (it just happens to be an instance of class Class), and [] is a method just like any other method (it just happens to have a funny name, and special syntactic conveniences that allow it to called using a different syntax in addition to the normal one).
So, you define it just like any other method:
class MyClass
def self.[](*) end
end

javascript arguments parameter in ruby

I have a function with no parameters declared in its firm, but I need to obtain them if eventually any was passed to it.
For example, in javascript I can have a function as follows:
function plus() {
return operator("+", arguments);
}
As you can see, I can obtain the function arguments via "arguments" implicit parameter.
Does ruby have something similar to javascript argument parameter?
Thanks.
PS: I did a previous research in google, stackoverflow and this book with no result, maybe there is a workaround for this and no an official way to obtain it.
How about using variable length arguments:
def plus(*args)
# Do something with `args` array
end
In ruby you can always put optional arguments in a hash, such as
def some_function(args = {})
end
and you can call it like
some_function :arg1 => some_integer, :arg2 => "some_string"

ruby - omitting default parameter before splat

I have the following method signature
def invalidate_cache(suffix = '', *args)
# blah
end
I don't know if this is possible but I want to call invalidate_cache and omit the first argument sometimes, for example:
middleware.invalidate_cache("test:1", "test")
This will of course bind the first argument to suffix and the second argument to args.
I would like both arguments to be bound to args without calling like this:
middleware.invalidate_cache("", "test:1", "test")
Is there a way round this?
Use keyword arguments (this works in Ruby 2.0+):
def invalidate_cache(suffix: '', **args) # note the double asterisk
[suffix, args]
end
> invalidate_cache(foo: "any", bar: 4242)
=> ["", {:foo=>"any", :bar=>4242}]
> invalidate_cache(foo: "any", bar: 4242, suffix: "aaaaa")
=> ["aaaaa", {:foo=>"any", :bar=>4242}]
Note that you will have the varargs in a Hash instead of an Array and keys are limited to valid Symbols.
If you need to reference the arguments by position, create an Array from the Hash with Hash#values.
How about you create a wrapper method for invalidate_cache that just calls invalidate_cache with the standard argument for suffix.
In order to do this, your code has to have some way of telling the difference between a suffix, and just another occurrence of args. E.g. In your first example, how is your program supposed to know that you didn't mean for "test:1" to actually be the suffix?
If you can answer that question, you can write some code to make the method determine at run time whether or not you provided a suffix. For example, say you specify that all suffixes have to start with a period (and no other arguments will). Then you could do something like this:
def invalidate_cache(*args)
suffix = (args.first =~ /^\./) ? args.shift : ''
[suffix, args]
end
invalidate_cache("test:1", "test") #=> ["", ["test:1", "test"]]
invalidate_cache(".jpeg", "test:1", "test") #=> [".jpeg", ["test:1", "test"]]
If, however, there actually is no way of telling the difference between an argument meant as a suffix and one meant to be lumped in with args, then you're kind of stuck. You'll either have to keep passing suffix explicitly, change the method signature to use keyword arguments (as detailed in karatedog's answer), or take an options hash.

Strange ruby syntax

what the syntax is in Action Mailer Basics rails guide ?
class UserMailer < ActionMailer::Base
def welcome_email(user)
recipients user.email
from "My Awesome Site Notifications <notifications#example.com>"
subject "Welcome to My Awesome Site"
sent_on Time.now
body {:user => user, :url => "http://example.com/login"}
end
end
How should i understand the construction, like
from "Some text for this field"
Is it an assignment the value to a variable, called "from" ?
No, it's a method call. The name of the method is from, and the argument is a string. In Ruby, parentheses around method calls are optional, so
from "Some text for this field"
is the same as
from("Some text for this field")
Rails (and many Ruby libraries) like to express code in a natural language style, though, so the parentheses-less version reads better, hence why it is used in examples.
It is a call to a method from with the argument "Some text for this field"
The method comes from the ActionMailer::Base class that your UserMailer extends from.
In Ruby the parentheses around a method call are optional unless something would be ambiguous so the statement is equivalent to from("Some text for this field")
Rails has a coding style that prefers to be close to natural language where possible, hence not using parentheses unless necessary.
Calling this method sets an instance variable #from to the value you provide so that it can be used later when sending the message.
Normally when you have accessor methods for getting and setting a variable you would have from= to set the value and from to return the value, however ActionMailer uses something called adv_attr_accessor to define the from method so that if you call it with a parameter then it acts as a setter but if you call it with no parameters then it acts as a getter.
This can be seen in actionmailer-2.x.x/lib/action_mailer/base.rb and actionmailer-2.x.x/lib/action_mailer/adv_attr_accessor.rb
It's not an assignment. In Ruby, assignments are done using the assignment operator = like this:
var = val
You are probably thinking of some Lisp dialects where assignment looks like this:
(def var val)
It's just a simple receiverless message send.
In Ruby, the general syntax for a message send is
receiver.selector(argument1, argument2)
However, if the receiver is self, you can leave off the receiver, so
selector(argument1, argument2)
is the same as
self.selector(argument1, argument2)
[Note: this is not quite true. In Ruby, private methods can only be invoked via a receiverless message send, so if in this example self responds to the selector message by invoking a private method, only the first variant will work, the second will raise a NoMethodError exception.]
Also, in cases where there are no ambiguities, you can leave off the parentheses around the arguments like this:
receiver.selector argument1, argument2
If you put the two things together, you can now see that
selector argument1, argument2
is equivalent to
self.selector(argument1, argument2)
and thus
from "Some text for this field"
is equivalent to
self.from("Some text for this field")
There is a third shortcut in Ruby's message sending syntax: if the very last argument to a message send is a Hash literal, then you can leave out the curly braces. So, the last line in the above example could also be written as
body :user => user, :url => "http://example.com/login"
Also, in Ruby 1.9, a Hash literal where all keys are Symbols can be written using an alternative Hash literal syntax:
{ key1: val1, key2: val2 }
is the same as the old syntax
{ :key1 => val1, :key2 => val2 }
which means that, at least in Ruby 1.9, that last line could also be written as
body user: user, url: "http://example.com/login"
You could also call from an attribute. It's a property of the email, but how it's implemented is hidden from you (encapsulation). This is a Good Thing. It means that if Rails core decided it's better to change #from into several variables, you wouldn't need to change any of your code.

bracket syntax for Ruby Hashes

Do these two statements pass the same type of argument (a Hash) to the new method?
#seat = Seat.new(:flight_id => #flight.id)
#seat = Seat.new({:flight_id => #flight.id})
Do the Hash brackets {} change anything in the second example?
They are both the same, the {} add nothing in the second argument, apart from making things even more explicit than they already were (using the => syntax is enough to say 'this is a hash' to anyone using ruby for any length of time).
Ruby will automatically turn a list of parameters like:
someFunction(:arg1 => value1, :arg2 => value2)
into a hash and pass it as a single argument for you. The time when you need to add {} around hashes is when you have things like a hash of hashes or a function that expects two hashes (such as several rails methods when you need to pass both options and html_options), like this:
someFunction({:arg1 => value1, :arg2 => value2}, {:arg3 => value3})
which will pass in two hashes (the interpreter wouldn't be able to deduce where the 2 hashes were split if left to itself, so you need to give it the {} to tell it what to do in this case)
More information is available in the Pickaxe book chapter: More About Methods in the section on Collecting Hash Arguments at the bottom.
This seems like a good place to mention another alternate syntax, using the comma to separate items within braces (using your example):
#seat = Seat.new({:flight_id, #flight.id})
I don't normally use the comma syntax in standard code -- like workmad3 says, the arrow (=>) makes the hash more obvious. But in an interactive Ruby session (irb), it is easier to type a comma than the arrow:
{:eyes, "blue", :height, 6.2} # => {:height=>6.2, :eyes=>"blue"}
And in Ruby 1.9, the idiomatic version has even fewer commas:
{eyes: "blue", height: 6.2}

Resources