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.
Related
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.
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.
I'm playing on rails console and did the following steps:
agendamento = Agendamento.last
And got a record that looks like this
#<Agendamento id: 4, sala_id: 2, name: "Delta C", start_at: "2013-10-11 02:00:00", end_at: "2013-10-10 04:00:00", bookingDate: "2013-10-10",
bookingTime: "2000-01-01 23:00:00", bookingDuration: 2, approved: true, usuario_id: nil, created_at: "2013-10-05 02:24:00", updated_at: "2013-
10-05 02:24:00">
when I do agendamento.name I receive:
Delta C
and it works in all values, but not on sala_id, agendamento.sala_id returns
nil
This attribute is acessible in the model, why it is returning nil ?
Environment: Ruby 2 and Rails 4
If sala_id is a foreign key reference, have you tried invoking sala instead? That should give you the related object.
I've fixed this for my actual app fairly trivially by overriding the == operator, but it is driving me nuts and I haven't been able to find an explanation. As far as I know, ActiveRecord is supposed to determine the equality of two existing records just via the id field, right?
Apparently, no!
Loading development environment (Rails 3.0.4)
irb(main):001:0> c = ChallengeClaim.find(1)
=> #<ChallengeClaim id: 1, collection_id: 954, creation_id: nil, creation_type: nil, request_signup_id: 2, request_prompt_id: 5, claiming_user_id: 8, sent_at: nil, fulfilled_at: nil, defaulted_at: nil, created_at: "2011-09-23 04:39:07", updated_at: "2011-09-23 04:39:07">
irb(main):002:0> c2 = ChallengeClaim.find(2)
=> #<ChallengeClaim id: 2, collection_id: 954, creation_id: nil, creation_type: nil, request_signup_id: 2, request_prompt_id: 4, claiming_user_id: 8, sent_at: nil, fulfilled_at: nil, defaulted_at: nil, created_at: "2011-11-07 17:47:33", updated_at: "2011-11-07 17:47:33">
irb(main):003:0> c == c2
=> true
?!?!!??!
Any explanations gratefully welcomed so I can sleep at night again. :>
As Chris Heald pointed out above in the comments, indeed the comparison operator <=> was defined on ChallengeClaim!
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.