Serialized Hash Rails - ruby-on-rails

I've just started Rails dev and have a question about inputing data into a serialized hash in my database from a url with a variable number of params. I'm trying to figure out how to implement a url with any number of param/values in the form (action?1=.5&2=.6&3=.1 etc) that I want to add to my serialized hash. If my hash already has some key/values previously stored, how do I add more to the same hash? I want all the data to be in one column.
I have a method where I pass it a model object:
def add_data_to_hash(obj)
params.each do |key, value|
if (key.to_i.is_a?(Integer) && key.to_i != 0) then
obj.hash[key] = value
obj.save
end
end
end
Thanks!

Something like this maybe?
this should go on the controller
new_values = {}
url = request.fullpath.split("?").last
url.split("&").each do |kv|
pair = kv.split("=")
new_values[pair[0].to_i] = pair[1]
end
new_value.delete 0
and something like this inside your method
new_values.each do |k,v|
object.hash[k] = v
end
object.save
Also it is better to call object.save once you finished updating the hash and not for every Integer key :)

Related

Ruby on Rails: How can i iterate over params and convert any empty strings into nil values in a controller?

Rails newbie here.
I have an integration with stripe where users can update the billing address on their card, however, stripe doesn't accept empty strings, only nil values, and it's possible that users won't need to fill in the second address line for example.
How would I go about iterating through params received from a form and convert empty strings into nil?
I have a Stripe Tool module that handles stripe related tasks.
In my controller i have:
def add_billing_address
account_id = current_user.account_id
account = Account.find_by(id: account_id)
stripe_id = account.stripe_customer_id
# convert params empty strings to nil here
StripeTool.add_billing_address(stripe_id: stripe_id,
stripe_token: params[:stripeToken],
address_line1: params[:address_line1],
address_line2: params[:address_line2],
address_city: params[:address_city],
address_state: params[:address_state],
address_zip: params[:address_zip]
)
# redirects and error handling happens after this
You can call .map .each on the params hash in the controller like this:
params.each do |key, value|
params[key] = nil if value === ''
end
But it's probably better to let your form return a nil value when a field contains no data.
I would recommend to avoid modifying the values in the params object, cause it is not good practice to change them in place. It is better to create a new object the has the values you want to use.
stripe_params = params.select { |_,v| v.present? }
This will create a new object without any of the blank attributes. I'm guessing that if an attribute is nil, you might as well not pass it at all.

Editing params nested hash

Assume we have a rails params hash full of nested hashes and arrays. Is there a way to alter every string value (whether in nested hashes or arrays) which matches a certain criteria (e.g. regex) and still keep the output as a params hash (still containing nested hashes arrays?
I want to do some sort of string manipulation on some attributes before even assigning them to a model. Is there any better way to achieve this?
[UPDATE]
Let's say we want to select the strings that have an h in the beginning and replace it with a 'b'. so we have:
before:
{ a: "h343", b: { c: ["h2", "s21"] } }
after:
{ a: "b343", b: { c: ["b2", "s21"] } }
For some reasons I can't do this with model callbacks and stuff, so it should have be done before assigning to the respective attributes.
still keep the output as a params hash (still containing nested hashes arrays
Sure.
You'll have to manipulate the params hash, which is done in the controller.
Whilst I don't have lots of experience with this I just spent a bunch of time testing -- you can use a blend of the ActionController::Parameters class and then using gsub! -- like this:
#app/controllers/your_controller.rb
class YourController < ApplicationController
before_action :set_params, only: :create
def create
# Params are passed from the browser request
#model = Model.new params_hash
end
private
def params_hash
params.require(:x).permit(:y).each do |k,v|
v.gsub!(/[regex]/, 'string')
end
end
end
I tested this on one of our test apps, and it worked perfectly:
--
There are several important points.
Firstly, when you call a strong_params hash, params.permit creates a new hash out of the passed params. This means you can't just modify the passed params with params[:description] = etc. You have to do it to the permitted params.
Secondly, I could only get the .each block working with a bang-operator (gsub!), as this changes the value directly. I'd have to spend more time to work out how to do more elaborate changes.
--
Update
If you wanted to include nested hashes, you'd have to call another loop:
def params_hash
params.require(:x).permit(:y).each do |k,v|
if /_attributes/ ~= k
k.each do |deep_k, deep_v|
deep_v.gsub!(/[regex]/, 'string'
end
else
v.gsub!(/[regex]/, 'string')
end
end
end
In general you should not alter the original params hash. When you use strong parameters to whitelist the params you are actually creating a copy of the params - which can be modified if you really need to.
def whitelist_params
params.require(:foo).permit(:bar, :baz)
end
But if mapping the input to a model is too complex or you don't want to do it on the model layer you should consider using a service object.
Assuming you have a hash like this:
hash = { "hello" => { "hello" => "hello", "world" => { "hello" => "world", "world" => { "hello" => "world" } } }, "world" => "hello" }
Then add a function that transforms the "ello" part of all keys and values into "i" (meaning that "hello" and "yellow" will become "hi" and "yiw")
def transform_hash(hash, &block)
hash.inject({}){ |result, (key,value)|
value = value.is_a?(Hash) ? transform_hash(value, &block) : value.gsub(/ello/, 'i')
block.call(result, key.gsub(/ello/, 'i'), value)
result
}
end
Use the function like:
new_hash = transform_hash(hash) {|hash, key, value| hash[key] = value }
This will transform your hash and it's values regardless of the nesting level. However, the values should be strings (or another Hash) otherwise you'll get an error. to solve this problem just change the value.is_a?(Hash) conditional a bit.
NOTE that I strongly recommend you NOT to change the keys of the hash!

How to assign an array of Hashes in a loop?

I'm attempting to convert MySQL timestamps in an ActiveRecord object to another timestamp format. My method takes an array of ActiveRecord records and returns an array of hashes with the timestamped fields with the formatted timestamp:
def convert_mysql_timestamps(records)
ary = []
hash = {}
records.each_with_index do |record, i|
record.attributes.each do |field, value|
if time_columns.include?(field) and value then
hash[field] = value.strftime("%Y-%m-%dT%H:%M:%S%z")
else
hash[field] = value
end
end
ary[i] = {}
ary[i] = hash
end
ary
end
However, when in the ary[i] = hash assignment, all ary elements get set to hash.
Is there a better way to convert a record's timestamp fields? (I don't need to save the records back to the database.) Also, how can I get the array to capture each individual hash representation of the record?
Input:
[#<Vehicle id: 15001, approved_at: "2011-03-28 10:16:31", entry_date: "2011-03-28 10:16:31">, #<Vehicle id: 15002, approved_at: "2011-03-28 10:16:31", entry_date: "2011-03-28 10:16:31">]
Desired output:
[{"id"=>15001, "approved_at"=>"2011-03-28T10:16:31-0700", "entry_date"=>"2011-03-28T10:16:31-0700"}, {"id"=>15002, "approved_at"=>"2011-03-28T10:16:31-0700", "entry_date"=>"2011-03-28T10:16:31-0700"}]
The problem is that you're creating one Hash:
def convert_mysql_timestamps(records)
ary = []
hash = {}
#...
and then trying to re-use for each record. You probably want a fresh Hash for each each_with_index iteration:
def convert_mysql_timestamps(records)
ary = []
records.each_with_index do |record, i|
hash = { }
record.attributes.each do |field, value|
#...
end
ary[i] = hash
end
end
You can use map for this - it's always a good option when you want to take an array in one format and produce a same-sized array in another. Here's how:
def convert_mysql_timestamps(records)
records.map do |record|
Hash[records.attributes.map{|k, v| [k, convert_mysql_timestamp(v)] }]
end
end
def convert_mysql_timestamp(field, value)
return value unless time_columns.include?(field) && value
value.strftime("%Y-%m-%dT%H:%M:%S%z")
end
It works like so:
Hash[array_of_pairs] turns an array of key-value pairs - like [["foo", 2], ["bar", 3], ...] - into a hash like {"foo" => 2, "bar" => 3, ...}.
map calls its block for each item in the collection, and collects each return value of the block into a new array, which it returns. The attributes.map inside the Hash[...] creates the array of key-value pairs, and the outer records.map collects up all the hashes into the returned array.
I'd suggest reading up on the methods in Enumerable because there are so many neat things like map in there. You will find that you almost never have to use indices in your loops, although if you're coming from another language with for loops everywhere it's a hard habit to break!
I am not sure what your time_columns are, but assuming they are Time class, you can simplify the part like value.is_a?(Time).
def convert_mysql_timestamps(records)
records.collect do |record|
# assuming records are from a Rails model, I am using #attributes
# to loop through all fields in a record
# then inject values in this hash -> ({}),
# which is in the block, named attributes
record.attributes.inject({}) do |attributes, (column_name, value)|
# if it is Time, convert it to iso8601 (slightly different from your format,
# but if this is also acceptable, your code can be simpler)
attributes[column_name] = (value.is_a?(Time) ? value.iso8601 : value)
attributes
end
end
end

Ruby / ROR assignment using or (Default values for checkbox)

I am trying to assign a default value to a check box in ROR. The following is the heirachy:
Check if value is in the params (url querystring)
Check if it's in the session variable
If neither, default to all possible values and set #rates to all possible values
I have written the following code:
#all_rates = Rates.all_rates
rates_all = {}
#all_rates.each {|rate| rates_all[rate] = "1"}
p rates_all
#rates = params[:rates] ||= session[:rates] ||= rates_all
puts #rates.length, #rates
when i p rates_all, i get the hash back, however when i check #rates.length it is not being assigned i get a 0.
Did you check if params[:rates] is nil? Because if it's an empty hash then it's still an object, just without any values. But the empty hash object would be assigned to #rates anyway, resulting that rates is an empty hash object too, with length 0.
Give this a try:
#rates = case
when params[:rates].present?
params[:rates]
when session[:rates].present?
session[:rates]
else
Rates.all_rates.inject({}) { |hsh, rate| hsh.merge(rate => '1') }
end
Sidebar:
Your model name should be the singular Rate. The ||= syntax in your example is invalid. It should be just ||.

serialized column always saved as null in Rails

All I am trying to do is save a two-dimensional hash in the column of a database in a Ruby on Rails application. I am only learning the how to use the framework, and this task is causing me a lot of grief. I have done my best to not make any stupid mistakes, though I believe my problem stems from one.
So what I'm doing is I create the hash in a controller and set it as the value of a field in my object, then call save.
Code:
#instance = Model.find(:first, :conditions => "id = 1"}
#instance.hash_field = Hash.new { |h, k| h[k] = Hash.new { |h1, k1| h1[k1] =0 }}
#instance.other_field = "some string"
#instance.save
other_field will have its new value saved and the value persists on subsequent retrievals from the database. The 'hash_field' will always be reset to nil when I pull the instance out of the database again. This happens despite the fact that the value of '#instance.hash_field' has the correct value in the controller and the view. This allows me to be confident in saying it is an issue with saving the hash to the database.
I have the statement
:serialize :hash_field
in my model, and its column in the database table is declared to have type 'text.' Any pointers would make me a very happy person.
-Chris
'serialize' is a method.
Hash.new { |h, k| h[k] = Hash.new { |h1, k1| h1[k1] =0 }} => {}
Please provide test data you use for hashes.
Get rid of the ":" in front of serialize:
class Model < ActiveRecord::Base
serialize :hash_field
end

Resources