I'm having problems adding a new dynamic attribute to mongoid document with a value of nil
class User
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Attributes::Dynamic
end
u = User.find(id)
u.write_attribute(:bar, nil)
puts u.bar
# => nil
u.save!
u = User.find(id)
puts u.bar
# => NoMethodError (undefined method `bar for #<User:0x00007fe87a97cfd8>)
Adding a new attribute with any other value than nil works fine.
u = User.find(id)
u.write_attribute(:foo, "a")
puts u.foo
# => "a"
u.save!
u = User.find(id)
puts u.foo
# => "a"
Is there something I'm missing here?
You can use #set to set attribute values to nil. See https://docs.mongodb.com/mongoid/master/tutorials/mongoid-persistence/#atomic.
irb(main):024:0> u.set(foo: nil)
D, [2020-10-05T21:31:20.167950 #18418] DEBUG -- : MONGODB | localhost:14400 req:15 conn:1:1 sconn:1088 | mongoid.update | STARTED | {"update"=>"users", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5f7bc8642c97a647f27735c6')}, "u"=>{"$set"=>{"foo"=>nil}}}], "$db"=>"mongoid", "lsid"=>{"id"=><BSON::Binary:0x7620 type=uuid data=0x606db89de7a04adf...>}}
D, [2020-10-05T21:31:20.169268 #18418] DEBUG -- : MONGODB | localhost:14400 req:15 | mongoid.update | SUCCEEDED | 0.001s
=> #<User _id: 5f7bc8642c97a647f27735c6, created_at: 2020-10-06 01:29:08.638566093 UTC, updated_at: 2020-10-06 01:29:20.656350934 UTC, bar: nil, bar1: 1, foo: nil>
Then #attributes works as expected:
irb(main):023:0> User.find(u.id).attributes
D, [2020-10-05T21:30:18.758322 #18418] DEBUG -- : MONGODB | localhost:14400 req:14 conn:1:1 sconn:1088 | mongoid.find | STARTED | {"find"=>"users", "filter"=>{"_id"=>BSON::ObjectId('5f7bc8642c97a647f27735c6'), "$and"=>[{"_id"=>BSON::ObjectId('5f7bc8642c97a647f27735c6')}]}, "$db"=>"mongoid", "lsid"=>{"id"=><BSON::Binary:0x7620 type=uuid data=0x606db89de7a04adf...>}}
D, [2020-10-05T21:30:18.759791 #18418] DEBUG -- : MONGODB | localhost:14400 req:14 | mongoid.find | SUCCEEDED | 0.001s
=> {"_id"=>BSON::ObjectId('5f7bc8642c97a647f27735c6'), "updated_at"=>2020-10-06 01:29:20.656 UTC, "created_at"=>2020-10-06 01:29:08.638 UTC, "bar1"=>1, "foo"=>nil}
If you believe write_attribute should persist attributes with nil values, you can create a ticket at https://jira.mongodb.org/browse/MONGOID describing your use case.
This is mentioned in the docs for Mongo::Attributes::Dynamic:
If the attribute does not already exist on the document, Mongoid will not provide you with the getters and setters and will enforce normal method_missing behavior. In this case you must use the other provided accessor methods: ([] and []=) or (read_attribute and write_attribute).
So you can just use user[:bar] or user.read_attribute(:bar)
Related
I create this class:
1. class Component
2.
3. getter :content, :htm_options, :options
3.
4. #html_options : Hash(Symbol | String, Symbol | String)
5. #options : Hash(Symbol | String, Symbol | String)
6. #content : String
7.
8. def initialize(c = nil, o = nil, h = nil, block = nil)
9. if false #!block.nil?
10. #html_options = o unless o.nil?
11. #options = c unless c.nil?
12. context = eval("self", block.binding)
13. #content = context.capture(&block)
14. else
15. if c.is_a?(Hash)
16. #html_options = o unless o.nil?
17. #options = c unless c.nil?
18. else
19. #html_options = h unless h.nil?
20. #options = o unless o.nil?
21. #content = c unless c.nil?
22. end
23. end
24. end
25. end
Component.new("My text")
I have an error :
in src/ui_bibz/ui/core/component.cr:11: instance variable '#options' of Component must be Hash(String | Symbol, String | Symbol), not String
#options = c unless c.nil?
I don't understand this behavior because I do not pass in the if condition. I'd like assign #htm_options, #options, #content according to several conditions.
Is it possible to declare a variable that has already been declared once?
I'm afraid the compiler is sometimes not smart enough to detect which paths are taken and which are not, when checking for types. So, in line 11, the compiler sees that you are attempting to assign a String to a variable previously defined as a Hash, and fails.
As for your question, I'm afraid it is not possible to re-declare an instance variable. If you do want #options to hold both a String or a Hash, you can declare it to be a Union of Hash and String.
Crystal is typed and this is a good thing, but for my case, i'd like to redefine my variables. My code works like a "link_to" in ruby. A component must be write in different ways. Sometimes, content will be filled by variable, sometimes by block. Initialize method will be filled by different ways. There's a way to redefined variables by macro or by an other solution?
class Component < UiBibz::Ui::Base
getter :content, :htm_options, :options
def initialize(c = nil, o = nil, h = nil, block = nil)
if !block.nil?
html_opts, opts = o, c
context = eval("self", block.binding)
#content = context.capture(&block)
else
if c.is_a?(Hash)
html_opts, opts = o, c
else
html_opts, opts, cont = h, o, c
end
end
#html_options = html_opts || {} of Symbol => Symbol | String
#options = opts || {} of Symbol => Symbol | String
#content = content
end
end
# 1 way
Component.new("My content", { :option_1 => true }, { :class => "red" })
# 2 way
Component.new({ :option_1 => true }, { :class => "red" }) do
"My content"
end
Error:
in src/ui_bibz/ui/core/component.cr: instance variable '#options' of Component must be Hash(Symbol, String | Symbol), not (Hash(Symbol, String | Symbol) | String)
#options = opts || {} of Symbol => Symbol | String
On a Rails project, I am gathering a hash with 10-15 key-value pairs, and passing it to a class (service object) for instantiation. The object properties should be set from the values in the hash except when there is no value (or nil). In this case, the property would desirably get set to a default value.
Instead of checking whether every value in the hash is not nil before creating an object, I would like to find a more efficient way of doing this.
I'm trying to use named parameters with default values. I don't know if this makes sense, but I would like to use the default value when the parameter is called with nil. I created a test for this functionality:
class Taco
def initialize(meat: "steak", cheese: true, salsa: "spicy")
#meat = meat
#cheese = cheese
#salsa = salsa
end
def assemble
"taco with: ##meat + ##cheese + ##salsa"
end
end
options1 = {:meat => "chicken", :cheese => false, :salsa => "mild"}
chickenTaco = Taco.new(options1)
puts chickenTaco.assemble
# => taco with: chicken + false + mild
options2 = {}
defaultTaco = Taco.new(options2)
puts defaultTaco.assemble
# => taco with: steak + true + spicy
options3 = {:meat => "pork", :cheese => nil, :salsa => nil}
invalidTaco = Taco.new(options3)
puts invalidTaco.assemble
# expected => taco with: pork + true + spicy
# actual => taco with: pork + +
If you want to follow a Object-Oriented approach, you could isolate your defaults in a separate method and then use Hash#merge:
class Taco
def initialize (args)
args = defaults.merge(args)
#meat = args[:meat]
#cheese = args[:cheese]
#salsa = args[:salsa]
end
def assemble
"taco with: #{#meat} + #{#cheese} + #{#salsa}"
end
def defaults
{meat: 'steak', cheese: true, salsa: 'spicy'}
end
end
Then following the suggestion by #sawa (thanks), use Rails' Hash#compact for your input hashes that have explicitly defined nil values and you will have the following output:
taco with: chicken + false + mild
taco with: steak + true + spicy
taco with: pork + true + spicy
EDIT:
If you do not want to use Rails' wonderful Hash#compact method, you can use Ruby's Array#compact method. Replacing the first line within the initialize method to:
args = defaults.merge(args.map{|k, v| [k,v] if v != nil }.compact.to_h)
Once you pass a value with a named parameter, access to the default value for that parameter is gone for that method call.
You either have to (i) assign the default value not in the method profile but in the method body as in sagarpandya82's answer, or (ii) remove the nil values before passing the arguments to the method like this using Rails' Hash#compact:
options3 = {:meat => "pork", :cheese => nil, :salsa => nil}
invalidTaco = Taco.new(options3.compact)
I don't think keyword arguments would be appropriate in your case. It seems a Hash is a better fit.
class Taco
attr_accessor :ingredients
def initialize(ingredients = {})
#ingredients = ingredients
end
def assemble
"taco with: #{ingredients[:meat]} + #{ingredients[:cheese]} + #{ingredients[:salsa]}"
end
end
You can even shorter the assemble method to list all the ingredients
def assemble
string = "taco with: " + ingredients.values.join(" + ")
end
And it will work as you'd expect
options1 = {:meat => "chicken", :cheese => false, :salsa => "mild"}
chicken_taco = Taco.new(options1)
puts chicken_taco.assemble() # output: taco with: chicken + false + mild
It is worth to mention that Ruby prefers chicken_tacos over chickenTacos.
Is it possible for a Ruby class to implement a truthiness method or are all objects beside false and nil automatically true by design?
The answer to your second question is: yes, everything else than false and nil is considered "truthy" in Ruby.
So, for example
a = 'foo' if 0
# => "foo"
a = 'foo' if []
# => "foo"
a = 'foo' if ''
# => "foo"
a = 'foo' if nil
# => nil
a = 'foo' if false
# => nil
At least in 1.9.3, you can get some control over !:
$ irb
>> class C
>> def !
>> true
>> end
>> end
=> nil
>> c = C.new
=> #<C:0x7b5b9cd6>
>> !c
=> true
>> !!c
=> false
>>
Everything except false and nil (including 0, which sometimes confuses people coming from other languages) is truthy.
I have #obj.items_per_page, which is 20 at the beginning, and I want the method below to assign value to it only if many_items is not nil:
def fetch_it_baby (many_items = nil)
#obj.items_per_page = many_items
With the code above, even if many_items is nil, #obj.items_per_page remains at 20. Why? And is that "good" coding? Shouldn't I use something like
#obj.items_per_page = many_items || #obj.items_per_page
Or is there a third way? I don't feel completely comfortable with either way.
The style I generally see looks like this:
#obj.items_per_page = many_items if many_items
This uses the inline conditional, while avoiding negative or double-negative conditions.
I suggest the following as it makes it clear that you have a default value for the assignment in case the caller did not specify many_items in the call:
def function(argument = nil)
variable = argument || 20
...
end
However, since you specified that the assignment takes places only if the value is not nil then you'll need to check for the nil value otherwise you will miss the assignment if the value was false. If you really need that case then the solution is more long-winded:
def function(argument = nil)
variable = argument.nil? ? 20 : argument
...
end
You can use &&= (in the same way as ||= is used to assign only if nil or false)
> a = 20 # => 20
> a &&= 30 # => 30
> a # => 30
> a = nil # => nil
> a &&= 30 # => nil
> a = false # => false
> a &&= 30 # => false
> a = {} # => {}
> a &&= 30 # => 30
remember though
> a = 30 # => 30
> a &&= nil # => nil
> a &&= false # => nil
> b &&= 3 # => nil
Even if many_items is nil #obj.items_per_page remains at 20
That sounds like whatever class #obj is has a custom modifier method items_per_page= that only updates the value if the new value is not nil. This is not standard Ruby. For example, given this definition:
class Demo
attr_accessor :items_per_page
end
I get this behavior:
irb(main):005:0> demo = Demo.new #=> #<Demo:0x007fb7b2060240>
irb(main):006:0> demo.items_per_page = 20 #=> 20
irb(main):007:0> demo.items_per_page #=> 20
irb(main):008:0> demo.items_per_page = nil #=> nil
irb(main):009:0> demo.items_per_page #=> nil
As for your example, I would probably write it this way:
#obj.items_per_page = many_items unless many_items.nil?
For Rails you can also use presence as described here
region = params[:state].presence || params[:country].presence || 'US'
new-alexandria's answer is my go-to, but another "third way" would be to use a ternary:
class Demo
attr_accessor :items_per_page
end
many_items = 100
#obj = Demo.new
#obj.items_per_page = 20 #=> 20
#obj.items_per_page = !many_items.nil? ? 30 : nil #=> 30
I am using Rails and I have a similar need.
You can define a method on your model:
class Gift < ApplicationRecord
def safe_set(attribute, value)
return if value.nil?
send("#{attribute}=", value)
end
end
So you can do
g = Gift.new
g.colour = 'red'
g.safe_set(:colour, nil)
g.colour -> 'red'
g.safe_set(:colour, 'green')
g.colour -> 'green'
We have one more method in rails that can help to remove values that are nil.
compact
This method can be used with an array, hash. Returns the same data removing all values that are nil
Eg :
array.compact
Further reference:
https://apidock.com/ruby/v1_9_3_392/Array/compact
If many items is a variable the if/unless versions above are the best. But if many_items is actually a method you don't want to evaluate multiple times I find the following useful.
#obj.items_per_page = many_items.presence || #obj.items_per_page
This isn't quite what you want since it won't assign empty strings or other non-nil but non-present values but most of the time I want to do this kind of thing this works for me (it does create a self-assignment when the value is nil/non-present so only use it if your setters are idempotent).
This question already has answers here:
Ruby Style: How to check whether a nested hash element exists
(16 answers)
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
I'm working a little utility written in ruby that makes extensive use of nested hashes. Currently, I'm checking access to nested hash elements as follows:
structure = { :a => { :b => 'foo' }}
# I want structure[:a][:b]
value = nil
if structure.has_key?(:a) && structure[:a].has_key?(:b) then
value = structure[:a][:b]
end
Is there a better way to do this? I'd like to be able to say:
value = structure[:a][:b]
And get nil if :a is not a key in structure, etc.
Traditionally, you really had to do something like this:
structure[:a] && structure[:a][:b]
However, Ruby 2.3 added a method Hash#dig that makes this way more graceful:
structure.dig :a, :b # nil if it misses anywhere along the way
There is a gem called ruby_dig that will back-patch this for you.
Hash and Array have a method called dig.
value = structure.dig(:a, :b)
It returns nil if the key is missing at any level.
If you are using a version of Ruby older than 2.3, you can install a gem such as ruby_dig or hash_dig_and_collect, or implement this functionality yourself:
module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end
if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end
The way I usually do this these days is:
h = Hash.new { |h,k| h[k] = {} }
This will give you a hash that creates a new hash as the entry for a missing key, but returns nil for the second level of key:
h['foo'] -> {}
h['foo']['bar'] -> nil
You can nest this to add multiple layers that can be addressed this way:
h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }
h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil
You can also chain indefinitely by using the default_proc method:
h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
h['bar'] -> {}
h['tar']['star']['par'] -> {}
The above code creates a hash whose default proc creates a new Hash with the same default proc. So, a hash created as a default value when a lookup for an unseen key occurs will have the same default behavior.
EDIT: More details
Ruby hashes allow you to control how default values are created when a lookup occurs for a new key. When specified, this behavior is encapsulated as a Proc object and is reachable via the default_proc and default_proc= methods. The default proc can also be specified by passing a block to Hash.new.
Let's break this code down a little. This is not idiomatic ruby, but it's easier to break it out into multiple lines:
1. recursive_hash = Hash.new do |h, k|
2. h[k] = Hash.new(&h.default_proc)
3. end
Line 1 declares a variable recursive_hash to be a new Hash and begins a block to be recursive_hash's default_proc. The block is passed two objects: h, which is the Hash instance the key lookup is being performed on, and k, the key being looked up.
Line 2 sets the default value in the hash to a new Hash instance. The default behavior for this hash is supplied by passing a Proc created from the default_proc of the hash the lookup is occurring in; ie, the default proc the block itself is defining.
Here's an example from an IRB session:
irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}
When the hash at recursive_hash[:foo] was created, its default_proc was supplied by recursive_hash's default_proc. This has two effects:
The default behavior for recursive_hash[:foo] is the same as recursive_hash.
The default behavior for hashes created by recursive_hash[:foo]'s default_proc will be the same as recursive_hash.
So, continuing in IRB, we get the following:
irb(main):016:0> recursive_hash[:foo][:bar]
=> {}
irb(main):017:0> recursive_hash
=> {:foo=>{:bar=>{}}}
irb(main):018:0> recursive_hash[:foo][:bar][:zap]
=> {}
irb(main):019:0> recursive_hash
=> {:foo=>{:bar=>{:zap=>{}}}}
I made rubygem for this. Try vine.
Install:
gem install vine
Usage:
hash.access("a.b.c")
I think one of the most readable solutions is using Hashie:
require 'hashie'
myhash = Hashie::Mash.new({foo: {bar: "blah" }})
myhash.foo.bar
=> "blah"
myhash.foo?
=> true
# use "underscore dot" for multi-level testing
myhash.foo_.bar?
=> true
myhash.foo_.huh_.what?
=> false
value = structure[:a][:b] rescue nil
Solution 1
I suggested this in my question before:
class NilClass; def to_hash; {} end end
Hash#to_hash is already defined, and returns self. Then you can do:
value = structure[:a].to_hash[:b]
The to_hash ensures that you get an empty hash when the previous key search fails.
Solution2
This solution is similar in spirit to mu is too short's answer in that it uses a subclass, but still somewhat different. In case there is no value for a certain key, it does not use a default value, but rather creates a value of empty hash, so that it does not have the problem of confusion in assigment that DigitalRoss's answer has, as was pointed out by mu is too short.
class NilFreeHash < Hash
def [] key; key?(key) ? super(key) : self[key] = NilFreeHash.new end
end
structure = NilFreeHash.new
structure[:a][:b] = 3
p strucrture[:a][:b] # => 3
It departs from the specification given in the question, though. When an undefined key is given, it will return an empty hash instread of nil.
p structure[:c] # => {}
If you build an instance of this NilFreeHash from the beginning and assign the key-values, it will work, but if you want to convert a hash into an instance of this class, that may be a problem.
You could just build a Hash subclass with an extra variadic method for digging all the way down with appropriate checks along the way. Something like this (with a better name of course):
class Thing < Hash
def find(*path)
path.inject(self) { |h, x| return nil if(!h.is_a?(Thing) || h[x].nil?); h[x] }
end
end
Then just use Things instead of hashes:
>> x = Thing.new
=> {}
>> x[:a] = Thing.new
=> {}
>> x[:a][:b] = 'k'
=> "k"
>> x.find(:a)
=> {:b=>"k"}
>> x.find(:a, :b)
=> "k"
>> x.find(:a, :b, :c)
=> nil
>> x.find(:a, :c, :d)
=> nil
This monkey patch function for Hash should be easiest (at least for me). It also doesn't alter structure i.e. changing nil's to {}. It would still also apply even if you're reading a tree from a raw source e.g. JSON. It also doesn't need to produce empty hash objects as it goes or parse a string. rescue nil was actually a good easy solution for me as I'm brave enough for such a low risk but I find it to essentially have a drawback with performance.
class ::Hash
def recurse(*keys)
v = self[keys.shift]
while keys.length > 0
return nil if not v.is_a? Hash
v = v[keys.shift]
end
v
end
end
Example:
> structure = { :a => { :b => 'foo' }}
=> {:a=>{:b=>"foo"}}
> structure.recurse(:a, :b)
=> "foo"
> structure.recurse(:a, :x)
=> nil
What's also good is that you can play around saved arrays with it:
> keys = [:a, :b]
=> [:a, :b]
> structure.recurse(*keys)
=> "foo"
> structure.recurse(*keys, :x1, :x2)
=> nil
The XKeys gem will read and auto-vivify-on-write nested hashes (::Hash) or hashes and arrays (::Auto, based on the key/index type) with a simple, clear, readable, and compact syntax by enhancing #[] and #[]=. The sentinel symbol :[] will push onto the end of an array.
require 'xkeys'
structure = {}.extend XKeys::Hash
structure[:a, :b] # nil
structure[:a, :b, :else => 0] # 0 (contextual default)
structure[:a] # nil, even after above
structure[:a, :b] = 'foo'
structure[:a, :b] # foo
You can use the andand gem, but I'm becoming more and more wary of it:
>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil
There is the cute but wrong way to do this. Which is to monkey-patch NilClass to add a [] method that returns nil. I say it is the wrong approach because you have no idea what other software may have made a different version, or what behavior change in a future version of Ruby can be broken by this.
A better approach is to create a new object that works a lot like nil but supports this behavior. Make this new object the default return of your hashes. And then it will just work.
Alternately you can create a simple "nested lookup" function that you pass the hash and the keys to, which traverses the hashes in order, breaking out when it can.
I would personally prefer one of the latter two approaches. Though I think it would be cute if the first was integrated into the Ruby language. (But monkey-patching is a bad idea. Don't do that. Particularly not to demonstrate what a cool hacker you are.)
Not that I would do it, but you can Monkeypatch in NilClass#[]:
> structure = { :a => { :b => 'foo' }}
#=> {:a=>{:b=>"foo"}}
> structure[:x][:y]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):2
from C:/Ruby/bin/irb:12:in `<main>'
> class NilClass; def [](*a); end; end
#=> nil
> structure[:x][:y]
#=> nil
> structure[:a][:y]
#=> nil
> structure[:a][:b]
#=> "foo"
Go with #DigitalRoss's answer. Yes, it's more typing, but that's because it's safer.
In my case, I needed a two-dimensional matrix where each cell is a list of items.
I found this technique which seems to work. It might work for the OP:
$all = Hash.new()
def $all.[](k)
v = fetch(k, nil)
return v if v
h = Hash.new()
def h.[](k2)
v = fetch(k2, nil)
return v if v
list = Array.new()
store(k2, list)
return list
end
store(k, h)
return h
end
$all['g1-a']['g2-a'] << '1'
$all['g1-a']['g2-a'] << '2'
$all['g1-a']['g2-a'] << '3'
$all['g1-a']['g2-b'] << '4'
$all['g1-b']['g2-a'] << '5'
$all['g1-b']['g2-c'] << '6'
$all.keys.each do |group1|
$all[group1].keys.each do |group2|
$all[group1][group2].each do |item|
puts "#{group1} #{group2} #{item}"
end
end
end
The output is:
$ ruby -v && ruby t.rb
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]
g1-a g2-a 1
g1-a g2-a 2
g1-a g2-a 3
g1-a g2-b 4
g1-b g2-a 5
g1-b g2-c 6
I am currently trying out this:
# --------------------------------------------------------------------
# System so that we chain methods together without worrying about nil
# values (a la Objective-c).
# Example:
# params[:foo].try?[:bar]
#
class Object
# Returns self, unless NilClass (see below)
def try?
self
end
end
class NilClass
class MethodMissingSink
include Singleton
def method_missing(meth, *args, &block)
end
end
def try?
MethodMissingSink.instance
end
end
I know the arguments against try, but it is useful when looking into things, like say, params.