I'm a newbie to Ruby and like others I had trouble wrapping my head around Ruby symbols. I know this subject has been brought up many times but I believe this post might slightly differ from the others. If not, my apologies. Take for instance this piece of code from the documentation.
module One
class Fred
end
$f1 = :Fred
end
module Two
Fred = 1
$f2 = :Fred
end
def Fred()
end
$f3 = :Fred
$f1.object_id #=> 2514190
$f2.object_id #=> 2514190
$f3.object_id #=> 2514190
My gripe is that it makes us think there is a link between the class, the module or the function and the :Fred symbol. No wonder people ask things like "can I assign a value to a symbol" or is the symbol a reference to another thing.
This code adds to the confusion :
class TestController < ApplicationController
layout :which_layout
def index
...
end
private
def which_layout
if condition
"layout1"
else
"layout2"
end
end
end
At first, I thought there was a reference to the function but in fact it's just that the behavior of the layout method will vary base on whether we'll pass a String (the template name) or a Symbol (call the method specified by the symbol) as stated by the documentation. (Does it look for a method.to_sym that is equivalent to the symbol we passed as argument?)
What I believe I've read however is that when creating a class, his symbol counterpart will be automatically created, that is :Fred will already exist in subsequent calls. But that's just it?
My question is : why did they have to include a class, a variable and a function to illustrate this? The context? Then why having the same name? Why not just do :
$f1 = :Fred
$f2 = :Fred
$f3 = :Fred
$f1.object_id #=> 2514190
$f2.object_id #=> 2514190
$f3.object_id #=> 2514190
When you use a symbol Ruby looks at list of existing symbols so when you reuse a symbol you are not creating a separate object in memory.
irb(main):006:0> :foo.object_id == :foo.object_id
=> true
You can contrast this with a string:
irb(main):007:0> "foo".object_id == "foo".object_id
=> false
This combined with the fact that they are so cheap to compare is what makes symbols so effective as hash keys.
What that pretty confusing example demonstrates is that symbols are not private to a scope - the table of symbols is global. It would be a bit less confusing if an instance variable was used instead of a global variable. I think it also attempts to demonstrate that module and class names are constants.
irb(main):016:0> Fred = Module.new do; end # this is the same as using the module keyword
irb(main):017:0> Fred.object_id != :Fred.object_id
=> true
Which means that Fred is a reference to the module while :Fred is a value (a symbol).
Symbols like strings are a value and thus cannot be a used as a reference. This is very much like true, false and nil which are singleton objects.
irb(main):008:0> true.class
=> TrueClass
irb(main):09:0> true.object_id == true.object_id
=> true
# you can even use the singletons as hash keys
irb(main):010:0> { true => 1, false => 2, nil => 3 }[true]
=> 1
The Rails example is not really that complicated. :which_layout is just an argument passed to the layout method. The layout method has a conditional which uses Object#send to dynamically call the :which_layout method if it exists. A string argument is instead used straight away to construct a glob.
My understanding is it's meant to illustrate the fact that all of the other "Fred's" do nothing to alter the fact that the symbol :Fred is unchanged in every context. Maybe changing the object_id list at the bottom of the example to something like this would make it more clear:
p One::Fred.object_id #=> 70222371662500
p Two::Fred.object_id #=> 3
p Fred().object_id #=> 8
p $f1.object_id #=> 2514190
p $f2.object_id #=> 2514190
p $f3.object_id #=> 2514190
I just started learning ruby on rails, and I'm wondering when I should use "=>" and when I should use "=" for assignment. I am seeing that you use "=>" for hash, for assigning values to symbols in migrations, but i'm not sure where to draw the line.
Thanks!
The => symbol is used solely for hashes. Ruby has a feature in which hashes can be passed as the last argument to a method call without including the surrounding braces. This provides something that resembles keyword arguments (though until Ruby 2.0, Ruby didn't have keyword arguments).
So when you see this:
t.integer :foo, :default => 5
What it really means is this:
t.integer(:foo, { :default => 5 })
The rest is just syntactic sugar designed to make it look nicer.
The = symbol, on the other hand, is the assignment operator you know and love from nearly any programming language.
I struggled with this for a while, but now prefer to use the new style for hashes wherever possible
t.integer :foo, default: 5
t.string :bar, default: 'Dave'
=> is not the same as assignment, but I can see why it is confusing. In a hash you create a key and a value as a pair. The key and value can be anything
{'key1' => 'some value', :symbol_key => 'other value'}
This is different to the assignment, which you can see clearly because if you want the above hash to remain available to your program, you either have to pass it to a method or assign it to a variable
myhash = {'key1' => 'some value', :symbol_key => 'other value'}
And only now can you retrieve stuff from your hash
puts myhash['key1']
So the => operator is actually used to construct hashes (or dictionary objects), assignment allows you to store values in the program.
What is happening quite commonly Rails (and therefore in migrations), is that the hash is being created and passed to the method call without you realising it. But the plumbing is still the same, it's still only a hash that is created.
In Ruby 1.9 you can now define hashes using a javascript-like syntax, so you might start seeing this as well.
myhash = {key1: 'some value', key2: 'other value'}
Factory Girl allows to do something like:
FactoryGirl define
factory :post do
content "some content"
styles "styles here"
team 1
end
end
However, if I try something inside the factory block like:
factory :post do
content "some content"
styles "styles here"
team 1
my_dictionary {'a' => 1, 'b' => 2}
end
The my_dictionary does not get interpreted as a dictionary type. I don't know how to make a dictionary as an attribute inside FactoryGirl. Can anyone help me ?
The issue you observe comes from a syntax ambiguity in Ruby. The language uses curly braces both for defining hashes (which you call dictionaries) as well as for blocks (e.g. when using each loops). As you now use the hash as the only parameter to the my_dictionary method, it is unclear to the parser whether that opening curly brace is to be interpreted as the start of a block or a hash. In this case, Ruby defaults to the block assumption.
To enforce the interpretation as a method parameter, you can use parenthesis like so:
my_dictionary({'a' => 1, 'b' => 2})
Then, the statement can be parsed without any ambiguity. What you have here is just one of the rare cases where you can't easily omit the parenthesis for method calls.
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]
I know these are the basics of rails but i still don't know the full difference between = sign and => and the difference between #some_variable, ##some_variable and :some_variable in rails.
Thanks.
OK.
The difference between the = and the => operators is that the first is assignment, the second represents an association in a hash (associative array). So { :key => 'val' } is saying "create an associative array, with :key being the key, and 'val' being the value". If you want to sound like a Rubyist, we call this the "hashrocket". (Believe it or not, this isn't the most strange operator in Ruby; we also have the <=>, or "spaceship operator".)
You may be confused because there is a bit of a shortcut you can use in methods, if the last parameter is a hash, you can omit the squiggly brackets ({}). so calling render :partial => 'foo' is basically calling the render method, passing in a hash with a single key/value pair. Because of this, you often see a hash as the last parameter to sort of have a poor man's optional parameters (you see something similar done in JavaScript too).
In Ruby, any normal word is a local variable. So foo inside a method is a variable scoped to the method level. Prefixing a variable with # means scope the variable to the instance. So #foo in a method is an instance level.
## means a class variable, meaning that ## variables are in scope of the class, and all instances.
: means symbol. A symbol in Ruby is a special kind of string that implies that it will be used as a key. If you are coming from C#/Java, they are similar in use to the key part of an enum. There are some other differences too, but basically any time you are going to treat a string as any sort of key, you use a symbol instead.
Wow, a that's a lot of different concepts together.
1) = is plain old assignment.
a = 4;
puts a
2) => is used to declare hashes
hash = {'a' => 1, 'b' => 2, 'c' => 3}
puts hash['b'] # prints 2
3) #var lets you access object instance variable.
class MyObject
def set_x(x)
#x = x
end
def get_x
#x
end
end
o = MyObject.new
o.set_x 3
puts o.get_x # prints 3
4) ##var lets you access class ('static') variables.
class MyObject
def set_x(x)
##x = x # now you can access '##x' from other MyObject instance
end
def get_x
##x
end
end
o1 = MyObject.new
o1.set_x 3
o2 = MyObject.new
puts o2.get_x # prints 3, even though 'set_x' was invoked on different object
5) I usually think of :var as special 'label' class. Example 2 can be rephrased like this
hash = {:a => 1, :b => 2, :c => 3}
puts hash[:b] # prints 2