I have an active record model that has a column called configuration of type text. That column is serialized with a custom class, like so:
class MyModel < ApplicationRecord
serialize :configuration, MySerializer
end
The class MySerializer has the following class methods:
def dump(configuration)
configuration.to_json if configuration
end
def load(configuration)
obj = new
obj.json_hash = JSON.parse(configuration) if configuration.present?
obj
end
This instantiates an instance of the class MySerializer with the attr accessor json_hash.
Now, here's the problem, I'm doing:
MyModel.create(configuration: {"key" => 1})
And once I do MyModel.first, i get this:
...
configuration:
#<MySerializer:0x00000007faa558
#json_hash={"json_hash"=>{"key" => 1}
I was expecting getting something like:
#json_hash = {"key" => 1}
Any idea why I'd get the repeated key json_hash inside the attr accessor #json_hash ?
Thanks!
Why do you want to use MySerializer class?
Instead you can simply use as below:
serialize :configuration, Hash
Now do,
MyModel.create(configuration: {"key" => 1})
And try
MyModel.first
Related
I want to be able to create attributes on a ActiveRecord:Base Model that are nested.
For example -
class Book < ActiveRecord::Base
attr_accessor :operator, :who
I would like :who to have further attributes like :family, :me
So finally I can access these as follows
book = Book.new
book.who.family = [1,2,3]
book.who.me = 1
I also want to know how can I define the kind of values that attributes can take so I do not have to do that at runtime.
Currently am using something like this
after_initialize do
#who = {family: [], me: nil}
end
I tried it in my console. So if you add the your attributes as attribute accessors and after_initialize set them like this.
attr_accessor :who, :operator
after_initialize do
self.who = {family: [], me: nil}
self.operator = "Minus"
end
then you can access them like this
self.who[:family] = [1,2,3,4]
or
self.operator = "Minus"
and you can simply access them like this
self.who[:family] ==> [1,2,3]
Will this help?
I have a Notifications module which have classes like 1)car 2)bike 3)Aeroplane. I have a serialized column in UserFeature model.And I have a module 'Notifications' which has list of 11 classes in it.
Notifications
1)car
2)bike
3)Aeroplane
The hash structure of the column notifications in UserFeature model must be
{:car => {:mirror => :true, :door => :true}
:bike => {:x=> :true, :x => :true}
:Aeroplane => {:p => :true, :q => :true}
}
I can access user_object.Notifications
But so as to access user_object.car and also user_object.mirror I need to write getter/setter methods { Defining getter/setter dynamically because I dont want to write getter/setter for every method and also I am unsure about the number of methods I have -> which in future may extend }
Notifications.constants.each do |notification_class|
class_methods = "Notifications::#{notification_class}".constantize.methods(false)
class_methods.each do |method|
method_name = method[0..-4].split('(')[0]
setter_getter_name = "#{notification_class.to_s.underscore}_#{method_name}"
define_method("#{setter_getter_name}=") do |value|
self.notifications = GlobalUtils.form_hash(self.notifications, "#{notification_class}".to_sym, "#{method_name}".to_sym)
self[:notifications]["#{notification_class}".to_sym][ "#{method_name}".to_sym] = value
end
define_method("#{setter_getter_name}") do
self.notifications.fetch("#{notification_class_name}".to_sym, {}).fetch("#{method_name}".to_sym)
end
end
end
But still when i try to access user_object.mirror,
undefined method for #<UserFeature000043645345>
What I am doing wrong?
I need to do this using getter/setter method only
An OpenStruct is a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values. This is accomplished by using Ruby’s metaprogramming to define methods on the class itself.
example:
require 'ostruct'
hash = { "country" => "Australia", :population => 20_000_000 }
data = OpenStruct.new(hash)
p data # -> <OpenStruct country="Australia" population=20000000>
Use Ruby OpenStruct class. It will fulfill your requirements without defining such bunch of code.
Edit1, example:
require 'ostruct'
class Aeroplane < OpenStruct; end
a = Aeroplane.new(:p => :true, :q => :true)
a.p # => true
I have a model
class Transaction < ActiveRecord::Base
end
I have a transaction_type column which is an integer.
How can I create an enumeration that I could map values to names like:
one_time = 1
monthly = 2
annually = 3
So in the db column, the values would be 1, 2 or 3.
Also, whenever I create a new instance, or save a model and the field wasn't set like:
#transaction = Transaction.new(params)
It should default to 1 (on_time).
I'm not sure how I can do this?
basically the same answer as Amit, slight variation
class TransactionType
TYPES = {
:one_time => 1,
:monthly => 2,
:annually => 3
}
# use to bind to select helpers in UI as needed
def self.options
TYPES.map { |item| [item[0], item[1].to_s.titleize] }
end
def self.default
TYPES[:one_time]
end
end
one way to control the default value
class Transaction < ActiveRecord::Base
before_create :set_default_for_type
def set_default_for_type
type = TransactionType.default unless type.present?
end
end
but - best way is to just apply the defaults on your database column and let ActiveRecord get it from there automatically
NOTE: it might also make sense to just have a TransactionType ActiveRecord object instead of above, depends on your situation, i.e.
# on Transaction with type_id:integer
belongs_to :type, class_name: "TransactionType"
You can map the values by creating a constant either in the same Transaction model or by creating a new module and place it inside that as explained by #KepaniHaole
In Transaction model, you can do it like :
class Transaction < ActiveRecord::Base
TRANSACTION_TYPES = { 'one_time' => 1, 'monthly' => 2, 'monthly' => 3 }
end
You can access these values by accessing the constant as
Transaction::TRANSACTION_TYPES['one_time'] # => 1
Transaction::TRANSACTION_TYPES['monthly'] # => 2
Transaction::TRANSACTION_TYPES['monthly'] # => 3
To add a default value to transaction_type column just create a new migration with :
def up
change_column :transactions, :transaction_type, :default => Transaction::TRANSACTION_TYPES['one_time']
end
With this, every time you create a Transaction object without passing transaction_type, the default value 1 with be stored in it.
Maybe you could try something like this? Ruby doesn't really support c-style enums..
module TransactionType
ONCE = 1
MONTHLY = 2
ANUALLY = 3
end
then you could access their values like so:
#transaction = Transaction.new(TransactionType::ONCE)
I've added this to my model:
key :name, :random_number
And I am using this callback:
before_create :create_random_number
But random_number is not getting appended to the _id using a method like this:
def create_random_number
rand(99999999999999999999)
end
This is the result that I get:
>> Product.create(name: "foo")
=> <Product _id: foo,
It turns out that you need to use after_initialize. This works for me:
key :slug
after_initialize :create_slug
def create_slug
name = self.name.gsub(' ', '-')
self.slug = "#{name}-#{rand(36**20).to_s(36)}"
end
In the database I have a field named 'body' that has an XML in it. The
method I created in the model looks like this:
def self.get_personal_data_module(person_id)
person_module = find_by_person_id(person_id)
item_module = Hpricot(person_module.body)
personal_info = Array.new
personal_info = {:studies => (item_module/"studies").inner_html,
:birth_place => (item_module/"birth_place").inner_html,
:marrital_status => (item_module/"marrital_status").inner_html}
return personal_info
end
I want the function to return an object instead of an array. So I can
use Module.studies instead of Model[:studies].
This is relatively simple; you're getting an Array because the code is building one. If you wanted to return an object, you'd do something like this:
class PersonalData
attr_accessor :studies
attr_accessor :birth_place
attr_accessor :marital_status
def initialize(studies,birth_place,marital_status)
#studies = studies
#birth_place = birth_place
#marital_status = marital_status
end
end
And your translation code would look like:
def self.get_personal_data_module(person_id)
person_module = find_by_person_id(person_id)
item_module = Hpricot(person_module.body)
personal_info = PersonalData.new((item_module/"studies").inner_html,
(item_module/"birth_place").inner_html,
(item_module/"marital_status").innner_html)
return personal_info
end
Or, if you want to avoid a model class, you could do something weird:
class Hash
def to_obj
self.inject(Object.new) do |obj, ary| # ary is [:key, "value"]
obj.instance_variable_set("##{ary[0]}", ary[1])
class << obj; self; end.instance_eval do # do this on obj's metaclass
attr_reader ary[0].to_sym # add getter method for this ivar
end
obj # return obj for next iteration
end
end
end
Then:
h = {:foo => "bar", :baz => "wibble"}
o = h.to_obj # => #<Object:0x30bf38 #foo="bar", #baz="wibble">
o.foo # => "bar"
o.baz # => "wibble"
It's like magic!
on a slightly different tack.
The idea of using a class method to do this feels wrong from an OO point of view.
You should really refactor this so that it works from an instance method.
def personal_data_module
item_module = Hpricot(body)
{
:studies => (item_module/"studies").inner_html,
:birth_place => (item_module/"birth_place").inner_html,
:marrital_status => (item_module/"marrital_status").inner_html
}
end
Then, where you need to use it, instead of doing....
Foobar.get_personal_data_module(the_id)
you would do
Foobar.find_by_person_id(the_id).personal_data_module
This looks worse, but in fact, thats a bit artificial, normally, you would be
referencing this from some other object, where in fact you would have a 'handle' on the person object, so would not have to construct it yourself.
For instance, if you have another class, where you reference person_id as a foreign key, you would have
class Organisation
belongs_to :person
end
then, where you have an organisation, you could go
organisation.person.personal_information_module
Yes, I know, that breaks demeter, so it would be better to wrap it in a delegate
class Organisation
belongs_to :person
def personal_info_module
person.personal_info_module
end
end
And then from controller code, you could just say
organisation.personal_info_module
without worrying about where it comes from at all.
This is because a 'personal_data_module' is really an attribute of that class, not something to be accessed through a class method.
But this also brings up some questions, for instance, is person_id the primary key of this table? is this a legacy situation where the primary key of the table is not called 'id'?
If this is the case, have you told ActiveRecord about this or do you have to use 'find_by_person_id' all over where you would really want to write 'find'?