Refactoring processing of JSON attributes - ruby-on-rails

In an API processing a large number of attributes, the following pattern is frequent
if !article[:ingredients].nil?
clean_ingredients = article[:ingredients].tr('*$+!##Â', ' ')
ingredients = clean_ingredients.downcase.capitalize
else
ingredients = nil
end
for a JSON string as: { "id": "YYYYYY", "article": [ { "ingredients": "long string", [...]
Unfortunately, a method defined as
def empty_and_clean(array_element, element_attribute)
if !array_element[:element_attribute].nil?
clean_ingredients = array_element[:element_attribute].tr('*$+!##Â', ' ')
ingredients = clean_ingredients.downcase.capitalize
else
ingredients = nil
end
end
cannot be called in the method as empty_and_clean(article, ingredients) as it returns
undefined local variable or method 'ingredients'
What syntax allows to refactor this pattern?

You can call your empty_and_clean method this way:
empty_and_clean(article, :ingredients)
Just modify empty_and_clean to use element_attribute directly rather than the symbol :element_attribute.
I suggest you read more about symbols in Ruby to understand how this works.
Also, array_element is a misleading name because it is an array, not an element of an array. array would be slightly better, but is still too generic. Maybe objects or something else that describes what is actually in the array.

Related

Ruby block as an array element for splatting in public_send

I am trying to build a generic method using meta programming where it uses manipulating methods from array and send to the object using splat, following is the working snippet:
ALLOWED_DATA_TYPES = {
'Integer' => [['to_i']],
'Csv' => [['split', ',']]
}
ALLOWED_DATA_TYPES.each do |type, methods|
define_method("#{type.downcase}_ified_value") do
manipulated_value = value
methods.each { |method| manipulated_value = manipulated_value.public_send(*method) }
return manipulated_value
end
end
It was working great so far, until we decided to add another datatype and it needs to call method on array, for e.g.
"1,2,3".split(',').map(&:to_f)
Now I am stuck, because it's a block. Technically, following code is working alright:
"1,2,3".public_send('split', ',').public_send(:map, &:to_f)
# => [1.0, 2.0, 3.0]
But adding that block to array is throwing error
[['split', ','], ['map', &:to_f]]
# => SyntaxError: syntax error, unexpected &, expecting ']'
I know I can create a proc and call it with amp & but I hope u get it that it is loosing consistency, I need something that will just work with splat operator as defined using #define_method
I am out of ideas now, please help.
You're out of luck, & is not an operator - it is a special syntax that is only allowed in function parameters (both definition and invocation). One way you could do it is to reserve the last element of the array for the block; but then you always have to use it (even if it is just nil).
methods.each { |*method, proc| manipulated_value = manipulated_value.public_send(*method, &proc) }
This should work with [['split', ',', nil], ['map', :to_f]].
Off-topic, but note that these three lines can be more succintly rewritten using inject:
manipulated_value = value
methods.each { |*method, proc| manipulated_value = manipulated_value.public_send(*method, &proc) }
return manipulated_value
becomes
methods.inject(value) { |manipulated_value, (*method, proc)| manipulated_value.public_send(*method, &proc) }

Simplifying an expression using .map

Below I have an example active record query using a bunch of each iterators
user.user_spells.each do |us|
us.spell.buff_effects.where(stat_effected:'gold').each do |be|
value = value + be.value
end
end
I would like to use .map to return a list of all the results so I can do it essentially in one line.
I came up with:
user.user_spells.map { |us| us.spell.buff_effects.where(stat_effected:stat_effected) }.each do |be|
value = value + be.value
end
However... the .map block returns some empty arrays. Not sure how to write it correctly.
Any help would be appreciated! Thanks
Probably along these lines, if what you want is the sum of values in the end:
value =
user.user_spells.flat_map do |us|
us.spell.buff_effects.where(stat_effected:'gold').map(&:value)
end.reduce(&:+)

how to get the key value from the nested hash inside the array?

I have a array which is inside a hash. I want know the result of the student (pass/fail) using the following array. First I have to match them with particular standard and compare their marks with the hash pass and fails. And I want to get the key pass or fail based on their mark. How to achieve this using Ruby?
array = [
{
:standard =>1
:pass=>{:tamil=>30,:eng=>25,:math=>35},
:fail=>{:tamil=>10,:eng=>15,:maths=>20}
},
{
:standard =>2,
:pass=>{:tamil=>40,:eng=>35,:math=>45},
:fail=>{:tamil=>20,:eng=>25,:maths=>30}
}
]
#student is assumed to be defined
standard = array.select {|standard| standard[:standard] == #student.standard}
eng_pass = #student.eng_mark >= standard[:pass][:eng]
eng_fail = #student.eng_mark <= standard[:fail][:eng]
return [eng_pass, eng_fail, whatever_else_you_want]
So on and forth for various topics.
The syntax in reading values from this structure is something like:
array[0][:pass][:eng]
and accordingly you can do the comparison as usual in batch:
for i in 0..#students_array.length
num = # student's score
standard = # something like array[0][:pass][:eng]
if num > standard
# something like 'put "You passed!"'
end
end

Params contain an array that wants to be a hash

I have an array (coming from a file_field, :multiple => true) in my params that I want to turn into a hash so I can build associated models for each element and process in my create action.
Currently receiving:
{"gallery"=>{"name"=>"A Gallery", "photos_attributes"=>{"0"=>{"image"=>[#<1st Image data removed for brevity>, #<2nd Image data removed for brevity>]}}}, "commit"=>"Save"}
I'd like to turn it into something like:
{"gallery"=>{"name"=>"A Gallery", "photos_attributes"=>{"0"=>{"image"=>#<1st Image data removed for brevity>}, "1"=>{"image"=>#<1st Image data removed for brevity>}}}, "commit"=>"Save"}
considered something like this but it's clearly wrong:
i = 0
params[:gallery][:photos_attributes]["0"][:image].reduce({}) do |result, element|
result[i++.to_s] = element
end
What's the "Rail's Way"?
You need to return the result hash at the end of each iteration.
i = 0
params[:gallery][:photos_attributes]["0"][:image].reduce({}) do |result, element|
result[(i += 1).to_s] = element
result
end
I've done something similar when receiving data from an iOS device. But, if I understand what you want and what your model(s) look like, to get nested attributes to work you don't want it to look like:
{ "photos_attributes" => { "0" => <image1>, "1" => <image2>, ... }
You want it to look like:
{ "photos_attributes" => [ <image1>, <image2>, ... ] }
And to do that all you need to do is:
params["gallery"]["photos_attributes"] = params["gallery"]["photos_attributes"]["0"]["image"]
Now, if I've misunderstood what you need, to get what you've asked for what you have might work (I don't use much reduce aka inject) or you could use tap:
i = 0
params["gallery"]["photos_attributes"] = {}.tap do |hash|
params["gallery"]["photos_attributes"]["0"]["image"].each do |image|
hash[i.to_s] = image
i = i + 1
end
end
Not a whole lot better IMO.

Using a method while looping through an array in ruby

I am using ruby-aaws to return Amazon Products and I want to enter them into my DB. I have created a model Amazonproduct and I have created a method get_amazon_data to return an array with all the product information. When i define the specific element in the array ( e.g. to_a[0] ) and then use ruby-aaws item_attributes method, it returns the name I am searching for and saves it to my DB. I am trying to iterate through the array and still have the item_attributes method work. When i don't define the element, i get this error: undefined method `item_attributes' for #Array:0x7f012cae2d68
Here is the code in my controller.
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each { |name|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = #arr.item_attributes.title.to_s
}
EDIT: Code in my model to see if that helps:
class Amazonproduct < ActiveRecord::Base
def self.get_amazon_data(r)
resp = Amazon::AWS.item_search('GourmetFood', { 'Keywords' => 'Coffee Maker' })
items = resp.item_search_response.items.item
end
end
Thanks for any help/advice.
I'm not familiar with the Amazon API, but I do observe that #arr is an array. Arrays do not usually have methods like item_attributes, so you probably lost track of which object was which somewhere in the coding process. It happens ;)
Try moving that .item_attributes call onto the object that supports that method. Maybe amazonproduct.get_amazon_data(:r), before its being turned into an array with to_a, has that method?
It's not quite clear to me what your classes are doing but to use #each, you can do something like
hash = {}
[['name', 'Macbook'], ['price', 1000]].each do |sub_array|
hash[sub_array[0]] = sub_array[1]
end
which gives you a hash like
{ 'name' => 'Macbook', 'price' => 1000 }
This hash may be easier to work with
#product = Product.new
#product.name = hash[:name]
....
EDIT
Try
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each do |aws_object|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = aws_object.item_attributes.title.to_s
end
end

Resources