Save a hash to a parameter? - ruby-on-rails

I would like to save the params from the form submitted into a hash called: hash_options which corresponds to a field in my table.
How do I store the hash of: hash_options as the value for: hash_fields ?
hash_fields is a text field and I am trying to store hash_options in this row as a plain hash.
def person_params
hash_options = {age: params['person']['age'], location: params['person']['location'], gender: params['person']['gender']}
params.require(:person).permit(:first_name, :last_name, :owner, hash_fields: [hash_options])
end
Side question: How does the model access and store the params?

In order to be able to save the hash as is to the database you have a couple of options:
If you are using MongoDB(mongoid) you can just define it as a hash type field in your model with:
field options_hash, type: Hash
You can use postgresql HStore to save schemaless data into that column which enables you to save the hash into the column and work with it as a Hash.
As for the params part there is no need to include it in the permit call, you can just do:
model = Model.new(person_params) do |m|
m.options_hash = hash_options
end
model.save

Related

Permit array of strong params without using nested attributes

I have an array of users I want to save in my database. An example when shown in the console after a POST is made:
"users"=>[{"name"=>"Job", "email"=>"*****"}, {"name"=>"Ed", "email"=>"****"}]
No nested attributes going on here. How to format strong params for this?
def user_params
params.fetch(:user, {}).permit(:name, :email, ...)
end
This may work for saving a single user but I'm passing an array of users. I'm currently storing the user in a "dirty" manner:
users = params[:users]
users.each do |user|
User.create(name: user[:name], email: user[:email])
end
I'd like to refactor and do something like:
User.create(user_params)
To permit a param key containing an array of hashes you pass the name as the key and an array of permitted attributes:
def user_params
params.permit(users: [:name, :email, ...])
.fetch(:users, [])
end
The output will be an array of ActionController::Parameters instances or an empty array if the key is not present:
[#<ActionController::Parameters {"name"=>"Job", "email"=>"*****"} permitted: true>,
#<ActionController::Parameters {"name"=>"Ed", "email"=>"****"} permitted: true>]

How to store string as array in database column using Ruby on Rails

This question is asked many times on SO. The main problem is nothing got fits into my situation.
Case is, I am not able to store typed content as array in database column.
text_field whose code is:
= text_field_tag 'product[keywords][]', #product.keywords, class: 'tab-input
product_keywords'
In controller strong parameters are:
params.require(:product).permit(:id, :name, :keywords => [])
Jquery code that is not not removing value upon deletion when typed wrong value but it add commas after each element as I want to take commas seperated value in one column.
$(document).on 'keyup', '.product_keywords', ->
keyword = #value.replace(/(\w)[\s,]+(\w?)/g, '$1, $2')
if keyword != #value
#value = keyword
return
model code:
serialize :keywords, Array
migration code:
class AddKeywordsToProducts < ActiveRecord::Migration[5.1]
def change
add_column :products, :keywords, :text
end
end
So, if someone writes, abc and hit space a comma is added in the end. after three typed words it will look like:
abc, dbx, she
now I want to store it as array in column but its not storing properly.
it stores as:
["abc, dbx, she"]
Also please can anybody tell me the best cases to handle these cases?
Plus best practices to deal with such cases using ruby so I will learn it for future?
You probably want a custom serializer as shown here. So instead of:
serialize :keywords, Array
You might do somewhat like:
serialize :keywords, KeywordSerializer
And somewhere in helpers:
class KeywordSerializer
def self.dump(what)
what.join(", ")
end
def self.load(what)
what.split(/\s*,\s*/)
end
end
Passing array elements using single form tag is not possible to pass as a array and passing array as a string, you need to process it near white-listing your params,
permitted_params = params.require(:product).permit(:id, :name, :keywords => [])
permitted_params[:keywords] = permitted_params[:keywords][0].split(/\s*,\s*/)

How to parse param comma separated string ids to has_many mongoid field

How to handle tag_ids in post params to save it in related model? I would like to use for it only post_params method.
has_many :tags
def post_params
params.require(:post).permit(:title, :message, :tag_ids)
end
#Parameters: {"post"=>{"title"=>"asdf", "message"=>"asfd", "tag_ids"=>"543d727a4261729ecd000000,543d8a914261729ecd010000"}}
I've got:
Mongoid::Errors::InvalidValue -
Problem:
Value of type String cannot be written to a field of type Array
I found solution but I don't like it, in Post model I've added:
def tag_ids=str
str.split(',').each do |id|
t = Tag.find(id)
self.tags << t
end
end
I think that you have to modify the incoming data in tag_ids in create action in your controller.
So when you receive the data, before you are saving the data into DB by, for example: post.create! you should add parsing to your PostsController action create:
If you want array of string:
post.tag_ids = params[tag_ids]split(",")
or if you want array of integers:
post.tag_ids = params[tag_ids]split(",").map(&:to_i)
Something like this? (I'm assuming you want an array of ObjectId)
id_array = params['post']['tag_ids'].split(',').map { |id| BSON::ObjectId.from_string(id) }
params['post']['tag_ids'] = id_array

Rails: use existing model validation rules against a collection instead of the database table

Rails 4, Mongoid instead of ActiveRecord (but this should change anything for the sake of the question).
Let's say I have a MyModel domain class with some validation rules:
class MyModel
include Mongoid::Document
field :text, type: String
field :type, type: String
belongs_to :parent
validates :text, presence: true
validates :type, inclusion: %w(A B C)
validates_uniqueness_of :text, scope: :parent # important validation rule for the purpose of the question
end
where Parent is another domain class:
class Parent
include Mongoid::Document
field :name, type: String
has_many my_models
end
Also I have the related tables in the database populated with some valid data.
Now, I want to import some data from an CSV file, which can conflict with the existing data in the database. The easy thing to do is to create an instance of MyModel for every row in the CSV and verify if it's valid, then save it to the database (or discard it).
Something like this:
csv_rows.each |data| # simplified
my_model = MyModel.new(data) # data is the hash with the values taken from the CSV row
if my_model.valid?
my_model.save validate: false
else
# do something useful, but not interesting for the question's purpose
# just know that I need to separate validation from saving
end
end
Now, this works pretty smoothly for a limited amount of data. But when the CSV contains hundreds of thousands of rows, this gets quite slow, because (worst case) there's a write operation for every row.
What I'd like to do, is to store the list of valid items and save them all at the end of the file parsing process. So, nothing complicated:
valids = []
csv_rows.each |data|
my_model = MyModel.new(data)
if my_model.valid? # THE INTERESTING LINE this "if" checks only against the database, what happens if it conflicts with some other my_models not saved yet?
valids << my_model
else
# ...
end
end
if valids.size > 0
# bulk insert of all data
end
That would be perfect, if I could be sure that the data in the CSV does not contain duplicated rows or data that goes against the validation rules of MyModel.
My question is: how can I check each row against the database AND the valids array, without having to repeat the validation rules defined into MyModel (avoiding to have them duplicated)?
Is there a different (more efficient) approach I'm not considering?
What you can do is validate as model, save the attributes in a hash, pushed to the valids array, then do a bulk insert of the values usint mongodb's insert:
valids = []
csv_rows.each |data|
my_model = MyModel.new(data)
if my_model.valid?
valids << my_model.attributes
end
end
MyModel.collection.insert(valids, continue_on_error: true)
This won't however prevent NEW duplicates... for that you could do something like the following, using a hash and compound key:
valids = {}
csv_rows.each |data|
my_model = MyModel.new(data)
if my_model.valid?
valids["#{my_model.text}_#{my_model.parent}"] = my_model.as_document
end
end
Then either of the following will work, DB Agnostic:
MyModel.create(valids.values)
Or MongoDB'ish:
MyModel.collection.insert(valids.values, continue_on_error: true)
OR EVEN BETTER
Ensure you have a uniq index on the collection:
class MyModel
...
index({ text: 1, parent: 1 }, { unique: true, dropDups: true })
...
end
Then Just do the following:
MyModel.collection.insert(csv_rows, continue_on_error: true)
http://api.mongodb.org/ruby/current/Mongo/Collection.html#insert-instance_method
http://mongoid.org/en/mongoid/docs/indexing.html
TIP: I recommend if you anticipate thousands of rows to do this in batches of 500 or so.

Saving a nested hash in Ruby on Rails

I'm trying to save a nested Hash to my database and retrieve it, but nested values are lost upon retrieval.
My model looks like this:
class User
serialize :metadata, MetaData
end
The class MetaData looks like this:
class MetaData < Hash
attr_accessor :availability, :validated
end
The code I'm using to store data looks something like this (the real data is coming from a HTML form, though):
user = User.find(id)
user.metadata.validated = true
user.metadata.availability = {'Sunday' => 'Yes', 'Monday' => 'No', 'Tuesday' => 'Yes'}
user.save
When I look at the data in the database, I see the following:
--- !map:MetaData
availability: !map:ActiveSupport::HashWithIndifferentAccess
Sunday: "Yes"
Monday: "No"
Tuesday: "Yes"
validated: true
The problem occurs when I try to get the object again:
user = User.find(id)
user.metadata.validated # <- this is true
user.metadata.availability # <- this is nil
Any ideas? I'm using Rails 3.1 with Postgresql as my datastore.
If you look in the database you see "map:ActiveSupport::HashWithIndifferentAccess" for availability?
My approach would be to separate out the single instance of availablity from the hash collection structure of days available.
you mean user.metadata.validated # <- this is true ?
What DB columns are metadata and availability stored as? They need to be TEXT

Resources