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
Related
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
I have a cgi_attributes hstore type column in the UrlCommand model.
class UrlCommand < ActiveRecord::Base
store_accessor :cgi_attributes, :name, :range, :security, :default_value
end
However, the keys in cgi_attributes should be dynamically added by users.
And I also want to render each key as a input filed in my form
Rather than hard-code the columns
- [:name, :range, :security].each do | column |
= render :partial => 'attributes' , :locals => { f: f, column: column }
And also need to add those dynamically generated key can be update into my Model.
def url_command_params
params.require(:url_command).permit(:model_name, :firmware_version, :cgi_attributes,
:cgi_name,:name, :range, :security)
end
Now, All of my code are based on hard-code, How to make the keys and value can be dynamically added by users and store into the UrlCommand model ?
Maybe you can create a separate table for the attribute names (CustomAttributes) and then create a loop that adds them with store_accessor like this:
CustomAttributes.all.each do |attribute|
store_accessor :cgi_attributes attribute.name
end
I guess you can do something similar to strong parameters. I have not tried this my self, because I'm struggeling to get hstore working in my application. But i will have the same user situation/ problem to solve. So i will test it and change my answer once i get it working and confirmed.
a bit like it is explained in this railscast:
http://railscasts.com/episodes/403-dynamic-forms
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
Lets say I had code in a controller that did not use Strong Parameters
Model.create name: params[:name], alias_id: params[:foreign_id]
Is it possible for me to use Strong Parameters here?
I cannot do
Model.create params.require(:name, :foreign_id)
Because foreign_id is not a param
I cannot do
Model.create params.require(:name, :alias_id)
Because alias_id is not on the model.
Basically, I want to know if you can alias paramater keys when using Strong Parameters.
Usually if I want to map params in this way (usually due to some external API) I use the alias_attribute method in ActiveRecord
So if I have params that come to me as {name: "bob", alias_id: 1234} and ideally I would want to have {name: "bob", foreign_id: 1234}
I declare the following in my ActiveRecord model
class MyModel < ActiveRecord::Base
alias_attribute :alias_id, :foreign_id
end
Now my model will always recognise alias_id as mapping to foreign_id in any context and I can push in params.require(:name, :alias_id) and my model will recognise it and process it as required without looping over attributes in my controller.
This is also simpler if you want to do it on other models as well.
I got the functionality I wanted with the following piece of code. I don't think Strong Parameters can do what I need, especially as require() cannot take multiple parameters.
By putting this in my ApplicationController or a module it inherits
#
# Pass in a list of param keys to pull out of the params object
# Alias param keys to another key by specifiying a mapping with a Hash
# eg. filter_params :foo, :bar, {:choo => :zoo}
#
def filter_params(*param_list)
filtered_params = {}
param_list.each do |param|
if param.is_a? Hash
param.each {|key, value| filtered_params[value] = params[key]}
else
filtered_params[param] = params[param]
end
end
filtered_params
end
I can now say
Model.create filter_params(:name, {foreign_id: :alias_id})
My setup: Rails 3.0.9, Ruby 1.9.2
I have my reasons for doing this but what I need is a way to add a virtual attribute to an activerecord resultset dynamically. That means I am not using attr_accessor in the model and instead wish to dynamically add virtual attributes to a resultset.
For example,
users = User.all
#a user has following attributes: name, email, password
What I like to do is say add (without using attr_accessor) a virtual attribute status to users, is that possible?
You should do this:
users.each do |user|
user.instance_eval do
def status
instance_variable_get("#status")
end
def status=(val)
instance_variable_set("#status",val)
end
end
end
you can do the following:
add an attribute "extras" which will be accessed as a Hash, and which will store any additional / dynamic attributes -- and then tell Rails to persist that Hash via JSON in ActiveRecord or Mongo or whatever you use
e.g.:
class AddExtrasToUsers < ActiveRecord::Migration
def self.up
add_column :users, :extras, :text # do not use a binary type here! (rails bug)
end
...
end
then in the model add a statement to "serialize" that new attribute -- e.g. that means it's persisted as JSON
class User < ActiveRecord::Base
...
serialize :extras
...
end
You can now do this:
u = User.find 3
u.extras[:status] = 'valid'
u.save
You can also add a bit of magic to the User model, to look at the extras Hash if it gets a method_missing() call
See also:
Google "Rails 3 serialize"
If your attributes are read-only, you can also add them by selecting them in your database query. Fields which appear in the SQL result result will automatically be add as attributes.
Examples:
User.find_by_sql('users.*, (...) AS status')
User.select('users.*, joined_table.status').join(...)
With these examples you'll have all User attributes and an additional status attribute.
You could simply do:
users.each { |user| user.class_eval { attr_accessor :status } }
All users will have user.status and user.status = :new_status available.