I'm using hstore on one of my models to store a hash with float values. I need to periodically access and perform calculations the values in the store.
My current solution for working with these values is to convert them to float everywhere I access them, which is very un-DRY. Is there a way for me to modify the store_accessor method to automatically covert any values that I read from the store to floats?
I know I can do this to specific keys by writing a function for them as described in the docs but I have a very long list of possible keys, so this way is also not very DRY.
I havent used hstore before, but studied the documentation you provided in the question. Well, hstore looks really useful (too bad it is available only in postgreSQL DB, but definitely learned something new).
From reading the documentation,
NOTE - If you are using PostgreSQL specific columns like hstore or
json there is no need for the serialization provided by store. Simply
use store_accessor instead to generate the accessor methods. Be aware
that these columns use a string keyed hash and do not allow access
using a symbol.
Also:
The stored attribute names can be retrieved using stored_attributes.
User.stored_attributes[:settings] # [:color, :homepage]
So, we will be getting the seperate fields in a stored_attribute using stored_attributes hash and will be getting the fields we declared on the stored_attribute as an array. Now, we need to override the accessors for all the elements in the array. We can use define_method to dynamically override the accessors. This is what I came up with.
class WorkingBill < ActiveRecord::Base
store_accessor :prices, :cost_price, :selling_price
stored_attributes[:prices].each do |price_column_name|
define_method("#{key}=") do |value|
super(value.to_f)
end
define_method(key) do
super.to_f
end
end
end
I havent tested this code as I dont have a development environment right now. Also, I have a doubt whether super call will work inside define_method. So, just try it out. I will also test it once I get a development environment.
Also, as #Vakily commented, the gem https://github.com/jalkoby/active_store_accessor seems to do this better and also handles boolean, integer fields also.
Related
With Active Record, we can access a value like
method access
user = User.find(1)
user.name #=> 'John'
or
hash access
user[:name] #=> 'John'
I just wonder when to use which, or is there any best practice out there?
Personally I'd prefer method access because I feel that is more like ruby way. However when I see code by others, I face the hash access.
Rails convention is to use ActiveRecord::AttributeMethods::Read#read_attribute (dot notation), rather than its alias ActiveRecord::AttributeMethods#[], which:
Returns the value of the attribute identified by attr_name after it
has been typecast (for example, “2004-12-12” in a date column is cast
to a date object, like Date.new(2004, 12, 12)). It raises
ActiveModel::MissingAttributeError if the identified attribute is
missing.
I would strongly advise against using bracket notation as it breaks the inheritance hierarchy of method calls and makes refactoring harder.
If my model has an attribute name, and I decide I want to enhance the name every time someone reads it, a very idiomatic way to do that would be:
def name
"Awesome #{super}!"
end
Any place in my app that uses the method version would work fine, any place that uses the [] notation would return raw database data. I could overwrite the [] but then I would need special conditions checking for specific attributes. The whole thing would be a nightmare.
Another scenario, let's say I had an attribute that used to be stored in the database, but after a while decide that it should be computed on the fly, and end up dropping the database column. With the method version all I would need to do is add methods to my model. With the [] notation the process would be much much harder.
Also [] provides an insignificant performance improvement so though it looks like it's "closer" to the raw data it really isn't.
I'm using Mongoid in a Rails project (both 4.0.x), and I've got a document with a hash field that stores some schema-less data.
class Thing
field :name, type: String
field :mass, type: Integer
field :info, type: Hash
end
With this setup, I can query for things, say, that have a key :endDate like so:
Thing.where("info.endDate"=>{'$exists'=>true})
And that's all nice and handy. Using a hash field for this :info field is nice because what I want to store doesn't have a fixed schema and varies from one thing to another.
Ok, but, I can't use the same dot syntax to $set key/value pairs in the :info hash. [1]
thing.set("info.endDate"=>Time.now)
Raises a Mongoid::Errors::UnknownAttribute error.
It tells me I'd have to include Mongoid::Attributes::Dynamic in my model to do this, but that doesn't seem right to me. The point of the hash field type seems to be to allow you to work with data that doesn't have a fixed schema. It doesn't seem like I should have to include a special "dynamic attributes" module to use hash fields.
So right now, I'm updating values using regular old [] syntax, and then calling save on the model, like so:
thing.info[:endDate] = Time.now
thing.save
But a lot of the time it happens that it would be nicer to just $set the value. Is there some other syntax for setting hash field values? Am I wrong about the above error message and Dynamic Attributes being wrong-headed? Am I stuck doing two step updates to hash fields for now?
[1] admittedly, I've recently migrated from mongomapper, and so my expectations of this syntax are partly set by having been able to do this previously in mongomapper.
The thing with Hash field is, it can be dynamic as much as you want. Therefore to prevent polluting your DB schema with unintended fields caused by bugs in your code this functionality is disabled by default.
No you are not stuck using 2-step updates for your hashes at all!
[],[]= are the shortcuts for read_attribute() and write_attribute() and should be used if you don't include Mongoid::Attributes::Dynamic. If you try to use $set without enabling dynamic attributes you will get a no-method error because it does not see your dynamic attributes as defined attributes.
If you'll read the source of Mongoid::Attributes::Dynamic then you'd find that this is required to add the dynamic attributes functionality.
To update the values by including Mongoid::Attributes::Dynamic you need to follow these steps:
thing = Thing.first
thing.set("info.endDate" => Time.now)
thing.reload # This will update the current variable
Otherwise if you need you can easily skip this and do the value update by 2-step method
I hope this sheds some light over your query.
Source:
Rails mongoid dynamic fields - no method error
Dynamic attributes with Rails and Mongoid
I think you pass parameter in wrong way. Replace arrow symbol with comma
You can change to this and it will work
thing.set("info.endDate", Time.now)
In Mongoid 3.0.21, how to get all model's attributes as a plain Ruby Hash?
Calling either #attributes or #raw_attributes returns Moped::BSON::Document. While it actually extends Hash, several hash method does not work as expected. Particularly #except returns unmodified self, not hash with given keys stripped off.
Update: Moped::BSON::Document properly inherits behavior of Hash. I was trying to name attributes with symbols, not strings, that's why #except didn't work. Shortly: do except('pictures'), not except(:pictures).
Hash[e.attributes]
where e is your model instance
I apologize for bumping something so old, but I wanted to leave this here for myself and all the future people who run into this same issue. I am using the Mongoid ORM for Rails, which uses Moped internally for its interaction with MongoDB.
This gem has now saved me hours and hours of manually converting things to Hash or HashWithIndifferentAccess: https://github.com/mindscratch/mongoid-indifferent-access.
Essentially it seems to have some sort of pre-return hook that automatically converts all documents coming from MongoDB to type HashWithIndifferentAccess.
Not looking for points on this. Just wanted to leave this here because it is the top Google result for this issue and it saved me from going insane.
Is there any gem/plugin for ruby on rails which gives the ability to define custom fields in a model at runtime with no need to change the model itself for every different field.
I'm looking for something like Redmine acts_as_customizable plugin which is packaged as a gem usable in the rails way, i.e.
gem 'gemname'
rails g something
rails db:migrate
class Model < ActiveRecord::Base
acts_as_something
end
Here are the CustomField and the CustomValue classes used in Redmine.
Edit:
Since my question is not clear I add a brief use case which explains my need better:
I want users to be able to design their own forms, and collect data
submitted on those forms. An important decision is the design of how
these custom dynamic records are stored and accessed.
Taken from here, in this article approach the problem with different ideas, but they all have drawbacks. For this reason I'm asking if the issue has been approached in some gem with no need to rethink the whole problem.
I'm not aware of a gem that does this, but serialize works quite well and it's a built-in. You get a NoSQL-ish document store backed by JSON/YAML.
If you allow user to create a custom form, you can pass nested arrays et cetera directly into the attribute. However, if you need to validate the structure, you're on your own.
I'm afraid it could be tricky and complicated to do it in ActiveRecoand (generally in standard relational database). Take a look at http://mongoid.org/docs/documents/dynamic.html - this mechanism is using nosql feature.
You can also may try the following trick:
1/ Serialize a hash with your custom fields in the database column, for example { :foo => 'bar', :fiz => 'biz' }
2/ After load a record from database do some metaprogramming and define corresponding methods on the record's singleton class, for instance (assume that custom fields are stored and serialized in custom_fields column):
after_initialize :define_custom_methods
# ..or other the most convinient callback
def define_custom_methods
# this trick will open record's singleton class
singleton_class = (class << self; self; end)
# iterate through custom values and define dynamic methods
custom_fields.each_with_key do |key, value|
singleton_class.send(:define_method, key) do
value
end
end
end
Since rails 3.2 you can use store method. Just include following in your model:
store :properties, accessors: [:property1, :property2, :property3...]
You only need to change your model once (to add properties field to db table). You can add more properties later without altering the schema.
The way this works is by serializing properties hash into YAML and saving it into database. It it suitable for most cases, but not if you'd like to use these values in db queries later.
I don't know a gem, but this can be accomplished be creating a table called custom_fields with a name column and possibly a datatype column if you wanted to restrict fields by datatype.
Then you create a join table for a custom field to your desired table and a value and do whatever validations you want.
I've looked everywhere for an elegant solution. The essential problem seems to be that ActiveRecord attributes that map to database columns are handled completely differently in ActiveRecord::Base than attr_accessor methods.
I would like to do something like:
model.attribute_names.each do |name|
# do stuff
end
in a way that also includes attr_accessor fields, but not any other instance methods. I know this in not built-in, but what is the most elegant way to do it?
You can't really solve this. You can approximate a hack, but it's not something that will ever work nicely.
model.attribute_names should get you all the ActiveRecord ones, but the attr_accessor fields are not fields. They are just ordinary ruby methods, and the only way to get them is with model.instance_methods.
Idea 1
You could do model.attribute_names + model.instance_methods, but then you'd have to filter out all your other normal ruby methods initialize, save, etc which would be impractical.
To help filter the instance_methods you could match them up against model.instance_variables (you'd have to account for the # sign in the instance variables manually), but the problem with this is that instance variables don't actually exist at all until they are first assigned.
Idea 2
In your environment.rb, before anything else ever gets loaded, define your own self.attr_accessor in ActiveRecord::Base. This could then wrap the underlying attr_accessor but also save the attribute names to a private list. Then you'd be able to pull out of this list later on. However I'd advise against this... monkey-patching core language facilities like attr_accessor is guaranteed to bring you a lot of pain.
Idea 3
Define your own custom_attr_accessor in ActiveRecord::Base, which does the same thing as Idea 2, and use it in your code where you want to be able to retrieve the attribute names. This would be safe as you won't be clobbering the built-in attr_accessor method any more, but you'll have to change all your code to use custom_attr_accessor where neccessary
I guess in summary, what are you trying to do that needs to know about all the attr_accessor fields? Try look at your problem from a different angle if you can.
I came here looking to do the same thing, and found out it was the wrong approach altogether thanks to Orion's answer.
Incase anyone else's use case is similar to mine, here's my solution. I was using attr_accessor to add extra properties to the models after querying them from ActiveRecord. I then wanted to output the results as JSON etc.
A better solution is to first convert the Models from ActiveRecord into regular hashes, and then add the attr_accessor properties as regular hash keys.
Example:
model_hash = model_from_activerecord.attributes.to_options
model_hash[:key] = value
The solution I came up with for myself builds upon Orion Edwards' answer.
The code:
klass_attributes = klass.column_names + klass.instance_methods(false).
map(&:to_s).keep_if{|a| a.include?('=')}.map{|a| a.sub('=', '')}
The breakdown:
klass.instance_methods(false) brings back only instance methods, and not inherited methods.
map(&:to_s) converts the array of symbols into an array of strings so we can use the include? method. Also needed to merge with array of strings returned by column_names.
keep_if{|a| a.include?('=')} will remove all strings within the array that do not have an equals sign. This was important, since I noticed that attr_accessor attributes all had '='. Ex: 'app_url' vs 'login_url='
map{|a| a.sub('=', '')} is the final piece that then removes the '=' from each string in the array.
You end up with an array of strings that represent attributes, including attr_accessor attributes.