mute
mute

Reputation: 311

Confused about Ruby on Rails routes.rb syntax

Suppose I had the following code in my routes.rb file.

  get '/dashboard', to: 'dashboard#index', :as => 'dashboard'

I know what's going on (a public url being mapped to a specific controller/view file which will process and return the request). That is not what I am confused about.

As a Ruby beginner, I'm confused about what Ruby code convention this is. It doesn't look like the correct way to declare a variable or call a method? What exactly is going on and is this some sort of shorthand form to call a method?

get("/dashboard", "dashboard#index", "dashboard")

Upvotes: 2

Views: 302

Answers (3)

Jörg W Mittag
Jörg W Mittag

Reputation: 369438

get '/dashboard', to: 'dashboard#index', :as => 'dashboard'

This is just a normal message send (what you call a "method call", but note that messages and methods are different things in Ruby, and the metaphor of sending a message is really important).

In Ruby, you are allowed to leave out the parentheses around an argument list. So, this is the same as this:

get('/dashboard', to: 'dashboard#index', :as => 'dashboard')

You are also allowed to leave out the receiver of the message send, in which case it is implied to be self, so this is the same as this:

self.get('/dashboard', to: 'dashboard#index', :as => 'dashboard')

If the last argument to a message send is a Hash literal, you are allowed to leave out the curly braces, so this is the same as this:

self.get('/dashboard', { to: 'dashboard#index', :as => 'dashboard' })

If the key in a Hash literal is a Symbol, you are allowed to write key: value instead of :key => value, so this is the same as this:

self.get('/dashboard', { :to => 'dashboard#index', :as => 'dashboard' })

Now, doesn't that look just like a boring old message send?

Most of these rules are there to allow you to write code that looks more natural. For example, things that would be keywords in other languages, are just methods in Ruby, like attr_reader, attr_writer, attr_accessor, require, private, public, protected, etc. It would look weird, if you were required to use parentheses and receivers there:

self.protected()
def foo() end
# instead of
protected
def foo() end

# or

self.private(def foo() end)
# instead of
private def foo() end

The rule that you can leave out the curly braces around a trailing Hash literal was introduced, so that you could "fake" keyword arguments:

def route(path, options = {}) end
route(:index, :from => :here, :to => :there)

Even more so with the new-style symbol keys:

route(:index, from: :here, to: :there)

Note that as of Ruby 2.0, Ruby also has proper keyword arguments, which for backwards-compatibility look exactly like a trailing Hash:

def route(path, from:, to:, with: nil) end
route(:index, from: :here, to: :there) # this line same as above

Upvotes: 2

jvillian
jvillian

Reputation: 20263

You're very close.

Yes, get is a method that is being called. In ruby, you can omit the parentheses. So,

get '/dashboard', to: 'dashboard#index', :as => 'dashboard'

Is the same as:

get("/dashboard", to: "dashboard#index", :as => "dashboard")

Where to: and :as are named parameters which are received by get as a hash.

I think this method is defined in action_dispatch/routing/mapper.rb:

module ActionDispatch
  module Routing
    class Mapper

      module HttpHelpers
        # Define a route that only recognizes HTTP GET.
        # For supported arguments, see match[rdoc-ref:Base#match]
        #
        #   get 'bacon', to: 'food#bacon'
        def get(*args, &block)
          map_method(:get, args, &block)
        end
        ...
      end

    end
  end
end

So, you can see that to: "dashboard#index", :as => "dashboard" are received by *args and the splat operator allows for a variable length set of arguments.

Upvotes: 2

MrYoshiji
MrYoshiji

Reputation: 54882

This is a common way to allow options to be passed to a method. Like the following:

def do_something(subject, options = {})
  subject.perform_something
  subject.perform_caching if options[:cache]
  subject.send_report unless options[:skip_reporting]
  # etc.
end

So you can call:

something(some_object)
something(some_object, cache: true)
something(some_object, skip_reporting: true, cache: true)

You can even set up defaults, like this:

def do_something_else(subject, options = {})
  options = { cache: true, skip_reporting: false }.merge(options)
  subject.perform_something
  subject.perform_caching if options[:cache]
  subject.send_report unless options[:skip_reporting]
end

So that by default if no cache options if given, it will be true.

Upvotes: 3

Related Questions