lost9123193
lost9123193

Reputation: 11040

Variable Syntax in Ruby on Rails

I'm going over a ruby on rails tutorial and I'm still a bit confused with the syntax.

class PostController < ApplicationController
    def index
        @posts = Post.all
    end

    def create
        @post = Post.new(post_params)
    end 

private
    def post_params
        params.require(:post).permit(:title, :body)
    end
end

I'm coming from a javascript background.

From what I understand @posts with the @ is variable that instantiates from the Post class in the models folder. But what exactly does :post with the : mean and where does params come from and what is it?

Upvotes: 3

Views: 592

Answers (4)

J&#246;rg W Mittag
J&#246;rg W Mittag

Reputation: 369614

First off, and very importantly, there is no such thing as "variable syntax in Ruby on Rails".

Ruby on Rails is simply a framework written in Ruby. Ruby does not allow user code to change the fundamental language syntax. So, the variable syntax in Ruby on Rails is exactly the same as the variable syntax in Sinatra or Padrino or whatever, it is simply the variable syntax in Ruby. This is identical to ECMAScript, where there is also no "variable syntax in Express" or "variable syntax in JQuery".

It is, in fact, how pretty much all languages work; languages where user code is allowed to change the syntax, let alone change the syntax of something as fundamental as variables are a tiny minority way outside the mainstream.

From what I understand @posts with the @ is variable that instantiates from the Post class in the models folder.

I must admit I have trouble parsing what you mean by that. Ruby has no concept of "folder", so that is completely irrelevant. And I don't understand what you mean by "instantiates from". But I will try to attempt an answer anyway:

@posts is an instance variable. The closest analog to that in ECMAScript would be a private instance field. Instance variables belong to instances (duh) aka objects as opposed to the other kind of variables that you most often see: local variables belong to the lexical scope.

Note that this is unlike a private field in e.g. Java, where other objects of the same type are allowed to access private fields. Ruby has true object-oriented encapsulation, where only the object itself is allowed to access its instance variables. (I believe that the ECMAScript proposal has the same semantics.)

But what exactly does :post with the : mean and

This is a Symbol literal. ECMAScript also has Symbols, but unlike Ruby, it doesn't have literal syntax for them.

Ruby inherited Symbols from its ancestors Lisp and Smalltalk. Incidentally, those are also ECMAScript's ancestors (Lisp via Scheme and Smalltalk via NewtonScript → Act-1 → Self), so it comes at no surprise that there are similarities between the two.

Just like in ECMAScript, Smalltalk, and Lisp, a Symbol in Ruby is a datatype that denotes the concept of a "label" or a "name". So, whenever you need to "name" something, you use a Symbol. For example, when you define a method, the method definition evaluates to a Symbol:

def foo; end
#=> :foo

When you ask Ruby for a list of an object's methods, it will return an Array of Symbols:

''.methods
#=> [:unpack1, :encode!, :include?, :%, :*, :+, :count, :partition, :sum, :next, :casecmp, :casecmp?, :insert, :<=>, :bytesize, :match?, :succ!, :match, :==, :===, :next!, :=~, :index, :[], :[]=, :getbyte, :rindex, :replace, :upto, :chr, :scrub, :empty?, :eql?, :undump, :scrub!, :setbyte, :byteslice, :clear, :+@, :-@, :capitalize, :upcase, :downcase, :downcase!, :dump, :upcase!, :split, :capitalize!, :swapcase!, :freeze, :inspect, :grapheme_clusters, :lines, :swapcase, :oct, :codepoints, :crypt, :bytes, :hex, :concat, :ljust, :length, :size, :chars, :succ, :scan, :reverse, :reverse!, :chop, :<<, :strip, :end_with?, :lstrip, :prepend, :rjust, :to_str, :to_sym, :intern, :delete_prefix, :chomp, :sub!, :to_s, :to_i, :to_f, :delete_suffix, :lstrip!, :gsub!, :chop!, :center, :sub, :ord, :start_with?, :delete_prefix!, :delete_suffix!, :chomp!, :rstrip, :delete, :rstrip!, :gsub, :tr_s!, :tr, :tr_s, :strip!, :squeeze, :tr!, :each_codepoint, :delete!, :squeeze!, :each_line, :each_byte, :each_char, :force_encoding, :each_grapheme_cluster, :hash, :slice!, :rpartition, :encoding, :unpack, :b, :valid_encoding?, :slice, :unicode_normalize, :unicode_normalize!, :unicode_normalized?, :ascii_only?, :to_c, :to_r, :encode, :clamp, :<=, :between?, :>=, :>, :<, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :methods, :untrusted?, :trust, :singleton_methods, :tainted?, :private_methods, :untrust, :frozen?, :method, :public_send, :public_method, :singleton_method, :protected_methods, :define_singleton_method, :extend, :to_enum, :enum_for, :!~, :respond_to?, :object_id, :send, :display, :singleton_class, :nil?, :class, :yield_self, :clone, :dup, :itself, :untaint, :then, :taint, :!, :equal?, :!=, :instance_eval, :instance_exec, :__id__, :__send__]

When you ask Ruby to create an alias between two methods, you will give it the name of the existing method and the alias as Symbols:

alias_method :foo, :bar

And so on.

There are two differences between Ruby's and ECMAScript's Symbols, though.

The first one is a syntactic difference, and I alluded to it above: Ruby has literal syntax for Symbols (:foo), ECMAScript does not.

The second is an important semantic difference. (You only asked about syntax, but I believe this difference is important for someone with an ECMAScript background.) In Ruby, Symbols are always interned, which means that when you refer to a Symbol by name, it is always the same Symbol. In other words:

:foo.equal?(:foo)
#=> true

'foo'.to_sym.equal?('foo'.to_sym)
#=> true

:foo.equal?('foo'.to_sym)
#=> true

And so on.

Whereas in ECMAScript, the exact opposite is true: two Symbols are never identical, even if they have the same name:

Symbol('foo') === Symbol('foo')
//=> false

This allows Symbols in ECMAScript to be used as unforgeable security tokens. For example, if I want to make sure that only someone who I trust can call a particular method, then I can store the method in a property whose name is a Symbol:

const foo = {
    [Symbol('bar')]: () => console.log('Hello')
}

And nobody will be able to call bar, because it is impossible to construct a Symbol which matches the property key. Only when I give someone the exact Symbol I used to store the method, can they access it. (In fact, not even I can call this method, because I forgot to store the Symbol somewhere, and now not even I can construct it again!)

[Note: to make this really work, I would also have to make the property non-enumerable, otherwise I could still discover it by iterating over the object.]

This cannot be done with Ruby Symbols.

ECMAScript has a Global Symbol Registry, and I can store and retrieve Symbols from it using Symbol.for. Ruby's Symbols behave like always using Symbol.for in ECMAScript.

where does params come from and what is it?

If you were asking this question without the context you have posted, the answer would be: it could either be a local variable or a message send (in ECMAScript it would be called a "method call") with implicit receiver and no argument list. Syntactically, it is impossible in Ruby to distinguish between dereferencing a local variable and sending a message with an implicit receiver and no argument list. In ECMAScript, this ambiguity does not exist, since a method call always needs an argument list, even if it is empty.

In order to know whether it is a message or a local variable, we have to consider the context: a local variable is created when the first assignment to the variable is parsed. (Note, this is important: it is created when the assignment is parsed, not when it is evaluated. E.g. if false then foo = 42 end will create the local variable foo.)

In your code, we can clearly see that there is no assignment to params in the local scope before (in fact, params is the first token in the scope, so there is no "before"), which means it cannot be a local variable, it must be a message send.

It is therefore equivalent to

this.params().require(Symbol.for('post')).permit(Symbol.for('title'), Symbol.for('body'))

Upvotes: 4

3limin4t0r
3limin4t0r

Reputation: 21160

@post is an instance variable. Instance variables are variables that live as long as an instance of a class lives. A normal variable has a scope that is a lot more narrow, let me give you an example:

class Counter
  def initialize(start = 0)
    @counter = start
  end

  def increment
    @counter += 1
  end
end

counter = Counter.new
counter.increment #=> 1
counter.increment #=> 2

If you where to replace @counter with counter you'll get:

class Counter
  def initialize(start = 0)
    counter = start
  end

  def increment
    counter += 1
  end
end

counter = Counter.new
counter.increment # NoMethodError (undefined method `+' for nil:NilClass)

The difference is that counter is set in the constructor, but is only available in that method. So when calling increment the counter used is a variable that has not been initialized.

You can read more about instance variable here.


Now for the second part of the question "what exactly does :post with the : mean ". Ruby has strings which you should know from JavaScript. Ruby also has symbols, they are similar to a string but are global singleton objects. They have a faster lookup and compare time then strings, but once a symbol is used it stays loaded in the symbol register (Symbol.all_symbols) for the entirety of your program duration. They are mostly used as options for methods and labels for hashes (dicts in other languages).

To show you what I mean with singleton object let me provide an example:

:foo.object_id #=> 1686748
:foo.object_id #=> 1686748

Like you can see, the object stays the same even though we've seemingly created two instances. The first time :foo is used it is created and added to the register, the second time Ruby detects that :foo is already in the register and returns that symbol.

To show you the difference let met demonstrate the same, but with strings:

'foo'.object_id #=> 24742300
'foo'.object_id #=> 26029360

Like you can see, both strings have a different object id (although containing the same content) which makes them different objects.

You can read more about symbols here.


No for the last part of your question "where does params come from and what is it?", we'll have to look at the Ruby on Rails framework. Your PostController inherits from ApplicationController which in turn inherits from ActionController::Base which provides you with some helper methods. params is one of those methods.

In Ruby parentheses are optional when calling methods, and are mostly unused when calling a method without arguments. This is what is happening here, by running the code:

params.require(:post).permit(:title, :body)

You'll first call the params method. This method is inherited from ActionController::Base and returns the parameters of the web request. You then call require on the returned parameters, which fetches the value under the symbol/label :post, if it isn't present an exception ActionController::ParameterMissing is raised (this is part of the behaviour of require). You then call permit on the returned value with the arguments :title and :body, this permit those labels/symbols with their values.

To conclude things the following line ties everything together:

@post = Post.new(post_params)

Says, assign a new instance of post to the instance variable @post. You'll pass the return value of post_params method to the constructor. In this case :title and :body are whitelisted and will be used during initialisation.


One last thing to know is that Rails does some behind the scene stuff to make the instance variables in the controller carry over to the view. So when you assign @post a value, you will be able to access it in the view you're rendering.

Upvotes: 2

tadman
tadman

Reputation: 211740

There's two things here. One is instance variables, or @-prefixed variables. These are local to the instance of the object. That is @posts is accessible to any method called on the same object instance. These are not shared between object instances.

If you're familiar with JavaScript this is a lot like this.posts where the this part means "local to this object" instead of "some variable named posts".

Rails uses these as a way of sharing data from the controller to the view. Any instance variables you define are propagated to the view instance where they can be used. This is a behaviour fairly unique to Rails.

The :post thing is a Symbol, or an internalized string. A symbol is, conceptually, just a constant that represents that particular name. Every instance of :post refers to the same object so this means that they're very inexpensive in terms of storage and comparison is trivial.

When comparing two strings a lot more work is involved. "post" == "post" requires doing a lot more computation than :post == :post. You'll see symbol keys used in Ruby hash structures a lot, and they're often used to represent things, like method calls or class names, as in belongs_to :model_type and so on.

Think of them as "singleton strings".

One of the more peculiar things about the Ruby programming language is how heavily it leans on Symbols and how Symbols are easily confused with strings because many methods will take either-or without caring, while some others are extremely particular.

They're really quite handy when you get used to them conceptually, but it isn't something you see in other languages like C++, Python, JavaScript or what have you.

Upvotes: 5

mrzasa
mrzasa

Reputation: 23347

@posts is an instance variable - think about it as a private field or private property of an object.

:post is a symbol - something like a constant string that is in this case uses as hash key (in params[:id]) or as a key indicating what params are allowed (in params.require(:post).permit(:title, :body)

Upvotes: 3

Related Questions