How Are Symbols Used to Identify Arguments in Ruby Methods

How are symbols used to identify arguments in ruby methods

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.

Using Symbol as Parameter for the Function in Ruby

It's fairly common for people learning Ruby to not quite understand symbols. A symbol is a standard Ruby type, just like other built in Ruby types. Here is an example of how a symbol is just an object that is of a certain type:

'A'.class # => String
1.class # => Fixnum
:a.class # => Symbol

As with any other type, symbols have methods. If you open IRB and type :a.methods.sort it will show you all the methods you can call on a symbol. E.g., :a.to_s # => 'a'

As you've noticed, symbols are often used as Hash keys. However, other types can also be Hash keys:

my_hash = { 'A' => 'an A', 1 => 'a 1', :a => 'the symbol a'}
my_hash['A'] # => 'an A'
my_hash[1] # => 'a 1'
my_hash[:a] # => 'the symbol a'

Just like you can pass a String or other types to a method, you can pass a Symbol. In fact, in the last example, we are passing a String, a Fixnum, and then a Symbol to my_hash's [] method.

The reason people really like symbols for hash keys is that they are very lightweight to reuse. Here is an example showing one of the main differences between a symbol and any other object:

"a".object_id # => 70098399407740
"a".object_id # => 70098399393460
"a".object_id # => 70098399388140
:a.object_id # => 359368
:a.object_id # => 359368
:a.object_id # => 359368

As you can see, I create three strings that have the value "a", and they each have a different object id. In other words, there are three String objects in memory that contain the value "a". In contrast, every time I use :a it has the same object id. There is only one :a object in my entire program.

It's not uncommon to pass symbols to methods. You'll see this a lot in Rails.

Rails methods called as symbols when used as parameters?

If you look at the interface of Object#send in ruby 2.1 you will find the symbol variant first and the string variant second, but up to ruby 2.0 there used to be only the symbol variant! As all methods will be passed through #send it made sense to use its parameter wherever you referred to a method in order to avoid constant calls to #to_sym everywhere. I do not know why this change was introduced, but I guess the symbols will stick around for some time, at least they are one character less to type :-)

That goes without saying that symbols are allocated only once throughout a program, they will not be garbage collected and are therefore slightly more efficient than strings. As processors still get faster more quickly than RAM I guess the distinction has become less important lately.

As you can see in the discussion of new features of ruby 2.1 the difference between symbols and frozen strings is becoming less. Frozen strings are now only allocated once, symbols are now frozen and there is even a hint about symbols possibly being garbage collected in the future. This would eventually eliminate an attack vector for a denial of service attack if external data is turned into symbols. On the other hand, def and define_method now return the names of the methods they define as symbols (instead of returning nil as they used to).

In Ruby, what does the symbol = mean in a method's parameter list?

Yes, it has the same meaning. In both cases, it's definition of a hash. Ruby allows to omit curly braces of hash literal, if it is the last parameter in method's signature.

Example:

def my_method a, b, h
puts a
puts b
puts h
end

my_method(1, 2, :timeout => 30)
# my_method(1, 2, timeout: 30) # alternative syntax for ruby 1.9+
# >> 1
# >> 2
# >> {:timeout=>30}

Note that it only works for last parameter which is hash. If you have several hashes at the end, you have to use normal form (with curly braces) for all but the last.

Why do we use symbols as parameters?

It's common to use hashes passed as arguments to methods because then you don't have to worry about the ordering of arguments. Also, used quite frequently for optional arguments. Consider the examples below:

def method_with_args(name, age, dollar_amount, occupation) # gets sort of ugly and error prone
# do something with args
end

def method_with_hash(hash = {}) # cleans up ordering and forces you to 'name' variables
name = hash[:name]
age = hash[:age]
dollar_amount = hash[:dollar_amount]
occupation = hash[:occupation]
# do stuff with variables
end

The second part of your question:

@tweets = Tweet.recent.includes(location)

location here is expected to be defined as a variable or method on the object that calls the method. If you run the code, the error will prompt you with that information.

In terms of hash access and performance: symbol access to a hash is about 2x faster. String access allocates a string to memory every time it's instantiated which creates a lot of garbage objects. Take a gander at this blog post

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

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.

How method name is converted into a symbol in Ruby?

it's not clear for me, how Ruby converts name of method into :symbol?

That's the way Method#name works, it returns the name of the method as a symbol:

m = "foo".method(:size)     #=> #<Method: String#size>
m.name #=> :size
m.call #=> 3

All methods referencing other methods usually work this way. For example, Object#methods returns a array of method names:

"foo".methods
#=> [:<=>, :==, :===, :eql?, :hash, :casecmp, :+, :*, ...]

In method definition we give it name meth ... but if we want check, does any method exist, we give into method_defined symbol :meth

meth would be a reference to a variable or another method, whereas :meth is just a symbol:

meth = :foo
Mod.method_defined? meth #=> false, equivalent to Mod.method_defined? :foo
Mod.method_defined? :meth #=> true

Variable Arguments with a symbol

You can use a hash(a popular Ruby idiom) as a last argument, in which you can store values with lists, strings, anything:

def my_method(var1, options={})
options[:values] ||= []
options[:names] ||= []

#code
end

From there you can call:

my_method(whatever_arg, :values => ['1', '2', '3', '4'], :names => ['mike'])

Usage of symbols and table of symbols

Symbols by themselves are not connected with methods in any way. Certain methods can accept symbols to dynamically call other methods, using symbols as their names. Observe:

class Foo
def bar
"called bar"
end

def baz
"called bazzz"
end

def invoke m
send m
end
end

f = Foo.new
f.invoke :bar # => "called bar"
f.invoke :baz # => "called bazzz"

# since `invoke` just delegates execution to `send`, and `send` can accept strings as well
# we can pass a string here.
f.invoke 'baz' # => "called bazzz"

This is the same case as in

before_filter :init_session
validates_presence_of :first_name

And many others

Edit:

if I have a symbol :x and also defined a method x(), does it mean that these two are linked somehow?

No, they are not linked in any way. You can safely ignore that passage about symbol table. It's unnecessary implementation detail for you at the moment.



Related Topics



Leave a reply



Submit