This question already has answers here:
What does map(&:name) mean in Ruby?
(17 answers)
Closed 8 years ago.
I'm reviewing someone's ruby code and in it they've written something similar to:
class Example
attr_reader :val
def initialize(val)
#val = val
end
end
def trigger
puts self.val
end
anArray = [Example.new(10), Example.new(21)]
anArray.each(&:trigger)
The :trigger means the symbol is taken and the & transforms it into a proc?
If that's correct, is there any way of passing variables into trigger apart from using self.?
This is related but never answered: http://www.ruby-forum.com/topic/198284#863450
is there any way of passing variables into trigger
No.
You're invoking Symbol#to_proc which does not allow you to specify any arguments. This is a convenient bit of sugar Ruby provides specifically for invoking a method with no arguments.
If you want arguments, you'll have to use the full block syntax:
anArray.each do |i|
i.trigger(arguments...)
end
Symbol#to_proc is a shortcut for calling methods without parameters. If you need to pass parameters, use full form.
[100, 200, 300].map(&:to_s) # => ["100", "200", "300"]
[100, 200, 300].map {|i| i.to_s(16) } # => ["64", "c8", "12c"]
This will do exactly what you need:
def trigger(ex)
puts ex.val
end
anArray = [Example.new(10), Example.new(21)]
anArray.each(&method(:trigger))
# 10
# 21
Related
This question already has an answer here:
naked asterisk as parameter in method definition: def f(*)
(1 answer)
Closed 4 years ago.
I am new in ruby and found this code:
def initialize(*)
# ...
end
What does * mean in initialize arguments list?
It mean's it's indiscriminate about arguments passed to it, i.e. you can pass as many or as few as you like.
They're 'throwaway' arguments, in that you can't access them subsequently. If you want to access them subsequently, you can use the commonly seen pattern def initialize(*args) and access the data via args.
It's often called with super to accept the arguments from a parent class, perhaps if they're not required.
For example, with your usage:
class MyClass
def initialize(*)
end
end
The following will work just fine:
MyClass.new(1, 2, 3, 4, 5, 'etc')
MyClass.new
It's largely undocumented, though is covered in the Ruby specs:
it "accepts an unnamed '*' argument" do
def foo(*); end;
foo.should == nil
foo(1, 2).should == nil
foo(1, 2, 3, 4, :a, :b, 'c', 'd').should == nil
end
This question already has answers here:
What does the (unary) * operator do in this Ruby code?
(3 answers)
Closed 7 years ago.
I see this *MyModel::MY_CONSTANT
and it references this in MyModel:
MY_CONSTANT = [
:a_field,
:another_field,
[
When called as
permit(:name, *MyModel::MY_CONSTANT)
it expands to
permit(:name, :a_field, :b_field)
but what just happened?
I hope that it actually expanded to permit(:name, :a_field, :another_field) :) But yeah, basically that's what it does when used on an array, it takes the values of the array and expands them out as if they were provided as individual arguments. So then you can take those array elements and send them into a method that's expecting individual arguments.
You can also use it in the inverse, you can define a method like:
def foo *args
end
...and when it's called with individual arguments:
foo 'a', 'b', 'c'
...those end up inside the foo method in the array args - this is very handy for wrapping another method, eg.
def some_method *args
do_something args.first
super *args # call the superclass's `some_method` with whatever args it would have received
end
Update
Btw, here's the official docs about this operator.
Is it possible to pass an arithmetic operator ( *, +, -, /) as a parameter to a ruby method? I have seen this performed in C++. Is rails capable of something similar?
def calculate(operator)
1254 operator 34
end
puts calculate(+)
Use Object#send:
def calculate(op)
1254.send(op, 34)
end
puts calculate(:+)
This works for any method, including the defined arithmetic operators. Note that you need to send the method name as a symbol or string.
You could use a block, and do something like
def calculate
yield 1254,34
end
calculate &:+ # => 1288
Operators desugar to messages. So you can pass a symbol that names the message you want to send.
def calculate(operator)
1254.send(operator, 34)
end
# to call:
puts calculate(:+)
Note, of course, that this will allow you to call any method, so if you're going to be accepting user input when you're doing this sort of thing, you'll usually want to whitelist.
You could achieve this with Object#send or Object#public_send methods:
def calculate(operator)
# specify accepted operators here:
raise ArgumentError unless [:*, :+, :-, :/].include? operator.to_sym
1254.public_send(op, 34)
end
I am learning rails and going back to ruby to understand how methods in rails (and ruby really work). When I see method calls like:
validates :first_name, :presence => true
I get confused. How do you write methods in ruby that accept symbols or hashes. The source code for the validates method is confusing too. Could someone please simplify this topic of using symbols as arguments in ruby class and instance methods for me?
UPDATE:
Good one #Dave! But What I was trying out was something like:
def full_name (:first_name, :last_name)
#first_name = :first_name
#last_name = :last_name
p "#{#first_name} #{last_name}"
end
full_name("Breta", "Von Sustern")
Which obviously raises errors. I am trying to understand: Why is passing symbols like this as arguments wrong if symbols are just like any other value?
Symbols and hashes are values like any other, and can be passed like any other value type.
Recall that ActiveRecord models accept a hash as an argument; it ends up being similar to this (it's not this simple, but it's the same idea in the end):
class User
attr_accessor :fname, :lname
def initialize(args)
#fname = args[:fname] if args[:fname]
#lname = args[:lname] if args[:lname]
end
end
u = User.new(:fname => 'Joe', :lname => 'Hacker')
This takes advantage of not having to put the hash in curly-brackets {} unless you need to disambiguate parameters (and there's a block parsing issue as well when you skip the parens).
Similarly:
class TestItOut
attr_accessor :field_name, :validations
def initialize(field_name, validations)
#field_name = field_name
#validations = validations
end
def show_validations
puts "Validating field '#{field_name}' with:"
validations.each do |type, args|
puts " validator '#{type}' with args '#{args}'"
end
end
end
t = TestItOut.new(:name, presence: true, length: { min: 2, max: 10 })
t.show_validations
This outputs:
Validating field 'name' with:
validator 'presence' with args 'true'
validator 'length' with args '{min: 2, max: 10}'
From there you can start to see how things like this work.
I thought I'd add an update for Ruby 2+ since this is the first result I found for 'symbols as arguments'.
Since Ruby 2.0.0 you can also use symbols when defining a method. When calling the method these symbols will then act almost the same as named optional parameters in other languages. See example below:
def variable_symbol_method(arg, arg_two: "two", arg_three: "three")
[arg, arg_two, arg_three]
end
result = variable_symbol_method :custom_symbol, arg_three: "Modified symbol arg"
# result is now equal to:
[:custom_symbol, "two", "Modified symbol arg"]
As shown in the example, we omit arg_two: when calling the method and in the method body we can still access it as variable arg_two. Also note that the variable arg_three is indeed altered by the function call.
In Ruby, if you call a method with a bunch of name => value pairs at the end of the argument list, these get automatically wrapped in a Hash and passed to your method as the last argument:
def foo(kwargs)
p kwargs
end
>> foo(:abc=>"def", 123=>456)
{:abc=>"def", 123=>456}
>> foo("cabbage")
"cabbage"
>> foo(:fluff)
:fluff
There's nothing "special" about how you write the method, it's how you call it. It would be perfectly legal to just pass a regular Hash object as the kwargs parameter. This syntactic shortcut is used to implement named parameters in an API.
A Ruby symbol is just a value as any other, so in your example, :first_name is just a regular positional argument. :presence is a symbol used as a Hash key – any type can be used as a Hash key, but symbols are a common choice because they're immutable values.
I think all replies have missed the point of question; and the fact it is asked by someone who is - I guess - not clear on what a symbol is ?
As a newcomer to Ruby I had similar confusions and to me an answer like following would have made more sense
Method Arguments are local variables populated by passed in values.
You cant use symbols as Arguments by themselves, as you cant change value of a symbol.
Symbols are not limited to hashes. They are identifiers, without the extra storage space of a string. It's just a way to say "this is ...."
A possible function definition for the validates call could be (just to simplify, I don't know off the top of my head what it really is):
def validates(column, options)
puts column.to_s
if options[:presence]
puts "Found a presence option"
end
end
Notice how the first symbol is a parameter all of its own, and the rest is the hash.
This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]