Mongoid: add readonly in hash fields - ruby

Readonly in field of type Hash in MongoId not work property
class Model
include Mongoid::Document
field :data, type: Hash
attr_readonly :data
end
This code is ok:
Model.create!(data: { foo: 'bar' })
puts Model.last.data # => { foo: 'bar' }
Model.update!(data: { baz: 'bar' }) # => raise exception
But when edit the hash directly the readonly not work:
model_instance = Model.last
model_instance.data # => { foo: 'bar' }
model_instance.data[:baz] = 'bar'
model_instance.update!
model_instance.reload
model_instance.data # => { foo: 'bar', baz: 'bar' } # problem, the values are saves in db.
who i could put readonly in hash fields?

Related

Ruby comparing values in hashes and returning the same structure

I have 2 hashes like so:
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
...
:value_x => "some_x_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "some_other_value",
:different_3 => "a_new_value",
...
:different_x => "some_x_value"
}
}
I find the common values in the :values key of both the hashes like this :
same_values = hash[:values].values & compared_hash[:values].values
And once I get it, I want to return a new hash similar to 'stored_hash', but with the :values of it containing the same_values I found before.
For example, if both hashes have "some_value", "some_other_value", "a_new_value", my new hash should look like :
new_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value"
}
}
This should work for you :
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
:value_x => "some_a_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "some_other_value",
:different_3 => "a_new_value",
:different_x => "some_b_value"
}
}
common_values = compare_hash[:values].values & stored_hash[:values].values
new_hash = stored_hash.dup
new_hash[:values] = {}
common_values.each_with_index do |value, index |
new_hash[:values]["value_#{index+1}".to_sym] = value
end
new_hash
# => new_hash = {
# :name => "hash_1",
# :version => "1.0",
# :values => {
# :value_1 => "some_value",
# :value_2 => "some_other_value",
# :value_3 => "a_new_value"
# }
# }
This is one way you could obtain your desired result.
Code
require 'set'
def make_new_hash(stored_hash, compare_hash)
new_hash = stored_hash.dup
compare_values =
(stored_hash[:values].values & compare_hash[:values].values).to_set
values_hash = new_hash[:values]
keys_to_keep = values_hash.keys.select { |k|
compare_values.include?(values_hash[k])}
new_hash[:values] =
Hash[keys_to_keep.zip(values_hash.values_at(*keys_to_keep))]
new_hash
end
Example
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
:value_x => "some_x_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "a_new_value",
:different_3 => "some_strange_value",
:different_x => "some_x_value"
}
}
Note that I've made a small change in stored_hash from that given in the question.
make_new_hash(stored_hash, compare_hash)
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
Explanation
Create a copy of stored_hash:
new_hash = stored_hash.dup
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value",
# :value_2=>"some_other_value",
# :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
We only want to keep a key k of new_hash[:values] if new_hash[:values][k] is one of the values in both the hash stored_hash[:values] and the hash compare_hash[:values], so we obtain those values:
compare_values =
(stored_hash[:values].values & compare_hash[:values].values).to_set
# => #<Set: {"some_value", "a_new_value", "some_x_value"}>
I chose to save them in a set both to speed lookup and to obtain unique values. They could alternatively be saved to array like so:
(stored_hash[:values].values & compare_hash[:values].values).uniq
#=> ["some_value", "a_new_value", "some_x_value"]
The code that follows is the same if an array is used rather than a set.
To simpify, let's create a variable:
values_hash = new_hash[:values]
#=> {:value_1=>"some_value", :value_2=>"some_other_value",
# :value_3=>"a_new_value", :value_x=>"some_x_value"}
Next determine the keys of New_hash[:values] we wish to keep:
keys_to_keep = values_hash.keys.select { |k|
compare_values.include?(values_hash[k])}
#=> [:value_1, :value_3, :value_x]
All keys other than :value_2 are kept. That key is not kept because comparative_hash[:values] does not have a value "some_other_value".
We can now construct the updated value of new_hash[:values]:
new_hash[:values] =
Hash[keys_to_keep.zip(values_hash.values_at(*keys_to_keep))]
#=> {:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}
Lastly, we return new_hash:
new_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
Let's confirm that stored_hash will not be changed when a value of new_hash[:values] is changed:
new_hash[:values][:value_1] = 'cat'
new_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"cat", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
stored_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_2=>"some_other_value",
# :value_3=>"a_new_value", :value_x=>"some_x_value"}}

Dynamic has_many queries using Ruby Mongoid and a array of hashes ex: user.send(:projects, "where(id: 1)")

Hello I am trying my hand at some meta programming and am stuck. I would like to get has many associations via an array of hashes with names and maybe id's
# what I would like to get is
user.projects.find(project.id).comments
# and or
user.projects.find(project.id).comments.find(comment.id)
# using the array of hashes called a_h -> array_of_hashes
# I am stuck and am looking for some guidance.
# gem install mongoid
require 'mongoid'
begin
Mongoid.load!("mongoid.yml", :development)
rescue
Mongoid.load!("config/mongoid.yml", :development)
end
class User
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
has_many :projects
end
class Project
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
belongs_to :user
has_many :comments
end
class Comment
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
belongs_to :project
end
user = User.create( name: :user )
project = user.projects.create( name: :project )
comment = project.comments.create( name: :comment )
comment = project.comments.create( name: :comment )
comment = project.comments.create( name: :comment )
comment = project.comments.create( name: :comment )
a_h = [
{ name: 'users', id: user.id, position: 0 },
{ name: 'projects', id: project.id, position: 1 },
{ name: 'comments', id: nil, position: 2 },
]
# or comment with id
a_h = [
{ name: 'users', id: user.id, position: 0 },
{ name: 'projects', id: project.id, position: 1 },
{ name: 'comments', id: comment.id, position: 2 },
]
# this works but would like to be able to itterate over projects a_h[1] and comments a_h[2]
user.send( a_h[1][ :name ].to_sym, "find( '#{a_h[1][ :id ]}' )" )
scope = User.scoped( {} )
scope = scope.scoped.where( id: user.id) # .last but doesn't work with projects
scope = scope.projects.where( id: project.id)
# what I would like to get is
user.projects.find(project.id).comments
# and or
user.projects.find(project.id).comments.find(comment.id)
user.send( a_h[1..-1].map { |h|
[ h[ :name ], "find( '#{ h[ :id ] }' )" ] if h[ :id ].present?
h[ :name ] if h[ :id ].blank?
} )
a_h[1..-1].map { |h| [ h[ :name ], "find( '#{ h[ :id ] }' )" ] }.flatten
a_h[1..-1].map { |h| h[ :name ], "find( '#{ h[ :id ] }' )" } # doesn't work without array
res = user
a_h[1..-1].each do |h|
res = res.send(h[:name]).send('find', h[:id]) if h[:id]
end
puts res

Complex map by objects property in ruby

I'm trying to create different kind of structure from the array I currently have in my code.
I've got an array of objects (active directory objects) so lets say i got a from db :
a = [o1,o2,o3,o4,o5]
My object has property source_id and name which are the relevant properties.
I want to create structure like this (I want hash) from the data I have in my array :
objects = Hash.new { |hash, key| hash[key] = [] }
And this would be one example of how to put the data inside new structure:
a.each do |ob|
objects[ob.source_id] << {
:new => '',
:name => {:unformated => ob.name, :formatted => ob.format(:name)}
}
end
I'm trying to replicate the same structure and it's not working out in my case :
a.group_by(&:source_id).map do |k,v|
{
k=> {
{
:new => '',
:name => {:unformated => v.name, :formatted => ob.format(:name)}
}
}
}
end.reduce(:merge)
This is the error I get :
! #<NoMethodError: undefined method `name' for #<Array:0xae542b4>>
With group_by your values in the a.group_by(&:source_id).map are going to be arrays of elements sharing the same source_id and not individual elements.
The following code may do what you wish:
a.group_by(&:source_id).map do |k,v|
{
k => v.map do |e|
{
:new => '',
:name => {:unformated => e.name, :formatted => e.format(:name)}
}
end
}
end.reduce(:merge)
Heres a one liner to do the same
new_hash = a.each_with_object({}) { |o, hash| hash[o.source_id] = {:new => '', :unformatted => o.name, :formatted => o.format(:name)} }
This does require ruby 1.9.3 though

Add to existing hash inside of a class ruby

I am trying to create a hash that has a data structure like so:
hash = {
:a => { :a1 => "x", :a2: => "x", :a3 => "x" },
:b => { :b1 => "x", :b2: => "x", :b3 => "x" },
}
Inside of a class function. I am rather new to OO, so maybe I'm not understanding the variable scoping correctly.
Here is my code:
class Foo
# Control class args
attr_accessor :site, :dir
# Initiate our class variables
def initialize(site,dir)
#site = site
#dir = dir
##records = {}
#records = Hash.new { |h, k| h[k] = Hash.new }
end
def grab_from_it
line = %x[tail -1 #{#dir}/#{#site}/log].split(" ")
time = line[0, 5].join(" ")
rc = line[6]
host = line[8]
ip = line[10]
file = line[12]
#records = { "#{file}" => { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" } }
end
end
Main body:
foo = Foo.new(site,dir)
foo.grab_from_it
pp foo
sleep(10)
foo.grab_from_it
pp foo
It works and successfully creates a hash with my desired structure, but when I run again, it overwrites the existing hash. I want it to keep adding to it, so I can create a "running tab".
Replace the following line
#records = { "#{file}" => { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" } }
with
#records["#{file}"] = { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" }
Every time you call #records = {} the instance variable points to a new hash. Thus the initialization code in initialize has no effect. Rather than replace the initialized hash with a new one, you should add new entry to the existing hash, using the []= instance method of Hash.
BTW, you can use variable to refer to the string rather than creating a new one using string interpolation "#{variable}".
#records[file] = { :time => time, :rc => rc, :host => host, :ip => ip }
If you want the UPDATE behavior for both the first and the second layers of the hash, you can take a look at the Hash#update method.
#records[file].update({ :time => time, :rc => rc, :host => host, :ip => ip })

How to check if hash cookie value is set?

Normally I have:
cookies[:location] = { :value => { :city => 'foo', :country => 'bar' } }
However, sometimes :country is not set. When I do:
cookies[:location][:country].present?
to check if country has been set, it returns an error:
[:country] is not a symbol
How would I check to see if a country is set in cookie[:location] if I cannot do it this way?
You cookies hash is nested differently than you are expecting: :value is nested under :location, :city and :country are nested under :value. Here is some irb output to get you started:
[~]$ irb
>> cookies={}
=> {}
>> cookies[:location] = { :value => { :city => 'foo', :country => 'bar' } }
=> {:value=>{:city=>"foo", :country=>"bar"}}
>> cookies[:location][:value][:country]
=> "bar"
>> cookies.to_s
=> "{:location=>{:value=>{:city=>\"foo\", :country=>\"bar\"}}}"
If you nest the hash like below, you will get your expected behavior:
>> cookies[:location] = { :city => 'foo', :country => 'bar' }
=> {:city=>"foo", :country=>"bar"}
>> cookies[:location][:country]
=> "bar"

Resources