Reputation: 11040
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
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 thePost
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 Symbol
s, but unlike Ruby, it doesn't have literal syntax for them.
Ruby inherited Symbol
s 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 Symbol
s:
''.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 Symbol
s:
alias_method :foo, :bar
And so on.
There are two differences between Ruby's and ECMAScript's Symbol
s, though.
The first one is a syntactic difference, and I alluded to it above: Ruby has literal syntax for Symbol
s (: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, Symbol
s 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 Symbol
s are never identical, even if they have the same name:
Symbol('foo') === Symbol('foo')
//=> false
This allows Symbol
s 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 Symbol
s.
ECMAScript has a Global Symbol Registry, and I can store and retrieve Symbol
s from it using Symbol.for
. Ruby's Symbol
s 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 param
s 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
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
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
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