Ruby named parameters with keywords - ruby

I'd love to use a method signature like:
def register(something, on:, for:)
This works, but I can't work out how to use "for" without causing a syntax error! Rather annoying, anyone know a way around this?

The problem is not the method definition line that you posted, the problem is the usage of the for variable inside the method body. Since for is a reserved word, you cannot use it as a plain variable name, but you can use it as part of a hash. In your case that means you must resort to using arbitrary keyword arguments (**opts), but you can use the keyword_argument for: in the method call. You may want to raise an ArgumentError if the key is not present to emulate the behavior of the method signature you posted above.
def register(something, on:, **opts)
raise ArgumentError, 'missing keyword: for' unless opts.has_key?(:for)
for_value = opts[:for]
puts "registering #{something} on #{on} for #{for_value}"
end
register 'chocolate chips', on: 'cookie'
# ArgumentError: missing keyword: for
register 'chocolate chips', on: 'cookie', for: 'cookie monster'
# registering chocolate chips on cookie for cookie monster

In Ruby, for is a reserved keyword - looks like you just cannot use them in other way to how they were meant to use.
That's the whole purpose of reserving keywords.
Additional resources on which keywords are reserved in Ruby:
http://www.rubymagic.org/posts/ruby-and-rails-reserved-words
http://www.java2s.com/Code/Ruby/Language-Basics/Rubysreservedwords.htm
(thanks #cremno for this comment) Both links don't mention ENCODING. Also, since 2.2.0 Ruby comes with an official document: $ ri ruby:keywords
UPD
Actualy, you can still use :for symbol as a key in hash (let's say, options hash), so, you can write like this:
def test(something, options = {})
puts something
puts options.values.join(' and ')
end
and it works like charm:
[4] pry(main)> test 'arguments', :for => :lulz, :with => :care, :while => 'you are writing code'
arguments
lulz and care and you are writing code

binding.local_variable_get(:for)
is a way I was thinking. Only works in ruby 2.1+ I think.
NOTE: Don't do this, I'm just interested in how you could get round it, you probably should just call your named parameter something else :)

Related

Ruby initialization idiom using symbols

I'm doing some work in an existing Ruby code base, and I'm quite new to Ruby. I see this initialization idiom pretty often:
def initialize(input)
#number = input[:number]
#color = input[:color]
end
I guess I have two questions. One is whether this is a common idiom and if so, what's good about it? The second is, what is actually happening here? Is the implication that input is an array? A hash? The underlying mechanics aren't clear to me.
Yes, it's common. Generally when you see code like this, it means that input is a Hash. Occasionally someone might pass in an object that acts like a hash, though, and we can still expect this code to work. We can tell that input is definitely not an array, because arrays require Integers to be used as their index, but :number and :color are Symbols.
Whenever you see something starting with a colon like :number, that is a Ruby symbol.
Whenever you see something starting with a # like #number, that is the name of an instance variable. Instance variables are used to store data inside objects for later use.
Suppose we have a class Foo defined like this:
class Foo
def initialize(input)
#number = input[:number]
#color = input[:color]
end
end
In this case, we can create a new object like this:
Foo.new(number: 4, color: 'red')
The code above is equivalent to:
input_hash = { :number => 4, :color => 'red' }
Foo.new(input_hash)
One nice thing about this pattern is that you can tell exactly what each input variable is being used for because it will be written next to a descriptive symbol, and also it doesn't matter what order you put the input variables in.
Improvements
If you want to improve this code, you might consider using a new feature of Ruby called keyword arguments. Alternatively, you might also consider Hash#fetch so you can have more control over what happens when one of the keys is missing, instead of just storing a nil value in your object. I would also recommend that you check the input hash for any unexpected keys and raise an exception if they are found.
Is this a common idiom and if so, what's good about it?
It's not common, but it's acceptable. When there are more than n parameters being passed in, where "n" is often > 3, we should use a hash.
What's good about it? Look at the code. Is it simple and readable? Does it make sense that an input parameter of :number is being assigned to an instance variable of the same name?
what is actually happening here? Is the implication that input is an array? A hash? The underlying mechanics aren't clear to me.
It's a hash where :number and :color are keys.

Why is splat argument in ruby not used all the time?

I know splat arguments are used when we do not know the number of arguments that would be passed. I wanted to know whether I should use splat all the time. Are there any risks in using the splat argument whenever I pass on arguments?
The splat is great when the method you are writing has a genuine need to have an arbitrary number of arguments, for a method such as Hash#values_at.
In general though, if a method actually requires a fixed number of arguments it's a lot clearer to have named arguments than to pass arrays around and having to remember which position serves which purpose. For example:
def File.rename(old_name, new_name)
...
end
is clearer than:
def File.rename(*names)
...
end
You'd have to read the documentation to know whether the old name was first or second. Inside the method, File.rename would need to implement error handling around whether you had passed the correct number of arguments. So unless you need the splat, "normal" arguments are usually clearer.
Keyword arguments (new in ruby 2.0) can be even clearer at point of usage, although their use in the standard library is not yet widespread.
For a method that would take an arbitrary amount of parameters, options hash is a de facto solution:
def foo(options = {})
# One way to do default values
defaults = { bar: 'baz' }
options = defaults.merge(options)
# Another way
options[:bar] ||= 'baz'
bar = options[bar]
do_stuff_with(bar)
end
A good use of splat is when you're working with an array and want to use just the first argument of the array and do something else with the rest of the array. It's much quicker as well than other methods. Here's a smart guy Jesse Farmer's use of it https://gist.github.com/jfarmer/d0f37717f6e7f6cebf72 and here is an example of some other ways I tried solving the spiraling array problem and some benchmarks to go with it. https://gist.github.com/TalkativeTree/6724065
The problem with it is that it's not easily digestible. If you've seen and used it before, great, but it could slow down other people's understanding of what the code is doing. Even your own if you haven't looked at it in a while hah.
Splat lets the argument be interpreted as an array, and you would need an extra step to take it out. Without splat, you do not need special things to do to access the argument:
def foo x
#x = x
end
but if you put it in an array using splat, you need extra step to take it out of the array:
def foo *x
#x = x.first # or x.pop, x.shift, etc.
end
There is no reason to introduce an extra step unless necessary.

Access Ruby hash variables

I am pretty new to ruby and sinatra but basically I have this route:
put '/user_list/:user_id' do
puts request.params["model"]
end
and it returns the following
{"password":"36494092d7d5682666ac04f62d624141","username":"nicholas","user_id":106,"firstname":"Nicholas","email":"nicholas#macpractice.com","is_admin":0,"lastname":"Rose","privileges":""}
I am now having a hard time accessing values of each of those. It doesn't really seem to be in hash format so I can't really do
request.params["model"][:password]
It just returns nil..
I just need to know what I can do to access those variables, or how to configure my request parameters to be in a good format to access variables.
Try request.params["model"]["password"]
A Hash's keys can consist of both symbols and strings. However, a string key is different than a symbol key.
Note the following:
h = {:name => 'Charles', "name" => 'Something else'}
h[:name] #=> 'Charles'
h["name"] #=> 'Something else'
EDIT:
In your particular situation, it appears request.params["model"] returns a string instead of a hash. There is a method String#[] which is a means of getting a substring.
s = "Winter is coming"
s["Winter"] #=> "Winter"
s["Summer"] #=> nil
This would explain your comments.
There are a couple things you can do to remedy your specific situation. I have found the most simplest way to be using JSON. (I'm sure there are others and maybe those will surface through other answers or through comments.)
require 'json'
hash_of_params = JSON.load(request.params["model"]).to_hash
hash_of_params["password"] #=> "36494092d7d5682666ac04f62d624141"
The standard Hash treats strings and symbols differently, and I'd be willing to bet that's what's happening in this case.
Use request.params["model"]["password"] to get the password.
The exception to that is when working with a HashWithIndifferentAccess which is part of ActiveSupport. For hashes of that type, either strings or symbols can be used to access the same elements.
Try the below,it will work too:
request.params["model"][:password.to_s]

Are "begin" and "end" reserved words or not?

I'm kind of confused about reserved words in Ruby.
"The Ruby Programming Language", co-authored by Matz, says that begin and end are reserved words of the language. They're certainly used syntactically to mark out blocks.
However, range objects in the language have methods named begin and end, as in
(1..10).end
=> 10
Now, testing this out, I find that, indeed, I can define methods named "begin" and "end" on objects, though if I try to name a variable "begin" it fails. (Here's a sample of using it as a method name, it actually works...:)
class Foo
def begin
puts "hi"
end
end
Foo.new.begin
So, I suppose I'm asking, what actually is the status of reserved words like this? I would have imagined that they couldn't be used for method names (and yet it seems to work) or that at the very least it would be terrible style (but it is actually used in the core language for the Range class).
I'm pretty confused as to when they're allowed to be used and for what. Is there even documentation on this?
Yes, they are reserved words. Yes, they can be used for method names. No, you can't call them without an explicit receiver. It's probably not a good idea anyway.
class Foo
def if(foo)
puts foo
end
end
Foo.new.if("foo") # outputs foo, returns nil
Update: Here's a quote from "The Ruby Programming Language", by Matz (the creator of Ruby) himself:
In most languages, these words would be called “reserved words” and
they would be never allowed as identifiers. The Ruby parser is
flexible and does not complain if you prefix these keywords with #,
##, or $ prefixes and use them as instance, class, or global variable
names. Also, you can use these keywords as method names, with the
caveat that the method must always be explicitly invoked through an
object.
When they are given in a form that is unambiguously a method call, you can use them. If you have a period in front of it .begin or have parentheses after is begin(), then it is unambiguously a method call. When you try to use it as a variable begin, it is ambiguous (in principle).
Actually, as Perry, notes, begin() might be tricky. I checked with irb with Ruby 1.9.3, and the following strange thing happens:
irb(main):001:0> def begin(foo)
irb(main):002:1> puts 'a'
irb(main):003:1> end
=> nil
irb(main):004:0> begin(3)
irb(main):005:1>
irb(main):006:1* end
=> 3
It is not defined, and what looks like a method call might be just a block returning the last-evaluated 3. But the lines around def begin(foo) remains mystery.

DSL block without argument in ruby

I'm writing a simple dsl in ruby. Few weeks ago I stumbled upon some blog post, which show how to transform code like:
some_method argument do |book|
book.some_method_on_book
book.some_other_method_on_book :with => argument
end
into cleaner code:
some_method argument do
some_method_on_book
some_other_method_on_book :with => argument
end
I can't remember how to do this and I'm not sure about downsides but cleaner syntax is tempting. Does anyone have a clue about this transformation?
def some_method argument, &blk
#...
book.instance_eval &blk
#...
end
UPDATE: However, that omits book but don't let you use the argument. To use it transparently you must transport it someway. I suggest to do it on book itself:
class Book
attr_accessor :argument
end
def some_method argument, &blk
#...
book.argument = argument
book.instance_eval &blk
#...
end
some_method 'argument' do
some_method_on_book
some_other_method_on_book argument
end
Take a look at this article http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation — there is an overview of the method (specifically stated in the context of its downsides and possible solution to them), plus there're several useful links for further reading.
Basically, it's about using instance_eval to execute the block in the desirable context.
Speaking about downside of this technique:
So what's the problem with it? Well, the problem is that blocks are
generally closures. And you expect them to actually be full closures.
And it's not obvious from the point where you write the block that
that block might not be a full closure. That's what happens when you
use instance_eval: you reset the self of that block into something
else - this means that the block is still a closure over all local
variables outside the block, but NOT for method calls. I don't even
know if constant lookup is changed or not.
Using instance_eval changes the rules for the language in a way that
is not obvious when reading a block. You need to think an extra step
to figure out exactly why a method call that you can lexically see
around the block can actually not be called from inside of the block.
Check out the docile gem. It takes care of all the sharp edges, making this very easy for you.

Resources