Using a store with Postgres HSTORE in Rails - ruby-on-rails

I have a User Model with a database field 'remark'. I am using Postgres as Database and the 'remark' field is of type HSTORE. So 'remark' stores a hash with extra user information.
As suggested by someone I added a store to my 'User' model like this:
class User < ActiveRecord::Base
store :remark, accessors: [ :info ]
#some code
end
Now I can get the value in #user.remark['info'] by using this accessor '#user.info'. That works fine. But when I try to set and save a new value like this:
#user.info = "some info"
#user.save
I get the following error:
ActiveRecord::StatementInvalid: PG::InternalError: ERROR: Syntax error near '!' at position 4
Is it possible to use a HSTORE type databasefield this way? What am I doing wrong?

Add a custom coder to store:
class User < ActiveRecord::Base
store :remark, accessors: [ :info ], coder: HstoreCoder
#some code
end
HstoreCoder class:
class HstoreCoder
class << self
def load(hash)
hash
end
def dump(value)
value.to_hash
end
end
end
It should help.

For Rails 5 and hstore use store_accessor instead of store
store_accessor :remark, :info
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.
https://api.rubyonrails.org/classes/ActiveRecord/Store.html

Related

Using Rails 5 attributes api with hash virtual attributes

I have a hash store as follows:
store :order_details, accessors: %w{item_name, external_id, total......etc}, coder: JSON
I'm deserializing this to a ruby object using a lib class elsewhere with a couple of methods in it for validations/operations
I would like to make use of the rails 5 attributes api instead so that I could directly have:
attribute :order_details, Type::OrderDetailType.new
(plus it would make it easier to add validations to each field in my hash)
I've seen examples online for using rails5's attributes api for simple virtual attributes(strings, integers...etc), but have not come across any info on how to implement it for a hash attribute.
Would appreciate it if someone could point me in the right direction.
Extend Type::OrderDetailType from ActiveRecord::Type::Value
You can override cast and serialize method.
def cast(value)
value
end
def serialize(value)
value
end
An example of Money type:
# app/models/product.rb
class Product < ApplicationRecord
attribute :price_in_cents, MoneyType.new
end
class MoneyType < ActiveRecord::Type::Integer
def type_cast(value)
# convert values like '$10.00' to 1000
end
end
product = Product.new(price_in_cents: '$10.00')
product.price_in_cents #=> 1000
Doc: http://edgeapi.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html
Example: http://nithinbekal.com/posts/rails-5-features/

setting hstore in rails4, dynamic key/values

I'm playing around with Hstore for the first time in a rails4 app, and I am using javascript in a form to build out dynamic form fields for the hstore column (:schema)
In rails 4 I dont need to add any setter/getter method in my model, correct?
In my form i am building dynamic input fields and allowing the user to set the key/value pairs. Much like the Hstore Heroku Demo App
So basically my form would have inputs like
input name="app[schema][dynamic_key1]" value="whatever value"
input name="app[schema][dynamic_key2]" value="whatever value2"
In my App Controller:
def app_params
params.require(:app).permit(:name, :title, :schema )
end
However, when i create a new App record, my schema hstore values are not saving. I saw some things about making the strong param for :schema => [] but that still does not work.
Since I do not know what these values will be, i cant setup store_accessors for these like I have seen in a lot of examples.
found this here: http://guides.rubyonrails.org/action_controller_overview.html#more-examples
and in my controller I used:
def app_params
params.require(:app).permit(:name, :title).tap do |whitelisted|
whitelisted[:schema] = params[:app][:schema]
end
end
I think Rails must have simplified this in recent versions (current as of 5.2.3 at least)... and much cleaner/easier:
params.require(:parent).permit(:name, :whatever, data: {})
This will allow and store any/all attributes of data into an hstore field. An example of POSTing or PUTing a data nested attribute via HTML:
<input type="text" name="parent[data][your_super_custom_nested_data] />`
4th example down: https://guides.rubyonrails.org/action_controller_overview.html#more-examples
Here's a way that also allows deleting of the hstore keys by submitting empty
parameters.
In your ApplicationController add this method:
# Returns the hstore keys to be whitelisted.
#
# #param key [Symbol] the name of the hstore field
# #param params [Hash] the parameters for the hstore field
#
# #return [{Symbol => Array<Symbol>}, Symbol]
def permit_hstore_params(key, hstore_params)
keys = hstore_params.try(:keys)
# Return key if params are empty,
# this allows the hstore key to be removed.
return key if keys.blank?
# Otherwise, return the keys to be whitelisted
{ key => keys }
end
Example:
class DynamicRecord < ActiveRecord::Base
store_accessor :metadata
end
class DynamicRecordController < ApplicationController
# ...
def dynamic_model_params
params
.require(:dynamic_model)
.permit(:name, permit_hstore_params(:metadata, params[:dynamic_model][:metadata]))
end
end

Ruby on Rails Meta Programming - Defining a class method using a dynamic array

I'm trying to use the meta programming example code in RailsCast #345 (Hstore) and replace the static array with one generated from another (unrelated) Model in the application. The code sample from Ryan is:
class Product < ActiveRecord::Base
attr_accessible :name, :category, :price, :description
# store_accessor :properties, :author
%w[author rating runtime].each do |key|
attr_accessible key
define_method(key) do
properties && properties[key]
end
define_method("#{key}=") do |value|
self.properties = (properties || {}).merge(key => value)
end
end
end
I would like to replace %w[author rating runtime] above with something like Foo.pluck(:content).map(&:downcase) but that doesn't work. I get a method undefined error when I try to access the dynamic methods. For example when calling
#product.send(foo.content.downcase) # <-- which returns "author"
I get:
undefined method `author' for #<Product:0x007ff111df0908>
It works fine using the static array.
Is it possible to replace that static array? I'd like to read those values from another model that an administrator populates and not have to touch the code for new values. Open to alternative ways to accomplish the same thing too! Thanks for your time.
I suspect that array [author, rating, runtime] arent the columns from Product class. They are defined as attributes to class Product. Did you tried by replacing L#7 from " attr_accessible key " to " attr_accessor key " ?
Thanks.

Making virtual attributes be part of one Hash

I have a model which has one actual column in the database. This column is stored as a JSON string of configuration. I use a bunch of virtual attributes which I want to map inside of this configuration JSON attribute. I basically dont want to create a bunch columns in the db, but rather use this one JSON attribute to contain everything. Is there a cleaner way than the below defs of achieving this?
class Device < ActiveRecord::Base
attr_accessible :configuration
serialize :configuration, JSON
attr_accessor :background_color, :title
# below is ew
def background_color; self.configuration["background_color"]; end
def background_color=(value); self.configuration["background_color"] = value; end
def title; self.configuration["title"]; end
def title=(value); self.configuration["title"] = value; end
end
Ideally i'd be looking for something like attr_maps_to_hash :configuration, [:background_color, :title]. Does something like this exist?
You can use ActiveRecord::Store for this.
class User < ActiveRecord::Base
store :settings, accessors: [ :color, :homepage ]
end
u = User.new(color: 'black', homepage: '37signals.com')
u.color # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
# Add additional accessors to an existing store through store_accessor
class SuperUser < User
store_accessor :settings, :privileges, :servants
end
If you are using PostgreSQL, check out HStore.
Rails as of 3.2 has key-value stores built into ActiveRecord-see here:
Where can i read more about Rails 3.2's Data Store with key-value in textfield?
in your case you can have a text field named configuration and then do this:
class Device < AR::Base
store :configuration, accessors: [:title, :background_color, ...]
...
This should work fine with forms, etc..
Two methods come to mind.
First, you could have an array of your attributes [:background_color, :title] and then iterate over them while calling define_method. You would defined two methods, define(method_name) and define("#{method_name}=").
Second, a similar idea but using method missing.
def method_missing(method_name, *args, &block)
...see if it's a get or set...
...do your stuff...
...rain dance...
...yay...
end

Custom serialization for fields in Rails

Is there a way to have a custom serialization for fields in rails, a method that runs when a field is saved and loaded to convert from/to a string which is what ultimately is saved on the database.
Specifically what I want to do is have a field of type symbol like gender, with possible values :male and :female storing "male" and "female" on the database. There are some workarounds, like:
def gender
read_attribute(:gender).try(:to_sym)
end
but that leaves obj.attributes unchanged, so it's a leaky abstraction.
You can do it in Rails 3.1. The object you want to serialize has to reply to load and dump methods.
Here is an example of serializing a string in Base64.
class User < ActiveRecord::Base
class Base64
def load(text)
return unless text
text.unpack('m').first
end
def dump(text)
[text].pack 'm'
end
end
serialize :bank_account_number, Base64.new
end
For more details see: http://archives.edgerails.info/articles/what-s-new-in-edge-rails/2011/03/09/custom-activerecord-attribute-serialization/index.html
def whachamacallit
read_attribute("whachamacallit").to_sym
end
def whachamacallit=(name)
write_attribute("whachamacallit", name.to_s)
end
store them as stings in the database, but extract them as symbols when you pull them out then convert back before you save.
would work with any number or combination of strings / symbols.
to limit it to only a select few
validates_inclusion_of :whachamacallit, :in => [ :male, :female, :unknown, :hidden ]
From http://blog.quov.is/2012/05/01/custom-activerecord-attribute-serializers/
class Recipe < ActiveRecord::Base
serialize :ingredients, IngredientsList
end
class IngredientsList < Array
def self.dump(ingredients)
ingredients ? ingredients.join("\n") : nil
end
def self.load(ingredients)
ingredients ? new(ingredients.split("\n")) : nil
end
end
you can define the models to_xml for a model and it will do that
http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html
its possible to define Marshall.dump and put in that way i think, but its something to look into
You could use serialize method inside the model. Please reference to this link:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
(ps. search keyword "serialize" in that page ;D)
In short, you could do this:
class YourModel < ActiveRecord::Base
serialize :db_field
end
Rails would automatically serialize the field before saving to database, and deserialize it after fetched from the database.
well for just male/female you could just do a Boolean column like male and if it was false assume that meant female, add wrapper methods for it
def female?
return !self.male?
end
We just released a gem (AttributeHelpers) that does exactly this. Disclaimer: I am a maintainer for the gem.
It allows you to call attr_symbol :gender in your class definition and the serialization happens automagically.

Resources