I have this line of code in my function:
people << {"id": person.id, "name": person.name, "age": person.age}
This ran fine in my development environment. But in my friend's pc, it says there is a syntax error in this line. It says that the colon in "id": person.id is wrong. Writing the above code as "id"=> person.id fixed the issue. Is this issue possibly due to different ruby versions?
people << {"id": person.id, "name": person.name, "age": person.age}
This syntax is new in 2.2.0. Before 2.2, the Symbols in the JSON-style Hash literals could only be valid Ruby identifiers (strictly speaking, valid Ruby labels) and could not be quoted.
See Feature #4276: Allow use of quotes in symbol syntactic sugar for hashes for details.
Writing the above code as "id"=> person.id fixed the issue.
Those two are not equivalent! The Hash above has Symbols as keys, your replacement has Strings as keys. There are several equivalent notations for the Hash literal above, but yours isn't:
{ id: person.id, name: person.name, age: person.age } # 1.9+
{ 'id': person.id, 'name': person.name, 'age': person.age } # 2.2+
{ :id => person.id, :name => person.name, :age => person.age } # all versions
{ :'id' => person.id, :'name' => person.name, :'age' => person.age } # all versions
{ :"id" => person.id, :"name" => person.name, :"age" => person.age } # all versions
I ordered them roughly in order of preference, with the first one being the most preferred. You shouldn't quote Symbol literals that don't need quoting, and you shouldn't use double quotes if you don't intend to use interpolation.
Your friend is probably using Ruby < v1.9.
That short syntax was introduced in 1.9. Before that it was all hash rocket (=>) syntax.
Related
Im currently trying to merge two hashes, I don't really have much else to go on but this is the result I need is showed in this example;
{key_1: 'i want to replace this', key_2: 'i want to keep this'}.merge({key_1: 'new text'})
=> {key_1: 'new text', key_2: 'i want to keep this'}
Currently what I've got looks like this;
#notification.attributes.merge({body: ()}).to_json Im attempting to merge an replace the first key with the body element. What I'm really missing is the argument to perform the key replacement. If anyone has any direction, advice or even answers it would be much appreciated, thanks.
In Rails #attributes returns a hash with string keys:
irb(main):001:0> note = Notification.new(title: 'All your base are belong to us', body: 'Loren Ipsum...')
irb(main):002:0> note.attributes
=> {"id"=>nil, "title"=>"All your base are belong to us", "body"=>"Loren Ipsum...", "read_at"=>nil, "created_at"=>nil, "updated_at"=>nil}
If you want to replace a key in the hash you either need to use a hash with string keys:
irb(main):003:0> note.attributes.merge("body" => "Moahahahahahaha")
=> {"id"=>nil, "title"=>"All your base are belong to us", "body"=>"Moahahahahahaha", "read_at"=>nil, "created_at"=>nil, "updated_at"=>nil}
Or you need to change the keys of the hash to symbols which can be done with Hash#symbolize_keys:
irb(main):004:0> note.attributes.symbolize_keys.merge(body: "Moahahahahahaha")
=> {:id=>nil, :title=>"All your base are belong to us", :body=>"Moahahahahahaha", :read_at=>nil, :created_at=>nil, :updated_at=>nil}
This is a pretty common source of errors for new developers as Rails abstracts away the difference between symbol and string keys in many places through the use of ActiveSupport::HashWithIndifferentAccess or hash like objects like ActionController::Parameters that have indifferent access while Ruby itself is strict about the difference.
irb(main):008:0> { "foo" => "bar" }.merge(foo: 'baz')
=> {"foo"=>"bar", :foo=>"baz"}
irb(main):009:0> { "foo" => "bar" }.with_indifferent_access.merge(foo: 'baz')
=> {"foo"=>"baz"}
If you ever need to do this with a nested hash you can use the recursive versions deep_symbolize_keys and deep_merge.
You have to add ! in your merge operation, as when you are doing operation it is not effecting your actual object but will create new object. So your example as you said I did following to change values
{key_1: 'i want to replace this', key_2: 'i want to keep this'}.merge!({key_1: 'new text'})
# result
{:key_1=>"new text", :key_2=>"i want to keep this"}
please change following from
#notification.attributes.merge({body: ()}).to_json
to
#notification.attributes.merge!({body: ()}).to_json
Please try to change the order of master hash (the master is what you want to keep)
[9] pry(main)> a = {:key_1=>"i want to replace this", :key_2=>"i want to keep this"}
=> {:key_1=>"i want to replace this", :key_2=>"i want to keep this"}
[10] pry(main)> b = {:key_1=>"new text"}
=> {:key_1=>"new text"}
[11] pry(main)> c = b.merge(a)
=> {:key_1=>"i want to replace this", :key_2=>"i want to keep this"}
[12] pry(main)> d = a.merge(b); // <===== This is what you want.
=> {:key_1=>"new text", :key_2=>"i want to keep this"}
Hope it help. Thanks
deep_symbolize_keys! converts string keys to symbol keys. This works for hashes and all sub-hashes. However, I have a data like this:
arr = [
{'name': 'pratha', 'email': 'p#g.com', 'sub': { 'id': 1 } },
{'name': 'john', 'email': 'c#d.com', 'sub': { 'id': 2 } }
]
arr.deep_symbolize_keys! # this is not working for array of hashes.
In this case, hashes are in an array. So how can i symbolize all at once?
Using Ruby 2.6.3
I also read somewhere that this is deprecated (probably on one of the Rails forum). Is that true? If so, what is the best way to convert keys to symbols in my case?
Currently using this:
def process(emails)
blacklist = ["a", "john", "c"]
e = emails.map do |hash|
blacklist.include?(hash['name']) ? nil : hash.deep_symbolize_keys!
end
e
end
Do you need a copy or an in-place transformation? In-place you can use arr.each(&:deep_symbolize_keys!). For a copy you should use arr.map(&:deep_symbolize_keys). Remember that map does not mutate but returns a new array.
The implementation already handles nested arrays, it just doesn't define the method on Array. So, nest it in a temporary hash and symbolize that. This works for arbitrary types:
[1] pry(main)> def deep_symbolize_keys(object) = {object:}.deep_symbolize_keys[:object];
[2] pry(main)> deep_symbolize_keys([{"a" => 1}, {"b" => {"c" => 2}}])
=> [{:a=>1}, {:b=>{:c=>2}}]
Also, be careful with your key syntax. In your example, your keys are already symbols - they're just quoted symbols:
[3] pry(main)> {a: 1}.keys.first.class
=> Symbol
[4] pry(main)> {'a': 1}.keys.first.class
=> Symbol
[5] pry(main)> {'a' => 1}.keys.first.class
=> String
The syntax is necessary to handle cases like {'a-b': 1}[:'a-b'], but it's very often misleading since they look so much like string keys. I recommend avoiding it entirely unless absolutely necessary - stick to {a: 1} for symbol keys and {'a' => 1} for string keys.
I used to be more of a hobby Java guy and try to switch to Ruby on Rails right now.
But I'm having some difficulties, believe it or not, I like braces and semicolons..gives some orientation.
But here's the question:
Right now I'm taking an online course in RoR and it occured to me, that I'm always wrong on how to work with symbols, hashes etc.
Example 1:
Let's take a look at this single line of code for example:
form_for(:session, :html => {class: "form-horizontal", role: "form"}, url: login_path)
And this is how I read it:
Method / Function name is
form_for
Parameters parsed to this method are:
:session, :html => {class: "form-horizontal", role: "form"}, url: login_path
Let's break up those into
:session
:html => {class: "form-horizontal", role: "form"}
url: login_path
How the heck should I know how to declare those parameters?
Why are :session and :html passend in as keys and url not?
Is the :html symbol a Hashmap-symbol?
Example:
In an model File you declare an n:m relationship like this (for example users <-> stocks)
has_many :users, through: :user_stocks
Ok, I get that the first argument is :users and the second is the same as
:through => :user_stocks
correct?
But in the same way, let's look at an routes.rb config from the same project:
resources :user_stocks, except: [:show, :edit, :update]
Now we're using an array of keys on the except hash, correct?
It does get clearer when writing an question but still, is there a rule of thumb / convention on when to use
:name
name: value
:name => {values}?
name: [values]
Or is it just an personal preference? In that case I should hope that my online teacher stays consistent..
Generally speaking, I'm very confused on how the parameter syntax convention is and when to use what (what type of argument).
Is it just because I am starting with Ruby or did I miss some piece of convention.
I hope my problem is kind of understandable and excuse my english - non native speaker.
I really like to get along with RoR but right now watching the online course sometimes leaves me more confused than before because if I would've done it by myself, I would've used a completely different way.
How the heck should I know how to declare those parameters?
You look up the method in the docs and you read about it.
Parameters parsed to this method are:
:session,
:html => {class: "form-horizontal", role: "form"},
url: login_path
How the heck should I know how to declare those parameters? Why are
:session and :html passend in as keys and url not? Is the :html symbol
a Hashmap-symbol?
In ruby, if you pass in a series of key-value pairs at the end of the argument list, ruby gathers them all into a hash and passes them as one argument to the method. Here is an example:
def go(x, y)
p x
p y
end
go(:hello, a: 10, b: 20)
--output:--
:hello
{:a=>10, :b=>20}
Another example:
def go(x, y)
p x
p y
end
go(
:session,
:html => {class: "form-horizontal", role: "form"},
url: 'xyz'
)
--output:--
:session
{:html=>{:class=>"form-horizontal", :role=>"form"}, :url=>"xyz"}
has_many :users, through: :user_stocks
Ok, I get that the first argument is :users and the second is the same
as
:through => :user_stocks
correct?
Correct. In old ruby, key-value pairs in hashes were written like this:
'a' => 'hello'
If the value was a symbol, then it looked like this:
'a' => :hello
If the key was also a symbol, then you wrote:
:a => :hello
In modern ruby, if the key is a symbol you can write:
a: 'hello'
which is a shortcut for:
:a => 'hello'
and if the value is a symbol as well, in modern ruby it looks like this:
a: :hello
which is a shortcut for:
:a => :hello
resources :user_stocks, except: [:show, :edit, :update]
Now we're using an array of keys on the except hash, correct?
The hash isn't named except, but otherwise you are correct.
a rule of thumb / convention on when to use
:name # Single symbol argument
name: value # A key-value pair in a hash. The key is a symbol.
:name => {values}? #A key-value pair in a hash. The value looks like a hash, but the syntax is incorrect.
name: [values] #A key-value pair in a hash. The value is your notation for an array.
Or is it just an personal preference? In that case I should hope that my online teacher stays consistent..
Once again, a method can be defined to take any type of argument. Because ruby variables don't have types, you have to check the docs. If a method expects you to pass in a hash where the key :name has a value that is a hash, then you need to do that. On the other hand, if the method expects you to pass in a hash where the key :name has a value that is an array, then you need to do that.
Generally speaking, I'm very confused on how the parameter syntax
convention is and when to use what (what type of argument). Is it just
because I am starting with Ruby or did I miss some piece of
convention.
Ruby has a lot of shortcuts, which can be confusing to a beginner. Then there is the whole String v. Symbol concept. If you can understand the practical difference between a Symbol and a String, then you will be ahead of the game. A Symbol is like an integer. So when ruby has to compare whether Symbols are equal, ruby compares two integers, which is fast. If ruby has to compare Strings, then ruby has to compare the ascii code of each letter in one string to the ascii code of each letter in the other string until ruby finds a difference. For instance, in order for ruby to compare the following two Strings:
"helloX" v. "helloY"
ruby won't find a difference until after it has made six integer comparisons:
'h' v 'h' => equal
'e' v 'e' => equal
...
...
'X' v 'Y' => not equal
On the other hand, if ruby were comparing:
:helloX v. :helloY
the Symbols are essentially stored as single integers, something like:
341343 v. 134142 => not equal
To compare them takes only a single integer comparison, so it's faster. As someone is sure to point out, that isn't quite how Symbols are implemented, but the details don't matter. It's sufficient to know that Symbol comparisons are faster than String comparisons, and as to why that is true the example above is sufficient to demonstrate there is at least one implementation where it can be true.
Hashes: hashrockets vs literal
In Ruby hashes can use either "hashrockets" or the newer literal notation (since Ruby 1.9):
# hashrockets
{ :foo => "bar" }
# literal
{ foo: "bar" }
They both do the exact same thing. They create a hash with the symbol :foo as a key. The literal syntax is now generally preferered.
Hashrockets should only be used today if you have something other than symbols as keys (you can have numbers, strings or any object as keys):
{ 1 => 'a', 2 => 'b' }
# using literals won't work here since it will cast the keys to symbols:
{ 1: 'a', 2: 'b' } # => { :1 => 'a', :2 => 'b' }
As Ruby is loosly typed hashes can contain any kinds of values:
{ foo: "bar" }
{ foo: [:bar, :baz] }
{ foo: { bar: :baz } }
Hash options
In Ruby methods can recieve both ordinal arguments and a hash with options:
def foo(bar, hash = {})
#test= hash[:]
end
The hash options must come after the positional arguments in the arguments list.
# syntax error
foo(a: 2, "test")
# good
foo("test", a: 2)
When passing a hash options you can forgo the surrounding brackets as they are implied:
foo("test", { a: 1, b: 2 })
# same result but nicer to read
foo("test", a: 1, b: 2 )
Ruby 2.0 introduced keyword arguments which reduces the amount of boilerplate code needed to parse the options:
def foo(bar, test: nil)
#test= test
end
def foo(bar, test: nil, **kwargs)
#test= test
# all the options except test
puts kwargs.inspect
end
# will raise an error if the test key is not passed
def foo(bar, test:)
#test= test
end
Postitional arguments vs hash options
Postitional arguments are shorter and ideal where the order of the parameters is self explainatory:
class Number
def add(x)
#val += x
end
end
When you have more complex methods with a large number of arguments it can be tricky to keep track of the order:
def book_flight(destination, seats, airline_preference= nil, special_meals= nil)
end
thats where hash options come in and shine as you can have an arbirtrary number of options and the program does not blow up because you forgot a nil in the call.
This is how I went about to query for one specific element.
results << read_db.collection("users").find(:created_at => {:$gt => initial_date}).to_a
Now, I am trying to query by more than one.
db.inventory.find({ $and: [ { price: 1.99 }, { qty: { $lt: 20 } }, { sale: true } ] } )
Now how do I build up my query? Essentially I will have have a bunch of if statements, if true, i want to extend my query. I heard there is a .extend command in another langue, is there something similar in ruby?
Essentially i want to do this:
if price
query = "{ price: 1.99 }"
end
if qty
query = query + "{ qty: { $lt: 20 } }"
end
and than just have
db.inventory.find({ $and: [query]})
This syntax is wrong, what is the best way to go about doing this?
You want to end up with something like this:
db.inventory.find({ :$and => some_array_of_mongodb_queries})
Note that I've switched to the hashrocket syntax, you can't use the JavaScript notation with symbols that aren't labels. The value for :$and should be an array of individual queries, not an array of strings; so you should build an array:
parts = [ ]
parts.push(:price => 1.99) if(price)
query.push(:qty => { :$lt => 20 }) if(qty)
#...
db.inventory.find(:$and => parts)
BTW, you might run into some floating point problems with :price => 1.99, you should probably use an integer for that and work in cents instead of dollars. Some sort of check that parts isn't empty might be a good idea too.
Trying to add a very rudimentary description template to one of my Rails models. What I want to do is take a template string like this:
template = "{{ name }} is the best {{ occupation }} in {{ city }}."
and a hash like this:
vals = {:name => "Joe Smith", :occupation => "birthday clown", :city => "Las Vegas"}
and get a description generated. I thought I could do this with a simple gsub but Ruby 1.8.7 doesn't accept hashes as the second argument. When I do a gsub as a block like this:
> template.gsub(/\{\{\s*(\w+)\s*\}\}/) {|m| vals[m]}
=> " is the best in ."
You can see it replaces it with the entire string (with curly braces), not the match captures.
How do I get it to replace "{{ something }}" with vals["something"] (or vals["something".to_sym])?
TIA
Using Ruby 1.9.2
The string formatting operator % will format a string with a hash as the arg
>> template = "%{name} is the best %{occupation} in %{city}."
>> vals = {:name => "Joe Smith", :occupation => "birthday clown", :city => "Las Vegas"}
>> template % vals
=> "Joe Smith is the best birthday clown in Las Vegas."
Using Ruby 1.8.7
The string formatting operator in Ruby 1.8.7 doesn't support hashes. Instead, you can use the same arguments as the Ruby 1.9.2 solution and patch the String object so when you upgrade Ruby you won't have to edit your strings.
if RUBY_VERSION < '1.9.2'
class String
old_format = instance_method(:%)
define_method(:%) do |arg|
if arg.is_a?(Hash)
self.gsub(/%\{(.*?)\}/) { arg[$1.to_sym] }
else
old_format.bind(self).call(arg)
end
end
end
end
>> "%05d" % 123
=> "00123"
>> "%-5s: %08x" % [ "ID", 123 ]
=> "ID : 0000007b"
>> template = "%{name} is the best %{occupation} in %{city}."
>> vals = {:name => "Joe Smith", :occupation => "birthday clown", :city => "Las Vegas"}
>> template % vals
=> "Joe Smith is the best birthday clown in Las Vegas."
codepad example showing the default and extended behavior
The easiest thing is probably to use $1.to_sym in your block:
>> template.gsub(/\{\{\s*(\w+)\s*\}\}/) { vals[$1.to_sym] }
=> "Joe Smith is the best birthday clown in Las Vegas."
From the fine manual:
In the block form, the current match string is passed in as a parameter, and variables such as $1, $2, $`, $&, and $’ will be set appropriately. The value returned by the block will be substituted for the match on each call.