Creating new instance of Hash in Ruby - ruby-on-rails

I am quite new to Ruby, I am using HashWithIndifferentAccess for hash feature in Ruby. So my code is like:
def someFunction
array_list = []
some_array.each do | x |
new_hash = HashWithIndifferentAccess.new
// add entries to new_hash
array_list.push(new_hash)
end
array_list
end
Problem is: for each iteration I am initializing new hash, But if i do following, entry in array_list becomes empty:
def someFunction
array_list = []
new_hash = HashWithIndifferentAccess.new
some_array.each do | x |
// add entries to new_hash
array_list.push(new_hash)
new_hash.clear
end
array_list
end
I don't want to initialize new hash for each iteration, Any solution for this issue ?

I don't want to initialize new hash for each iteration
Why not? What is the reasoning behind this? You have to, otherwise it cannot work.
If you don't create a new hash each iteration, you're pushing the same hash into the array every time. Each element in the array is the same object, sharing the same state. There is only one hash, when you clear it, all the references to that same hash are obviously also cleared, because they are all the same object.
Any solution for this issue ?
Yes, you already have it: You need to create a new hash each iteration.

Related

Writing custom method for a hash on Ruby? on rails

i'm trying (and actually succeded, but i don't understand how it works) to write a custom method for a hash in my model (I'm working on Ruby on Rails 6).
My hash looks like this
my_hash = {
[['name_1', 'slug_1']=>value_1],
[['name_2', 'slug_2']=>value_2],
[['name_1', 'slug_1']=>value_3],
[['name_2', 'slug_2']=>value_4]
}
So basically a hash of arrays. You notice that the 'keys' are arrays that repeat themselves many times, but with different values. What i want to achieve is to write a custom method that "joins" all the keys in only one key, which will have an array of values assigned, so basically i should be able to get:
my_hash = {
['name_1', 'slug_1']=>"values": [value_1, value_3],
['name_2', 'slug_2']=>"values": [value_2, value_4]
}
For that, I have this piece of code, which i use many times:
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
Since I use this many times, i wanted to write a custom method, so i did:
def format_hash_data my_hash
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
And used it like: my_hash = format_hash_data(my_hash) with no success(it threw an error saying that 'format_hash_data' was not a valid method for the class).
So I fiddled around and added 'self' to the name of the method, leaving:
def self.format_hash_data my_hash
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
Which, to my surprise, worked flawlessly when using my_hash = format_hash_data(my_hash)
I don't really understand why adding 'self' makes my code works, maybe anyone can shed some light? I tried using things like send() or instance_eval first, to just send the piece of code to the actual hash as a method (something like my_hash.instance_eval(my_method)) but I couldn't get it working.
I'm sorry about the long explanation, I hope i was clear enough so any of you who had this same dilemma can understand. Thanks in advance.
Prepending self. to the method name makes it a class method instead of an instance method. If you are not sure of the difference, you should look it up as it is fundamental to properly defining and using classes and methods.
As a class method, you would use it as:
my_hash = MyHash.format_hash_data(my_hash)
Or if you're in scope of the class, simply my_hash = format_hash_data(my_hash), which is why it worked in your case with the self. prepended (class method definition).
If you want to define it as an instance method (a method that is defined for the instance), you would use it like so:
my_hash = my_hash.format_hash_data
And the definition would use the implicit self of the instance:
def format_hash_data
self.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end

How to make a serialized column empty?

I have a column in Company, that is serialized as Array:
class Company
serialize :column_name, Array
end
In rails console, when I try the following:
existing = Company.last.column_name
# it gives a array with single element, say [1]
existing.delete(1)
# now existing is []
Company.last.update_attributes!(column_name: existing)
# It empties the array, so:
Company.last.column_name #gives []
But, when I try the same code in remove method of some controller, it never removes the last element. It always returns [1].
How can I empty the serialized column?
NOTE:- This logic works when I have multiple elements, but doesn't work for the last element alone.
CONTROLLER CODE
def remove_restricted_num
company = Company.where(id: params[:id]).first
restricted_numbers = company.restricted_numbers
num = params[:num].to_i
if restricted_numbers.include?(num)
restricted_numbers.delete(num)
company.update_attributes!(restricted_numbers: restricted_numbers)
render js: "alert('#{num} was removed')"
else
render js: "alert('Number not found in the list')"
end
end
I got the fix, we need to use dup to get a independent variable existing, which otherwise was referencing Company.last.column_name
existing = Company.last.column_name.dup # THIS WORKS!!!
existing.delete(1)
Company.last.update_attributes!(column_name: existing)
This updates the column to [], as I need.

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!

Serialized Hash 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 :)

Inserting new item to instance variable in Ruby on Rails

#xs stores urls like www.yahoo.com, www.google.com
for x in #xs
y = x... #do something with x
#result += y #i want to do something like that. i want to store them in #result. What do i have to write in here?
end
Sorry for noob question. By the way how do you call #result ? Is it an instance variable or an array ?
You need to initialize #result first.
#result = []
for x in #xs
y = x...
#result << y
end
You should either do this:
#result << y
or this:
#result += [y]
The + operator expects two arrays, the << operator appends an object onto an array.
From what I can make out from the question, you want to mutate the contents of the already existing array
#mutated_xs = #xs.collect do |x|
y = x.do_something # some code for to do something to x returning y
x += y # mutate existing x here
end
puts #mutated_xs.inspect
If you want to take every element in an array and change it, the idiomatic Ruby way is to use map or collect:
#new_urls = #urls.map do |url|
# change url to its new value here
end
You don't need to manually assign it to #new_urls, just write a statement that returns the desired value, like url.upcase or whatever you want to do.

Resources