When we try to deserialize a Model from our database we always receive a YAML object. For that we added the following code in the environment.rb:
YAML.add_domain_type("ActiveRecord,2007", "") do |type, val|
klass = type.split(":").last.constantize
YAML.object_maker(klass, val)
end
class ActiveRecord::Base
def to_yaml_type
"!ActiveRecord,2007/#{self.class}"
end
end
class ActiveRecord::Base
def to_yaml_properties
['#attributes']
end
end
This works! But only once, when I refresh the screen I always undefined method ... for YAML. It seems like my code isn't executed anymore...
Can anyone help?
Thnx!
it's not a good idea to serialize a full active record object. The object might change in the meantime and, when you load it, you might find yourself working with a stale object.
be sure the class definition of the object you are deserializing is loaded before the object is deserialized. Normally, you won't need to require the class because it's automatically loaded by Ruby when you try to use it. This doesn't happen when you deserialize an object.
Related
I have special method in array.rb, which I want to add to array's instance objects.
Basically, it iterates through collection of new records and saves it in one transaction.
##items.list_transaction
in controller.
include ActiveRecord
class Array
def list_transaction
errors=[]
ActiveRecord::Base.transaction do
self.each do |list_item|
unless list_item.save #&& yield(list_item).save
errors << list_item.errors
end
end
end
if errors.any?
errors
else
true
end
end
end
What I noticed, as this code is not part of controller/model, it does not handle associations.
For example, If I put exact same code inside of controller, on list_item.save it will also validate associated with list_item account, but as I place it in separate file, I need to add this commented part: yield(list_item).save where block refers to list_item.account.save, or Rails just skips this.
Same thing with errors.
Having this code as part of controller code, I would get list_item.errors contain list_item.account.errors, but that doesn't work in separate file.
I tried also
include ActiveRecord:Association
both inside and outside Array class (not sure if this matters), but it doesn't work out.
ActiveModel::Model is what you're looking for. It has already included ActiveModel::Validations.
But extending Array class with ActiveModel feels horribly wrong.
I have an instance variable in an active record class called hash_value. It's a serialized hash.
I want to display the hash as XML. Is it right to call hash_value.to_xml? Many nodes are numbers, and XML tags are not allowed to be only number (or start with a number).
I want to override the to_xml method of hash_value. I don't want to override on all hashes, just the hash that's in this record.
class ProductVersion < ActiveRecord::base
serialize :hash_value, Hash
def hash_value.to_xml
end
end
I tried the answer here redefining a single ruby method on a single instance with a lambda
but it doesn't seem to be applicable. I suspect because when I load the record, it creates a new hash_value object and thus the singleton adjustment on the original is moot.
Any thoughts would be great.
I know I could write a function hash_value_to_xml, but I'd rather avoid doing something like that.
Thanks to the first comment, I came up with a solution. Not a good one, but one that works. I'd love to see if there's a better way, because this one smells a bit.
class MyHash < Hash
def to_xml
1/0 #to see if it's run.
end
end
def hash_value
MyHash.new().merge( attributes['hash_value'] );
end
I would personally go for hash_value_to_xml route. But since you insist, here's an idea that might work (haven't tested that)
class ProductVersion < ActiveRecord::base
serialize :hash_value, Hash
alias_method :old_hash_value, :hash_value
def hash_value
h = old_hash_value
h.define_singleton_method(:to_xml) do
# your custom xml logic here
end
h
end
end
The idea is that you intercept value returned from hash_value and patch it on the fly.
I previously had:
serialize :params, JSON
But this would return the JSON and convert hash key symbols to strings. I want to reference the hash using symbols, as is most common when working with hashes. I feed it symbols, Rails returns strings. To avoid this, I created my own getter/setter. The setter is simple enough (JSON encode), the getter is:
def params
read_attribute(:params) || JSON.parse(read_attribute(:params).to_json).with_indifferent_access
end
I couldn't reference params directly because that would cause a loop, so I'm using read_attribute, and now my hash keys can be referenced with symbols or strings. However, this does not update the hash:
model.params.merge!(test: 'test')
puts model.params # => returns default params without merge
Which makes me think the hash is being referenced by copy.
My question is twofold. Can I extend active record JSON serialization to return indifferent access hash (or not convert symbols to strings), and still have hash work as above with merge? If not, what can I do to improve my getter so that model.params.merge! works?
I was hoping for something along the lines of (which works):
def params_merge!(hash)
write_attribute(:params, read_attribute(:params).merge(hash))
end
# usage: model.params_merge!(test: 'test')
Better yet, just get Rails to return a hash with indifferent access or not convert my symbols into strings! Appreciate any help.
use the built-in serialize method :
class Whatever < ActiveRecord::Base
serialize :params, HashWithIndifferentAccess
end
see ActiveRecord::Base docs on serialization for more info.
Posting comment as answer, per #fguillen's request... Caveat: I am not typically a Rubyist… so this may not be idiomatic or efficient. Functionally, it got me what I wanted. Seems to work in Rails 3.2 and 4.0...
In application_helper.rb:
module ApplicationHelper
class JSONWithIndifferentAccess
def self.load(str)
obj = HashWithIndifferentAccess.new(JSON.load(str))
#...or simply: obj = JSON.load(str, nil, symbolize_names:true)
obj.freeze #i also want it set all or nothing, not piecemeal; ymmv
obj
end
def self.dump(obj)
JSON.dump(obj)
end
end
end
In my model, I have a field called rule_spec, serialized into a text field:
serialize :rule_spec, ApplicationHelper::JSONWithIndifferentAccess
Ultimately, I realized I just wanted symbols, not indifferent access, but by tweaking the load method you can get either behavior.
Using HashWithIndifferentAccess is great, but it still acts like a Hash, and it can only serialize as YAML in the database.
My preference, using Postgres 9.3 and higher, is to use the json column type in Postgres. This means that when the table is read, ActiveRecord will get a Hash directly from Postgres.
create_table "gadgets" do |t|
t.json "info"
end
ActiveRecord serialize requires that you provide it a single class that is both responsible for reading/writing the data and serializing/deserializing it.
So you can create an object that does the job by inheriting from HashWithIndifferentAccess, or my preference, Hashie::Mash. Then you implement the serialization as the dump and load class methods.
class HashieMashStoredAsJson < Hashie::Mash
def self.dump(obj)
ActiveSupport::JSON.encode(obj.to_h)
end
def self.load(raw_hash)
new(raw_hash || {})
end
end
In your model, you can specify this class for serialization.
class Gadget < ActiveRecord::Base
serialize :info, HashieMashStoredAsJson
# This allows the field to be set as a Hash or anything compatible with it.
def info=(new_value)
self[:info] = HashieMashStoredAsJson.new new_value
end
end
If you don't use the json column type in Postgres, the implementation changes slightly
Full code and documentation here: using a JSON column type and using a string column type.
I ended up using a variation on bimsapi's solution that you can use not only with simple un-nested JSON but any JSON.
Once this is loaded...
module JsonHelper
class JsonWithIndifferentAccess
def self.load(str)
self.indifferent_access JSON.load(str)
end
def self.dump(obj)
JSON.dump(obj)
end
private
def self.indifferent_access(obj)
if obj.is_a? Array
obj.map!{|o| self.indifferent_access(o)}
elsif obj.is_a? Hash
obj.with_indifferent_access
else
obj
end
end
end
end
then instead of calling
JSON.load(http_response)
you just call
JsonHelper::JsonWithIndifferentAccess.load(http_response)
Does the same thing but all the nested hashes are indifferent access.
Should serve you well but think a little before making it your default approach for all parsing as massive JSON payloads will add significant ruby operations on top of the native JSON parser which is optimised in C and more fully designed for performance.
I have a simple class:
class User
include Mongoid::Document
field :name
end
And I would like to reopen it to add a Mongoid callback:
class User
before_create :do_this
def do_this
# do it...
end
end
Unfortunately I got the error: undefined method 'before_create' for User:Class
Any idea how to do this ? Should I use a mixin pattern instead of re-opening ?
UPDATE: I can't change the original class definition, since it's in a shared library. And the load order is tricky, because it's in Rails. The original class is in a file loaded in autoload_path. Where should I reopen it ? And I would rather use a module rather than reopening, but I'm not sure it's possible to include my module "from the outside" !
UPDATE 2: You are right, it's just a load order problem. So now my question becomes: Since Rails' autoload is lazy, how can I force Rails to load my reopening file after it loads the original class file ? :)
Your code above worked for me in the console. I suspect the second class declaration is being loaded first. You might try printing out a message immediately inside each class declaration e.g.
class User
puts "First"
...
end
...
class User
puts "Second"
...
end
and verifying that they load in the correct order.
Also, if you do have access to the first class declaration, you might use a mixin if possible, as it keeps everything for the User class in a single location.
UPDATE: Can you first load/require the shared User class to ensure it is loaded? That is:
require 'app/models/user'
class User
before_create :do_something
def do_something
...
end
end
I am using MongoMapper 0.9.1 in Rails 3.0.9 and it throws the following error, when I try to save an object of a custom class into the DB:
BSON::InvalidDocument (Cannot serialize an object of class Signature into BSON.)
My application will enable users to sign documents and these signatures should be saved in a custom class. I simply declared the Signature-class before the Doc-class which is going to store it:
class Signature
#value
#date
#user
def self.to_mongo(value)
value.to_a
end
def self.from_mongo(value)
Signature.new(value || [])
end
end
class Doc
# ...
No matter if I comment out the to_mongo or from_mongo methods, it always throws the exception quoted above when I want to test it by calling it from the controller via
doc = Doc.new {:signature => Signature.new}
I have no idea why it won't work in my case. If anyone of you has got an idea it would be awesome if you help me. Thank you very much in advance!
Kind regards,
Sebastian
Your key needs to be explicitly declared as the Signature type:
class Doc
include MongoMapper::Document
key :signature, Signature
end