Related
I would like to add a hash into an array using Ruby version 1.8.7:
items = Array.new
items.push {:a => "b", :c => "d"}
Statements above will return an error something like:
SyntaxError: compile error
(irb):35: syntax error, unexpected tASSOC, expecting '}'
items.push {:a => "b", :c => "d"}
^
(irb):35: syntax error, unexpected ',', expecting '}'
items.push {:a => "b", :b => "c"}
^
Well, I found that the solution is to wrap the push arguments within parenthesis ( ) or I can use the << operator. I also know that push accept one or more argument and << only accept a single argument from this answer, but what's bothering me is that why do I need to use the parenthesis, while as we all know parenthesis in Ruby are optional?
My guess is that this is because ruby is trying to parse the hash as a block, expecting code and not hash keys and values. this is similar to:
items.push() do
:a => "b", :b => "c"
end
which is not valid syntax.
This is a newbie question. I find the method definition in the YARD Rdoc:
(Object) find(selector = {}, opts = {})
Options Hash (opts):
:fields (Array, Hash)
then I try this coll.find('English' => 'fulcrum',{English:1,Chinese:1}), want the result 'English' field is fulcrum ,and only return English and Chinese field, but Ruby punished me with the this
irb(main):018:0> coll.find('English' => 'fulcrum',{English:1,Chinese:1})
SyntaxError: (irb):18: syntax error, unexpected ')', expecting tASSOC
from /usr/local/bin/irb:12:in `<main>'
irb(main):019:0>
I want to know why, thanks
after correct the syntax problem by the suggestion by #mu, I got Unknown options error:
irb(main):013:0> coll.find({English:'fulcrum'},{English:1, :Chinese => 1})RuntimeError: Unknown options [{:English=>1, :Chinese=>1}]
from /usr/local/lib/ruby/gems/1.9.1/gems/mongo-1.5.2/lib/mongo/collection.rb:234:in `find'
from (irb):13
from /usr/local/bin/irb:12:in `<main>'
irb(main):014:0>
When Ruby sees an unwrapped Hash in argument list:
o.m(k => v, ...)
it assumes that you really mean this:
o.m({ k => v, ... })
So, when you say this:
coll.find('English' => 'fulcrum', {English: 1, Chinese: 1})
Ruby sees this:
coll.find({ 'English' => 'fulcrum', {English: 1, Chinese: 1} })
A Hash is a perfectly valid key so Ruby expects it to be followed by a => value:
coll.find('English' => 'fulcrum', {English: 1, Chinese: 1} => some_value)
and that's where the error message comes from:
syntax error, unexpected ')', expecting tASSOC
If you want to pass two hashes, you need to wrap the first one in braces:
coll.find({'English' => 'fulcrum'}, {English: 1, Chinese: 1})
The second argument to [find](
http://api.mongodb.org/ruby/current/Mongo/Collection.html#find-instance_method) should be an options Hash and it looks like you want the :fields option and you can give that an array of names instead of a noisy Hash:
coll.find({'English' => 'fulcrum'}, :fields => %w[English Chinese])
I'm trying to implement the rails-settings gem (https://github.com/100hz/rails-settings) into my Rails 3 project using Ruby 1.8.7
Setting and retrieving the settings works perfectly, but I get an error if I try getting all settings of a specific user.
So, in the 'rails console' the following works:
user = User.find(123)
user.settings.color = :red
user.settings.color
But if I try to get all settings:
user.settings.all
I get:
NoMethodError: undefined method `merge' for []:Array
from /[...]/.rvm/gems/ruby-1.8.7-p334/bundler/gems/rails-settings-883114dfd933/lib/rails-settings/settings.rb:55:in `all'
from (irb):5
line 55 in the settings.rb:
#retrieve all settings as a hash (optionally starting with a given namespace)
def self.all(starting_with=nil)
options = starting_with ? { :conditions => "var LIKE '#{starting_with}%'"} : {}
vars = thing_scoped.find(:all, {:select => 'var, value'}.merge(options))
result = {}
vars.each do |record|
result[record.var] = record.value
end
# line 55 is below this one...
##defaults.select{ |k| k =~ /^#{starting_with}/ }.merge(result).with_indifferent_access
end
Whats the problem here? Or is this a ruby 1.8.7 vs. 1.9.2 thing?
That's a Ruby 1.8.7 vs. 1.9.2 thing
The Hash select method under ruby 1.8.7 will return an Array of Arrays.
Example:
{:a => 'a', :b => 'b', :c => 'c'}.select {|k, v| v > 'a'} #=> [[:b,'b'],[:c,'c']]
While the same thing running Ruby 1.9.2 will return:
{:a => 'a', :b => 'b', :c => 'c'}.select {|k, v| v > 'a'} #=> {:b => 'b',:c => 'c'}
You will need to post process the result and turn it into a hsah again or use something like inject.
Edit:
Here is a quick/ugly example of the inject
{:a => 'a', :b => 'b', :c => 'c'}.inject({}) {|r, e| e[1] > 'a' ? r.merge({e[0] => e[1]}) : r }
Semantically speaking:
collection.inject(container) { |container, element| select_condition ? container + element : container }
Edit 2: (Based on #CaleyWoods post)
Hash[*##defaults.select{ |k,v| k =~ /^#{starting_with}/ }.flatten].merge(result)
The |k, v| will prevent unnecessary warnings.
This looks like it's trying to do a merge on an Array which is not a method the Array class in Ruby. Merge is supported for Hash, it looks like your returned object is not the correct type. The author was definitely relying on a hash, in the next to last line 'with_indifferent_access' is called which is trying to allow you to select items from the hash with strings or symbols.
I can't examine the gem further right now and I wish I could provide a more helpful answer. If this hasn't been answered later i'll come back and help you out.
Not sure why the author is using double and single quotes in options and vars. He's also trying to populate the hash by hand instead of with inject. It's not awful by any means but I think there's room for improvement in the small bit of code you posted from the file.
Is there a nice (one line) way of writing a hash in ruby with some entry only there if a condition is fulfilled? I thought of
{:a => 'a', :b => ('b' if condition)}
But that leaves :b == nil if the condition is not fulfilled. I realize this could be done easily in two lines or so, but it would be much nicer in one line (e.g. when passing the hash to a function).
Am I missing (yet) another one of ruby's amazing features here? ;)
UPDATE Ruby 2.4+
Since ruby 2.4.0, you can use the compact method:
{ a: 'a', b: ('b' if cond) }.compact
Original answer (Ruby 1.9.2)
You could first create the hash with key => nil for when the condition is not met, and then delete those pairs where the value is nil. For example:
{ :a => 'a', :b => ('b' if cond) }.delete_if{ |k,v| v.nil? }
yields, for cond == true:
{:b=>"b", :a=>"a"}
and for cond == false
{:a=>"a"}
UPDATE for ruby 1.9.3
This is equivalent - a bit more concise and in ruby 1.9.3 notation:
{ a: 'a', b: ('b' if cond) }.reject{ |k,v| v.nil? }
From Ruby 1.9+, if you want to build a hash based on conditionals you can use tap, which is my new favourite thing. This breaks it onto multiple lines but is more readable IMHO:
{}.tap do |my_hash|
my_hash[:a] = 'a'
my_hash[:b] = 'b' if condition
end
>= Ruby 2.4:
{a: 'asd', b: nil}.compact
=> {:a=>"asd"}
Interested in seeing other answers, but this is the best I can think up of for a one-liner (I'm also notoriously bad at one-liners :P)
{:a => 'a'}.merge( condition ? {:b => 'b'} : {} )
There's a lot of clever solutions in here, but IMO the simplest and therefore best approach is
hash = { a: 'a', b: 'b' }
hash[:c] = 'c' if condition
It goes against the OP's request of doing it in two lines, but really so do the other answers that only appear to be one-liners. Let's face it, this is the most trivial solution and it's easy to read.
In Ruby 2.0 there is a double-splat operator (**) for hashes (and keyword parameters) by analogy to the old splat operator (*) for arrays (and positional parameters). So you could say:
{a: 'b', **(condition ? {b: 'b'} : {})}
Hash[:a, 'a', *([:b, 'b'] if condition1), *([:c, 'c'] if condition2)]
This relies on the fact that *nil expands to vacuity in ruby 1.9. In ruby 1.8, you might need to do:
Hash[:a, 'a', *(condition1 ? [:b, 'b'] : []), *(condition2 ? [:c, 'c'] : [])]
or
Hash[:a, 'a', *([:b, 'b'] if condition1).to_a, *([:c, 'c'] if condition2).to_a]
If you have multiple conditions and logic that others will need to understand later then I suggest this is not a good candidate for a 1 liner. It would make more sense to properly create your hash based on the required logic.
This one is nice for multiple conditionals.
(
hash = {:a => 'a'}.tap {|h|
h.store( *[(:b if condition_b), 'b'] )
h.store( *[(:c if condition_c), 'c'] )
}
).delete(nil)
Note that I chose nil as the "garbage" key, which gets deleted when you're done. If you ever need to store a real value with a nil key, just change the store conditionals to something like:
(condition_b ? :b : garbage_key)
then delete(garbage_key) at the end.
This solution will also keep existing nil values intact, e.g. if you had :a => nil in the original hash, it won't be deleted.
My one-liner solution:
{:a => 'a'}.tap { |h| h.merge!(:b => 'b') if condition }
hash, hash_new = {:a => ['a', true], :b => ['b', false]}, {}
hash.each_pair{|k,v| hash_new[k] = v[1] ? v : nil }
puts hash_new
eval("{:a => 'a' #{', :b => \'b\'' if condition }}")
or even
eval("{#{[":a => 'a'", (":b=>'b'" if ax)].compact.join(',')}}")
for more simple add conditions
I am learning Ruby & Perl has this very convenient module called Data::Dumper, which allows you to recursively analyze a data structure (like hash) & allow you to print it. This is very useful while debugging. Is there some thing similar for Ruby?
Look into pp
example:
require 'pp'
x = { :a => [1,2,3, {:foo => bar}]}
pp x
there is also the inspect method which also works quite nicely
x = { :a => [1,2,3, {:foo => bar}]}
puts x.inspect
I normally use a YAML dump if I need to quickly check something.
In irb the syntax is simply y obj_to_inspect. In a normal Ruby app, you may need to add a require 'YAML' to the file, not sure.
Here is an example in irb:
>> my_hash = {:array => [0,2,5,6], :sub_hash => {:a => 1, :b => 2}, :visible => true}
=> {:sub_hash=>{:b=>2, :a=>1}, :visible=>true, :array=>[0, 2, 5, 6]}
>> y my_hash # <----- THE IMPORTANT LINE
---
:sub_hash:
:b: 2
:a: 1
:visible: true
:array:
- 0
- 2
- 5
- 6
=> nil
>>
The final => nil just means the method didn't return anything. It has nothing to do with your data structure.
you can use Marshal, amarshal, YAML