Using a method while looping through an array in ruby - ruby-on-rails

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

Related

Looping through object and blanking out fields

I have this constant to hold all dates fields:
DATE_FIELDS = [:a_effective_on, :a_expire_on,
:field_effective_on, :field_expire_on,
:line_manager_effective_on, :line_manager_expire_on,
:officer_effective_on, :officer_expire_on,
:regional_effective_on, :regional_expire_on]
I want to loop over an object and remove a date if it has a value.
Is this valid?:
#copy_to.DATE_FIELDS.each do |_date|
# remove dates here
end
I am getting a nil class for id error.
DATE_FIELDS.each do |field|
#copy_to.send("#{field}=".to_sym, nil) if #copy_to.send(field)
end
When you put a constant in your class like this, it exists at the class level. Access it with YourClassName::DATE_FIELDS if you're outside the class, or just DATE_FIELDS if you're inside the class.
This is how i would do it:
#instance method in your class
def clear_all_date_fields
atts = {}
DATE_FIELDS.each{|f| atts[f] = nil}
self.attributes = atts
end
Now you can do
#copy_to.clear_all_date_fields
note that this doesn't save #copy_to. You can save it afterwards, or put the save into the method (though i think it's better to have it not saving automatically).

Create hash from variables in loop

I want to end up with an array of hashes.
I am starting with an array of codes:
#codes = ['123', '456', '789']
I take each of those codes and hit an API with them and it returns values that I parse into variables in a loop, like so:
#codes.each do |x|
#y = x.get_some_data
#brand = #y[brand]
#size = #y[size]
end
Then I want to put this data into an array of hashes
merged_array = []
final_hash = #codes.map{|code| {:code => code, :brand=> #brand, :size=> #size}
merged_array << final_hash
And in a perfect world, end up with hashes that look like this in the merged_array:
{:code => '123', :brand=> 'nike', :size=> 8 }
{:code => '456', :brand=> 'adidas', :size=> 4 }
{:code => '789', :brand=> 'converse', :size=> 10 }
But when I run my script it maps the codes right, but overwrites the #brand, #size variables and just returns the values of the last loop.
Not sure how to get all my variables into the hashes?
In your code example, the variables are all declared as being instance variables, because they're prefixed with #.
However, the variables within the loop are simply working/temporary variables, not instance variables. Additionally, x.get_some_data is probably not working, since x is just the loop variable and contains 456, abc, etc., and not an object with the desired method. Thus, the following code should produce your desired result:
def get_data(codes)
result = []
codes.each do |code|
y = get_some_data(code)
result << {
code: code,
brand: y['brand'],
size: y['size']
}
end
result
end
This is a very verbose example; you can put the whole logic in map, if the return value of get_some_data permits it.
A more elegant version would utilize Enumerable#each_with_object (the Array class includes Enumereable):
def get_data(codes)
codes.each_with_object([]) do |code, result|
y = get_some_data(code)
result << {
code: code,
brand: y['brand'],
size: y['size']
}
end
end
Thanks, Cary Swoveland, for pointing this out!
This should do the work
#codes.map{|code| {:code => code, :brand => code.get_some_data['brand'], code.get_some_data['size']}}
But, I'm really not sure what String.get_some_data will give you. Eg:- '123'.get_some_data['brand']

ActiveRecord: How to set the "changed" property of an model?

For every model in ActiveRecord, there seems to be a private property called "changed", which is an array listing all the fields that have changed since you retrieved the record from the database.
Example:
a = Article.find(1)
a.shares = 10
a.url = "TEST"
a.changed ["shares", "url"]
Is there anyway to set this "changed" property yourself? I know it sounds hacky/klunky, but I am doing something rather unordinary, which is using Redis to cache/retrieve objects.
ActiveModel::Dirty#changed returns keys of the #changed_attributes hash, which itself returns attribute names and their original values:
a = Article.find(1)
a.shares = 10
a.url = "TEST"
a.changed #=> ["shares", "url"]
a.changed_attributes #=> {"shares" => 9, "url" => "BEFORE_TEST"}
Since there is no setter method changed_attributes=, you can set the instance variable by force:
a.instance_variable_set(:#changed_attributes, {"foo" => "bar"})
a.changed #=> ["foo"]
See this example from: Dirty Attributes
class Person
include ActiveModel::Dirty
define_attribute_methods :name
def name
#name
end
def name=(val)
name_will_change! unless val == #name
#name = val
end
def save
#previously_changed = changes
#changed_attributes.clear
end
end
So, if you have a attribute foo and want to "change" that just call foo_will_change!.

Build an array using inject

I'm trying to build an array using inject. I expect consents to be an array of ParticipantConsent objects.
Each ParticipantConsent object can :have_many ParticipantConsentSample objects.
I expect sc to contain an array of arrays of ParticipantConsentSample objects associated with every ParticipantConsent object associated with a Participant.
consents = ParticipantConsent.where(:participant_id => #participant.id).all
sample_consents = consents.inject { |sc, c| sc << ParticipantConsentSample.where(:participant_consent_id => c.id).all }
Currently getting back the contents of consents when I check the contents of sample_consents. Where am I going wrong? Thanks.
Since you just want an array of arrays obtained from ParticipantConsentSample, you don't really want inject, you want map:
sample_consents = consents.map do |c|
ParticipantConsentSample.where(:participant_consent_id => c.id).all
end
try the below:
sample_consents = consents.inject([]) do |sc, c|
sc << ParticipantConsentSample.where(participant_consent_id: c.id).to_a
sc
end
If you want sample_consents to be an array, you need to initialize it as one using an argument to inject:
sample_consents = consents.inject([]) { |sc, c| ... }

Create Hash iterating an array of objects

I have an object that has attributes name and data, among others. I want to create a hash that uses the name as the key and the data (which is an array) as the value. I can't figure out how to reduce the code below using map. Is it possible?
def fc_hash
fcs = Hash.new
self.forecasts.each do |fc|
fcs[fc.name] = fc.data
end
fcs
end
Use Hash[]:
Forecast = Struct.new(:name, :data)
forecasts = [Forecast.new('bob', 1), Forecast.new('mary', 2)]
Hash[forecasts.map{|forecast| [forecast.name, forecast.data]}]
# => {"mary"=>2, "bob"=>1}
def fc_hash
forecasts.each_with_object({}) do |forecast, hash|
hash[forecast.name] = forecast.data
end
end
I always use inject or reduce for this:
self.forecasts.reduce({}) do |h,e|
h.merge(e.name => e.data)
end
Hash[*self.forecases.map{ [fc.name, fc.data]}.flatten]
With Ruby 2.1 and onward, you could also use Array#to_h
self.forecasts.to_h { |forecast| [forecast.name, forecast.data] }

Resources