merge 2 array of hashes based on different keys in rails - ruby-on-rails

I have 2 query results one of them is an array of hashes like this
[{"user_id"=>"1", "latlng"=>[#<BigDecimal:7fc67f8412d0,'0.43653226E2',18(36)>, #<BigDecimal:7fc67f840560,'-0.793831843E2',18(36)>], "loc"=>["Toronto", "Ontario", "Canada"]}, {"user_id"=>"2", "latlng"=>[#<BigDecimal:7fc67f84a8f8,'0.43653226E2',18(36)>, #<BigDecimal:7fc67f849d18,'-0.793831843E2',18(36)>], "loc"=>["Toronto", "Ontario", "Canada"]}, {"user_id"=>"3", "latlng"=>[#<BigDecimal:7fc67f848828,'0.43653226E2',18(36)>, #<BigDecimal:7fc67f848210,'-0.793831843E2',18(36)>], "loc"=>["Toronto", "Ontario", "Canada"]}, {"user_id"=>"4", "latlng"=>[#<BigDecimal:7fc67f852620,'0.43653226E2',18(36)>, #<BigDecimal:7fc67f851b30,'-0.793831843E2',18(36)>], "loc"=>["Toronto", "Ontario", "Canada"]}, {"user_id"=>"5", "latlng"=>[#<BigDecimal:7fc67f85ae88,'0.43653226E2',18(36)>, #<BigDecimal:7fc67f85a9b0,'-0.793831843E2',18(36)>], "loc"=>["Toronto", "Ontario", "Canada"]}]
the second is an active record relations object of users,
<ActiveRecord::Relation [#<User id: 4, email: "hello.misc#gmail.com", username: "steve", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 13, current_sign_in_at: "2017-02-18 21:16:17", last_sign_in_at: "2017-01-14 20:32:57", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", confirmed_at: "2016-12-13 01:42:57", confirmation_sent_at: "2016-12-13 01:42:55", unconfirmed_email: nil, failed_attempts: 0, unlock_token: nil, locked_at: nil, created_at: "2016-12-13 01:42:55", updated_at: "2017-02-18 22:03:46", slug: "user2", uuid: "xdn5n5z3fmr4", impressions_count: 1, likers_count: 3, lat: #<BigDecimal:7fc67fc39130,'0.0',9(27)>, lng: #<BigDecimal:7fc67fc38c30,'0.0',9(27)>, currently_online: false, status: "unverified", deleted_at: nil>, #<User id: 5, email: "jack#gmail.com", username: "user21", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, current_sign_in_at: "2017-02-17 02:49:07", last_sign_in_at: "2017-02-17 02:49:07", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", confirmed_at: "2017-02-17 02:49:10", confirmation_sent_at: "2017-02-17 02:49:07", unconfirmed_email: nil, failed_attempts: 0, unlock_token: nil, locked_at: nil, created_at: "2017-02-17 02:49:07", updated_at: "2017-02-17 02:49:43", slug: "user21", uuid: "xtffdh2ajnp7", impressions_count: 1, likers_count: 0, lat: #<BigDecimal:7fc67fca6758,'0.0',9(27)>, lng: #<BigDecimal:7fc67fca5ec0,'0.0',9(27)>, currently_online: false, status: "unverified", deleted_at: nil>]>
What I want to do, is to merge the first array of hashes into the activerecord relations based on the foreign key of the first array,
so if the first array has an user_id of 3, i want to insert that hash into that active record of the user with an ID of 3 on the active record object.
I came up up with this
index = a1.group_by{|entry| entry["id"]}
i2= a2.map{|entry| (index[entry.id] || []).reduce(entry, :merge) }
but its not merging them together at all.. what am I doing wrong?
edit: Not all of the first array is relevent, I only want to selectively get the item from the first array based on whats returned in the active record collection of the second query
edit2: I am not looking to save the data back, I just want to merge the data set to display it back in a view template.

If you use Array#map or collect on the collection, you're going to end up with an array instead of an ActiveRecord collection, which may not be what you want.
You could add virtual attributes to the User model and then iterate over the collection:
# app/models/user.rb
attr_accessor :latlon, :loc
users.each do |user|
if location = location_array[user.id.to_s]
user.latlon = location['latlng']
user.loc = location['loc']
end
end
This probably won't be very efficient, but for a small paginated dataset it might not be a big deal.
Another option would be to use a decorator pattern, which acts like a wrapper around your user object. In this case you would add your location attributes to the decorator rather than injecting them into the collection. Have a look at the Draper gem for a good overview.

If I understood you correctly, you can do something like this:
# users is an array of ActiveRecord::Relation
# extra_data is the hash
users.each do |user|
# .detect basically will return an object (first appearance) or nil
data = extra_data.detect{|d| d["user_id"].to_i == user.id}
if data.present?
user.lat = data["latlng"][0]
user.lng = data["latlng"][1]
# if you want to attach "loc", add a attr_accesor :loc in your Use model
# user.loc = data["loc"]
end
end
If you want to know more about detect method, read the docs.

Related

Spree: Return only Orders with Line Items in them

I'm having trouble setting up the controller in a spree app so it only returns orders with line_items in them.
<Spree::Order id: 1057, number: "R498797188", item_total: #<BigDecimal:7f90c2acf4b8,'0.999E1',18(18)>, total: #<BigDecimal:7f90c2acf1e8,'0.1098E1',18(18)>, state: "payment", adjustment_total: #<BigDecimal:7f90c2ace770,'0.0',9(18)>, user_id: nil, completed_at: nil, bill_address_id: 1814, ship_address_id: 1815, payment_total: #<BigDecimal:7f90c2acc358,'0.0',9(18)>, shipping_method_id: nil, shipment_state: nil, payment_state: nil, email: nil, special_instructions: nil, created_at: "2015-09-10 22:31:23", updated_at: "2015-09-10 22:33:03", currency: "USD", last_ip_address: "127.0.0.1", created_by_id: nil, shipment_total: #<BigDecimal:7f90c4b721e8,'0.199E1',18(18)>, additional_tax_total: #<BigDecimal:7f90c4b71fb8,'0.0',9(18)>, promo_total: #<BigDecimal:7f90c4b71e78,'0.0',9(18)>, channel: "spree", included_tax_total: #<BigDecimal:7f90c4b71798,'0.0',9(18)>, item_count: 1, approver_id: nil, approved_at: nil, confirmation_delivered: false, considered_risky: false>
There is a record on Spree::Order called item_count that needs to be > 0, but I don't know what the syntax needs to be in the controller?
#orders = Spree::Order.all.where(:item_count > 0) #this returns the following error: comparison of Symbol with 0 failed
This seems simple, but any help would be greatly appreciated! Thanks!
You can use symbol to check for absolute value, like where(item_count: 0), but not for comparison like that.
Inside where, when you are using symbols, you are actually working on a Hash object.
.where(item_count: 0)= .where({item_count: 0})
and to work with a hash, you should have key-value pair like above, not comparison. {:item_count > 0} is an invalid hash syntax.
For comparison in your case, use string:
#orders = Spree::Order.where('item_count > 0').all
Also, you should use .all, at the end of relation, if all needed. all returns the scope object and is usually meant to fetch all the records matching the query before it.

Rails 4 to_json produces unexpected Exception nil is not a symbol

I am in the process of upgrading a Rails 3 application to Rails 4. In Rails 3 the json serialization of a hash containing an array of ActiveRecord objects was working correctly; now in Rails 4 it is having unpredictable results.
Here is an example object that fails with TypeError Exception: nil is not a symbol on Rails 4
{1230 =>
[
#<QuestionAnswerResponse response_id: 127, response_set_id: 6, response_group: nil, question_data_export_identifier: "is_co_pi_involved", answer: "No", question_id: 1230, answer_id: 2077, response: "">,
#<QuestionAnswerResponse response_id: 131, response_set_id: 6, response_group: nil, question_data_export_identifier: "is_co_pi_involved", answer: "No", question_id: 1230, answer_id: 2077, response: "">
]
}
Now if I take another similar object; hash containing an array of ActiveRecord objects and run to_json it works on this one...
{1234 =>
[
#<Response id: 1, response_set_id: 2, question_id: 4, answer_id: 2, datetime_value: nil, integer_value: nil, float_value: nil, unit: nil, text_value: nil, string_value: nil, response_other: nil, response_group: nil, created_at: "2014-05-30 21:17:23", updated_at: "2014-05-30 21:17:23", survey_section_id: 1, api_id: "f44b22ba-a93b-477f-8a7f-c5b4566338f0", attach_file_name: nil, attach_content_type: nil, attach_file_size: nil, attach_updated_at: nil>,
#<Response id: 2, response_set_id: 2, question_id: 10, answer_id: 10, datetime_value: nil, integer_value: nil, float_value: nil, unit: nil, text_value: "test", string_value: nil, response_other: nil, response_group: nil, created_at: "2014-05-30 21:17:23", updated_at: "2014-05-30 21:17:23", survey_section_id: 1, api_id: "e7fa8aa2-6e47-4f88-8802-949fdc902a2e", attach_file_name: nil, attach_content_type: nil, attach_file_size: nil, attach_updated_at: nil>
]
}
The view backing my QuestionAnswerResponse model does not have an id column and I was not setting a primary key in the model. In the capacity that I use this model I do not need a primary key; this is a read only view used to more easily access some complex key/value pairings directly instead of through more complex logic.
In Rails 3 this worked fine; in Rails 4 when you access a model without a primary key you end up with an attribute that looks like this in your hash nil => nil
The problem is actually up at the ActiveRecord level, but wasn't actually causing a problem until I attempt to do json serialization; at which point in time an attempt is made to call nil.to_sym which raises the exception.
This seems like a bug in ActiveRecord to me; but for now I have worked around it by manually setting a primary key on my model.

Rails update individual attribute not working

I have the following scenario:
A user cancels their account, and optionally gives a reason.
The reason is saved, and the user is soft_deleted.
For some reason my code to set the reason doesn't result in any updates being saved.
Here is my code:
class User < ActiveRecord::Base
def set_cancellation_reason(reason)
cancellation_reason = reason
save!
end
end
What is wrong here that cancellation_reason is not getting persisted?
This is what happens if I run the code from the console:
u = User.first
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 1, email: "leebrooks0#gmail.com", encrypted_password: "$2a$15$GQ.WHk.nxlArc668bf3NW.WFJVV.ost3R85PGs6ePYaT...", role_id: nil, reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 11, current_sign_in_at: "2014-03-21 12:08:54", last_sign_in_at: "2014-03-21 12:06:31", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", confirmation_token: nil, confirmed_at: "2014-03-21 10:59:27", confirmation_sent_at: nil, unconfirmed_email: nil, failed_attempts: 0, unlock_token: nil, locked_at: nil, cancellation_reason: nil, created_at: "2014-03-21 10:59:27", updated_at: "2014-03-21 12:12:29", deleted_at: nil>
2.1.1 :008 > u.set_cancellation_reason("hogwash")
(0.5ms) BEGIN
(0.4ms) COMMIT
=> true
I might nbe missing something on how it is being called, but I would do the following. I am assuming this is being called from a controller or view.
Called by using myuser.set_cancellation_reason("don't like it")
def set_cancellation_reason(reason)
self.cancellation_reason = reason
self.save!
end
I'm also assuming you set the cancelled flag somewheer else otheriwse you need
self.cancel_flag = true
When doing cancellation_reason = reason, it will create a cancellation_reason variable instead of calling the dynamic method cancellation_reason= defined by Active Record.
self is the default receiver but when the expression can be ambiguously considered to be an assignation, the assignation wins.
You can use self.cancellation_reason = reason to be sure to pick up the rails method but I prefer doing the following :
update_attribute(:cancellation_reason, reason)
I think it's more clear, and you don't need an extra line for saving.

Ruby on Rails: more verbose tests

This test keeps failing and I don't know why:
test "correctly formatted profile_name2" do
user = User.new(first_name: 'Jim', last_name: 'Johnson', email: 'jim#teamtreehouse.com', password: 'awfawwf', profile_name: "jimmy")
puts user.errors.inspect
assert user.valid?
end
I tried to find out by that puts user.errors.inspect statement, but I get back an array (I think) that simply lists database input rather than precisely what's failing.
For clarification:
<ActiveModel::Errors:0x00000103c8ad30 #base=#<User id: nil, first_name: "Jim", last_name: "Johnson", profile_name: "jimmy", email: "jim#teamtreehouse.com", encrypted_password: "$2a$04$LTOb5O.gG0DEITsb/HDOb.fPLP83LaXzKlEerwCDE1og...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, confirmation_token: nil, confirmed_at: nil, confirmation_sent_at: nil, unconfirmed_email: nil, failed_attempts: 0, unlock_token: nil, locked_at: nil, authentication_token: nil, created_at: nil, updated_at: nil>, #messages={}>
In future tests, what statements are used in tests to print to screen explicitly what's going wrong?
Instead of outputting more verbose information within your tests, it might also help to set the TESTOPTS argument to get verbose output when you run your tests.
For example you would set it like this:
rake test TESTOPTS="-v"

Finding elements in array of hashes

I'm trying to build a model of products which has many components. Some components are optional and depend on the choice the user is making to enable them or not.
I have two models, one is configuration and the other is elements (of that configuration).
At the beginning I bring all the elements of the array, and then create another array of those which will be shown by default.
But when I write the following code it gives me an error despite both objects being arrays of hashes.
So I bring my first array of all elements:
irb(main):252:0* #all = Configuration.find(1).elements
=> [#<Element id: 1, name: "elem1", quantity: 1, position: 1, subposition: nil, created_at: nil, updated_at: nil, configuration_id: 1>, #<Element id: 2, name: "elem2", quantity: 2, position: 2, subposition: 1, created_at: nil, updated_at: nil, configuration_id: 1>, #<Element id: 3, name: "elem3", quantity: 3, position: 2, subposition: 2, created_at: nil, updated_at: nil, configuration_id: 1>, #<Element id: 4, name: "elem4", quantity: 4, position: 3, subposition: nil, created_at: nil, updated_at: nil, configuration_id: 1>]
Then I filter to be only those that have a subposition nil or 1
irb(main):253:0> #default = #all.where(:subposition=>nil).concat(#all.where(:subposition=>1))
=> [#<Element id: 1, name: "elem1", quantity: 1, position: 1, subposition: nil, created_at: nil, updated_at: nil, configuration_id: 1>, #<Element id: 4, name: "elem4", quantity: 4, position: 3, subposition: nil, created_at: nil, updated_at: nil, configuration_id: 1>, #<Element id: 2, name: "elem2", quantity: 2, position: 2, subposition: 1, created_at: nil, updated_at: nil, configuration_id: 1>]
So far so good, as you can see, Elem3 is not being shown in #default as it doesn't meet the requiements.
The problem comes when I try to play with the arrays as I need to perform certain operations.
irb(main):257:0> #all.where(:position =>1)
=> [#<Element id: 1, name: "elem1", quantity: 1, position: 1, subposition: nil, created_at: nil, updated_at: nil, configuration_id: 1>]
But the same operation in #default will fail,
irb(main):258:0> #default.where(:position =>1)
NoMethodError: undefined method `where' for #<Array:0x2641660>
Now, they're both arrays of hashes and look the same, why is the same method failing in the second case?
Thanks a lot for your help!
Throughout your code, #all is an ActiveRecord::Relation, not an array. This lets you perform the standard .where call (among others). When you assigned to #default, you used .concat which evaluated the query and assigned an actual array to #default.
You might try a different approach in your second code block. Maybe something like this:
#default = #all.where("subposition is null or subposition = ?", 1)
Well, your problem is that concat transforms a collection into an array.
I'd replace:
irb(main):253:0> #default = #all.where(:subposition=>nil).concat(#all.where(:subposition=>1))
by:
#default = #all.where("subposition = '1' OR subposition = nil") #I'm unsure of the nil in the statement, I nerver remember, try NULL if it fails
This way, you make only one db query and you keep an ActiveRecord collection.
Thus, you'll be able to chain other where conditions on it.

Resources