=> operator vs = operator - ruby-on-rails

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'}

Related

What is the difference between :id, id: and id in ruby?

I am trying to build a ruby on rails and graphQL app, and am working on a user update mutation. I have spent a long time trying to figure it out, and when I accidentally made a typo, it suddenly worked.
The below is the working migration:
module Mutations
class UpdateUser < BaseMutation
argument :id, Int
argument :first_name, String
argument :last_name, String
argument :username, String
argument :email, String
argument :password, String
field :user, Types::User
field :errors, [String], null: false
def resolve(id:, first_name:, last_name:, username:, email:, password:)
user = User.find(id)
user.update(first_name:, last_name:, username:, email:, password:)
{ user:, errors: [] }
rescue StandardError => e
{ user: nil, errors: [e.message] }
end
end
end
The thing I am confused about is when I define the arguments, they are colon first: eg :id or :first_name
When I pass them to the resolve method they only work if they have the colon after: eg id: or first_name:
When I pass the variables to the update method, they use the same syntax of colon after, for all variables other than ID. For some reason, when I used id: it was resolving to a string "id", and using colon first :id was returning an undefined error.
It is only when I accidentally deleted the colon, and tried id that it actually resolved to the passed through value.
My question for this, is why and how this is behaving this way? I have tried finding the answer in the docs, and reading other posts, but have been unable to find an answer.
Please someone help my brain get around this, coming from a PHP background, ruby is melting my brain.
It's going to take some time to get used to Ruby, coming from PHP, but it won't be too bad.
Essentially id is a variable, or object/model attribute when used like model_instance.id. In PHP this would be like $id or $object_instance->id.
When you see id: it is the key in a key-value pair, so it expects something (a value) after it (or assumes nil if nothing follows, often in method definitions using keyword arguments like your example). A typical use might be model_instance.update(id: 25) where you are essentially passing in a hash to the update method with id as the key and 25 as the value. The older way to write this in Ruby is with a "hash rocket" like so: model_instance.update(:id => 25).
More reading on Ruby hashes: https://www.rubyguides.com/2020/05/ruby-hash-methods
More reading on keyword arguments: https://www.rubyguides.com/2018/06/rubys-method-arguments
Now if you're paying attention that hash rocket now uses the 3rd type you're asking about. When you see a colon preceding a string like that it is called a "symbol" and it will take some time to get used to them but they are essentially strings in Ruby that are one character fewer to define (and immutable). Instead of using 'id' or "id" as a string, Ruby folks often like to use :id as a symbol and it will typically auto-convert to a string when needed. A good example might be an enumerator of sorts.
state = :ready
if state == :ready
state = :finished
else
state = :undefined
end
More reading on Ruby symbols: https://www.rubyguides.com/2018/02/ruby-symbols

Rails find_or_create_by add cast to hash value json type attribute?

I have a model with an :extra_fields column that is :jsonb datatype, I want to add in the attr hashes to the column, something like this below but I am unsure of the syntax to cast the hash values' datatypes here, and if not here what is the best practice for casting hash value data ?
instance = Model.find_or_create_by(ref_id: hash[:ref_id]) do |a|
a.extra_fields = {
'attr1' : hash[:attr1], <-- //possible to cast type here ie ::type ?
'attr2' : hash[:attr2] <--
}
instance.save!
end
Bonus: how would I cast the hash values as type :decimal, :string, :boolean, :date for example?
All incoming parameters in Rails/Rack are strings. Well except except array/hash parameters which still have strings as values. Rails does the actual casting when you pass parameters to models.
You can cast strings to any other type in Ruby with the .to_x methods:
irb(main):006:0> "1.23".to_f
=> 1.23
irb(main):007:0> "1.23".to_d
=> #<BigDecimal:7ff7dea40b68,'0.123E1',18(18)>
irb(main):008:0> 1.23.to_s
=> "1.23"
irb(main):008:0> 1.23.to_i
=> 1
Boolean casting is a Rails feature. You can do it by:
# Rails 5
ActiveModel::Type::Boolean.new.cast(value)
ActiveModel::Type::Boolean.new.cast("true") # true
ActiveModel::Type::Boolean.new.cast("t") # true
ActiveModel::Type::Boolean.new.cast("false") # false
ActiveModel::Type::Boolean.new.cast("f") # false
# This is somewhat surprising
ActiveModel::Type::Boolean.new.cast("any arbitrary string") # true
# Rails 4.2
ActiveRecord::Type::Boolean.new.type_cast_from_database(value)
# Rails 4.1 and below
ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
Note that this is very different then the Ruby boolean coercion done by ! and !!.
irb(main):008:0> !!"false"
(irb):8: warning: string literal in condition
=> true
In Ruby everything except nil and false are true.
Dates are somewhat more complex. The default Rails date inputs use multi-parameters to send each part of the date (year, month, day) and a special setter that constructs a date from these inputs.
Processing by PeopleController#create as HTML
Parameters: { "person"=>{"birthday(1i)"=>"2019", "birthday(2i)"=>"2", "birthday(3i)"=>"16"}, ...}
You can construct a date from these parameters by:
date_params = params.fetch(:person).permit("birthday")
Date.new(*date_params.values.map(&:to_i))
what is the best practice for casting hash value data ?
There is no best practice here. What you instead should be pondering is the use of a JSON column. Since you seem to be want to apply some sort of schema to the data it might be a good idea to actually create a separate table and model. You are after all using a relational database.
JSON columns are great for solving some complex issues like key/value tables or storing raw JSON data but they should not be your first choice when modelling your data.
See PostgreSQL anti-patterns: Unnecessary json/hstore dynamic columns for a good write up on the topic.

trying to understand specific bit of ruby syntax

I'm new to both Ruby and Rails, and as I go over various tutorials, I occasionally hit on a bit of Ruby syntax that I just can't grok.
For instance, what does this actually do?
root to: "welcome#index"
I can gather that this is probably a method named "root", but I'm lost after that. "To" isn't a symbol, is it? The colon would be before, as in ":to" if it were. Is this some form of keyword argument utilizing hashes? I can't make this syntax work when trying it in irb with ruby1.9.3.
I know this might be a RTFM question, but I can't even think of what to google for this.
Thanks!
I'm still playing around with this syntax,
def func(h)
puts h[:to]
end
x = { :to => "welcome#index" }
y = :to => "welcome#index"
z = to: "welcome#index"
func to: "welcome#index"
I see that this example only works with the lines defining "y" and "z" commented out. So the braceless and the "colon-after" syntax are only valid in the context of calling a method?
First, that's right - root is a method call.
Now
to: 'welcome#index'
is equivalent to
:to => 'welcome#index'
and it's a Hash where the key is :to symbol and value is 'welcome#index' string. You can use this syntax in defining hashes since Ruby 1.9.
It's equivalent to
root(:to => "welcome#index")
I'm having trouble finding the official documentation on the new hash syntax, but when you see foo: bar, it means that foo is a symbol used as a key in the hash and has a value bar.
Here is an example of defining a function foo which takes a hash, and prints to screen.
def foo(hash)
puts hash.inspect
puts hash[:to]
end
foo to: "wecome#index" #method call without paratheses
Output of method call above:
{:to=>"welcome#index"}
welcome#index
Equivalent declarations:
h = {:to => "welcome#index"}
h = {to: "wecolme#index"}
Also, you can use Ripper (part of Ruby standard library) to understand how Ruby parses code. In the example below, I have already defined foo as above. Now, I call foo without using Ripper. Then I use Ripper to see how Ruby parses the method call.
[2] pry(main)> foo to: "welcome#index"
{:to=>"welcome#index"}
welcome#index
=> nil
[3] pry(main)> require 'ripper'
=> true
[4] pry(main)> Ripper.sexp 'foo to: "welcome#index"'
=> [:program,
[[:command,
[:#ident, "foo", [1, 0]],
[:args_add_block,
[[:bare_assoc_hash,
[[:assoc_new,
[:#label, "to:", [1, 4]],
[:string_literal,
[:string_content, [:#tstring_content, "welcome#index", [1, 9]]]]]]]],
false]]]]
In ruby braces in method calls are optional, so it can be rewritten as:
root(to: "welcome#index")
and it can be rewritten again as
root(:to => "welcome#index")
Hashes as keyword arguments(ruby 1.9) explained here as well: hash-constructor-parameter-in-1-9
P.S. and by the way general rule of the thumb for the rails-newcomers is "learn ruby first, then learn rails" ;)
As you correctly gathered, root is a method call. Or rather, it's a message send. Ruby, like Smalltalk, builds upon a messaging metaphor, where objects send messages to other objects, and those objects (called the receiver) respond to those messages.
In this case, you pass an argument to root, that's how you know it's a message send. Message sends are the only thing that can take arguments, if you see an argument, then it must be a message send. There are no functions, no static methods, no constructors, no procedures, only methods and message sends.
So, what is the argument? Well, in Ruby, a lot of things that are syntactically required in other languages are optional. For example, parenthesis around the argument list:
foo.bar(baz)
# can also be written as
foo.bar baz
If the very last argument to the message send is a Hash literal, you can leave off the curly braces:
foo.bar({ :baz => 23, :quux => 42 })
# can also be written as
foo.bar(:baz => 23, :quux => 42)
Put the two together, and you get:
foo.bar({ :baz => 23, :quux => 42 })
# can also be written as
foo.bar :baz => 23, :quux => 42
In Ruby 1.9, a new alternative Hash literal syntax was introduced. This literal syntax is very limited compared to the original one, because it can only express Hashes whose keys are Symbols which are also valid Ruby identifiers, whereas with the original syntax, you can write down a Hash with any arbitrary object as key. But, for that limited use case, it is very readable:
{ :baz => 23, :quux => 42 }
# can also be written as
{ baz: 23, quux: 42 }
If we put that feature together with the other two, we get the message send syntax you are asking about:
foo.bar baz: 23, quux: 42
If we have method declared with a single argument like this:
def foo.bar(opts) p opts end
opts will be bound to a single Hash with two key-value pairs.
These features were often used to emulate keyword arguments as found in other languages. And it has long been a desire of the Ruby community to get support for real keyword arguments. This support was implemented in two steps: first, the new Hash literal syntax was introduced in Ruby 1.9, which allows you to make message sends which look like they are using keyword arguments, even though they are really just a Hash. And then in a second step, in Ruby 2.0 real keyword arguments were introduced. The modified method signature would look like this:
def foo.bar(baz: nil, quux: nil) p baz, quux end
Note that at the moment, it is not possible to have required keyword arguments, they always need to have a default value and are thus always optional. You can, however, use the fact that default values can be arbitrary expressions and do something like this:
def foo.bar(baz: raise ArgumentError '`baz` must be supplied!',
quux: raise ArgumentError '`quux` must be supplied!') p baz, quux end
In a future version of Ruby (it was actually already implemented in February and will likely be in 2.1), required keyword arguments can be specified by omitting the default value:
def foo.bar(baz:, quux:) p baz, quux end
Note that there is a syntactic ambiguity now:
foo.bar baz: 23, quux: 42
# is this sending the message `bar` to `foo` with *one* `Hash` or *two* keywords?
This ambiguity is actually intentional, because it allows old client code written against APIs which use a Hash argument to work unchanged with new APIs that use keyword arguments. There are some semi-complex rules which determine whether that syntax will be interpreted as a Hash or as keywords, but mostly those rules work out the way you would expect them to.

Create a dictionary as an attribute in FactoryGirl

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.

How are symbols used to identify arguments in ruby methods

I am learning rails and going back to ruby to understand how methods in rails (and ruby really work). When I see method calls like:
validates :first_name, :presence => true
I get confused. How do you write methods in ruby that accept symbols or hashes. The source code for the validates method is confusing too. Could someone please simplify this topic of using symbols as arguments in ruby class and instance methods for me?
UPDATE:
Good one #Dave! But What I was trying out was something like:
def full_name (:first_name, :last_name)
#first_name = :first_name
#last_name = :last_name
p "#{#first_name} #{last_name}"
end
full_name("Breta", "Von Sustern")
Which obviously raises errors. I am trying to understand: Why is passing symbols like this as arguments wrong if symbols are just like any other value?
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.
I thought I'd add an update for Ruby 2+ since this is the first result I found for 'symbols as arguments'.
Since Ruby 2.0.0 you can also use symbols when defining a method. When calling the method these symbols will then act almost the same as named optional parameters in other languages. See example below:
def variable_symbol_method(arg, arg_two: "two", arg_three: "three")
[arg, arg_two, arg_three]
end
result = variable_symbol_method :custom_symbol, arg_three: "Modified symbol arg"
# result is now equal to:
[:custom_symbol, "two", "Modified symbol arg"]
As shown in the example, we omit arg_two: when calling the method and in the method body we can still access it as variable arg_two. Also note that the variable arg_three is indeed altered by the function call.
In Ruby, if you call a method with a bunch of name => value pairs at the end of the argument list, these get automatically wrapped in a Hash and passed to your method as the last argument:
def foo(kwargs)
p kwargs
end
>> foo(:abc=>"def", 123=>456)
{:abc=>"def", 123=>456}
>> foo("cabbage")
"cabbage"
>> foo(:fluff)
:fluff
There's nothing "special" about how you write the method, it's how you call it. It would be perfectly legal to just pass a regular Hash object as the kwargs parameter. This syntactic shortcut is used to implement named parameters in an API.
A Ruby symbol is just a value as any other, so in your example, :first_name is just a regular positional argument. :presence is a symbol used as a Hash key – any type can be used as a Hash key, but symbols are a common choice because they're immutable values.
I think all replies have missed the point of question; and the fact it is asked by someone who is - I guess - not clear on what a symbol is ?
As a newcomer to Ruby I had similar confusions and to me an answer like following would have made more sense
Method Arguments are local variables populated by passed in values.
You cant use symbols as Arguments by themselves, as you cant change value of a symbol.
Symbols are not limited to hashes. They are identifiers, without the extra storage space of a string. It's just a way to say "this is ...."
A possible function definition for the validates call could be (just to simplify, I don't know off the top of my head what it really is):
def validates(column, options)
puts column.to_s
if options[:presence]
puts "Found a presence option"
end
end
Notice how the first symbol is a parameter all of its own, and the rest is the hash.

Resources