Related
I've been getting intermittent errors while seeding with rails. I'm hoping someone can help provide some insight into the different types of the User class.
The error in full:
ActiveRecord::AssociationTypeMismatch: User(#35560) expected, got #<User id: "bedc7c4e-cdd2-4ea1-a7ee-4e6642467fba", email: "phil#email.domain", jti: "7376cf41-7f88-407d-8365-1e311d946b88", ios_device_token: nil, fcm_device_token: nil, first_name: "Phil", last_name: "6", phone_number: nil, date_of_birth: nil, super_user: true, created_at: "2023-02-08 08:16:37.559974000 +0000", updated_at: "2023-02-08 08:16:37.559974000 +0000"> which is an instance of User(#22700)
The code which causes it:
user = User.new(
first_name: 'Phil',
last_name: '6',
email: 'phil#email.domain',
super_user: true,
password: 'test1234'
)
user.skip_confirmation!
user.save!
organisation = Organisation.find_by_name('Team')
Membership.create!(
user:,
organisation:,
verified: true,
verified_at: now,
organisation_admin: true,
shift_admin: true,
email: 'phil.6#group.com',
email_confirmed: true,
category: organisation.categories.find_by_name('Developer')
)
organisation = Organisation.find_by_name('Test Org')
membership = Membership.create!(
user:,
organisation:,
verified: true,
verified_at: now,
email: 'phil#testorg.com',
email_confirmed: true
)
If I pause execution before the error I can see that user == User.first is false despite User.first and user being these two lines, which are visually identical:
#<User id: "6ce62b08-cf4c-4bfa-878a-02a1ed889c69", email: "phil#email.domain", jti: "710948b6-5f4f-40ea-ab9f-df8e3b1219c3", ios_device_token: nil, fcm_device_token: nil, first_name: "Phil", last_name: "6", phone_number: nil, date_of_birth: nil, super_user: true, created_at: "2023-02-08 08:17:06.024800000 +0000", updated_at: "2023-02-08 08:17:06.024800000 +0000">
#<User id: "6ce62b08-cf4c-4bfa-878a-02a1ed889c69", email: "phil#email.domain", jti: "710948b6-5f4f-40ea-ab9f-df8e3b1219c3", ios_device_token: nil, fcm_device_token: nil, first_name: "Phil", last_name: "6", phone_number: nil, date_of_birth: nil, super_user: true, created_at: "2023-02-08 08:17:06.024800000 +0000", updated_at: "2023-02-08 08:17:06.024800000 +0000">
It's the same thing if I compare user.class and User.first.class, they look the same but a comparison evaluates to false.
Am I doing something to mutate the local variable?
What you should be doing here is to create an assocation:
class User < ApplicationRecord
has_many :memberships
end
Then you create the memberships through that assocation instead:
user = User.create!(
first_name: 'Phil',
last_name: '6',
email: 'phil#email.domain',
super_user: true,
password: 'test1234',
confirmed_at: Time.current # the easy way to skip Devise::Confirmable
)
# make sure you use the bang method so that you're not just getting a nil
organisation = Organisation.find_by_name!('Test Org')
user.memberships.create!(
organisation: organisation,
verified: true,
verified_at: now,
organisation_admin: true,
shift_admin: true,
email: 'phil.6#group.com',
email_confirmed: true,
category: organisation.categories.find_by_name!('Developer')
)
Not sure why this is happening:
2.0.0p247 :001 > User.column_names
=> ["id", "user_name", "email", "password_digest", "created_at", "updated_at", "register_key", "culminated", "remember_token", "register_token_created_at", "profile_image", "licence_image", "first_name", "last_name", "nearest_town"]
2.0.0p247 :002 > user1 = User.create(user_name: 'james', email: 'james#killbots.com', first_name:'jj', last_name:'jj', nearest_town:'mordor')
=> #<User id: nil, user_name: "james", email: "james#killbots.com", password_digest: nil, created_at: nil, updated_at: nil, register_key: nil, culminated: nil, remember_token: nil, register_token_created_at: nil, profile_image: nil, licence_image: nil, first_name: "jj", last_name: "jj", nearest_town: "mordor">
2.0.0p247 :003 > user1.update(user_name: 'killo')
=> false
Rather than a solution, how would you go about debugging this problem from the console?
Your User record is not saved, probably because of failed validation.
You should check its validity with:
user1.valid?
and show errors:
user1.errors.full_messages
If you notice:
2.0.0p247 :002 > user1 = User.create(user_name: 'james', email: 'james#killbots.com', first_name:'jj', last_name:'jj', nearest_town:'mordor')
=> #<User id: nil, user_name: "james", email: "james#killbots.com", password_digest: nil, created_at: nil, updated_at: nil, register_key: nil, culminated: nil, remember_token: nil, register_token_created_at: nil, profile_image: nil, licence_image: nil, first_name: "jj", last_name: "jj", nearest_town: "mordor">
User record (user1) was not created at all. User id is nil. You must be having some failed validations. If the record would have been successfully created in the database then your user id would never be nil as its the primary key.
Try with User.create! instead so you know that why the record was not created, you will get the exact exception raised. For example:
2.0.0p247 :002 > user1 = User.create!(user_name: 'james', email: 'james#killbots.com', first_name:'jj', last_name:'jj',
nearest_town:'mordor')
Seems like your object is not valid. As Kirti pointed out it has not been persisted to the database as no primary key id has been returned. Checking the validity of your object would give you more information on what is up with your object. Checkout rails guides for a breakdown of validation.
I'm capturing "point in time" (audit) data about certain model records using the inspect method to dump the state of the record to a string. For example after I've stored a User record in the variable a_user I call inspect and store the results in a string variable archived_user_data:
1.9.3p484 :045 > archived_user_data = a_user.inspect
=> "#<User id: 17, email: \"ray.johnson#breakfs.com\", encrypted_password: \"$2a$10$v3CJZftIyDW/XZpktXXdMOuN1IxMoVmaofcIqEB6kBV....\", created_at: \"2014-04-05 21:42:09\", updated_at: \"2014-04-05 21:43:25\", account_id: 9>"
1.9.3p484 :046 > archived_user_data
=> "#<User id: 17, email: \"ray.johnson#breakfs.com\", encrypted_password: \"$2a$10$v3CJZftIyDW/XZpktXXdMOuN1IxMoVmaofcIqEB6kBV....\", created_at: \"2014-04-05 21:42:09\", updated_at: \"2014-04-05 21:43:25\", account_id: 9>"
When the archived_user_data is retrieved sometime in the future, I need to convert it into a hash. Is there a simple way to do this? It looks like hashes converted to strings are usually converted back using eval, but in this case eval(archived_user_data) returns nil.
If you are still free to use Marshal, fine! If not, I suggest you peel down the string to the hash part using
s = archived_user_data.match(/#<User (.*)>/)[1]
after which you can reconstruct the hash using eval
eval("{" + s + "}")
Just do as below using attributes :
Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
archived_user_data = a_user.attributes
You can use Marshal to dump and store any Ruby Object.
Example:
(Using reference from #Arup's code)
data_hash = a_user.attributes
dump_string = Marshal.dump(data_hash)
retrieved_hash = Marshal.load(dump_string)
You can store dump_string in file or database or in any other storage area.
EDIT
Specific case:
2.1.0 :013 > {:a => "b"}.inspect
=> "{:a=>\"b\"}"
2.1.0 :014 > "{id: 17, email: \"ray.johnson#breakfs.com\", encrypted_password: \"$2a$10$v3CJZftIyDW/XZpktXXdMOuN1IxMoVmaofcIqEB6kBV....\", created_at: \"2014-04-05 21:42:09\", updated_at: \"2014-04-05 21:43:25\", account_id: 9}"
=> "{id: 17, email: \"ray.johnson#breakfs.com\", encrypted_password: \"$2a$10$v3CJZftIyDW/XZpktXXdMOuN1IxMoVmaofcIqEB6kBV....\", created_at: \"2014-04-05 21:42:09\", updated_at: \"2014-04-05 21:43:25\", account_id: 9}"
2.1.0 :015 > eval("{id: 17, email: \"ray.johnson#breakfs.com\", encrypted_password: \"$2a$10$v3CJZftIyDW/XZpktXXdMOuN1IxMoVmaofcIqEB6kBV....\", created_at: \"2014-04-05 21:42:09\", updated_at: \"2014-04-05 21:43:25\", account_id: 9}")
=> {:id=>17, :email=>"ray.johnson#breakfs.com", :encrypted_password=>"$2a$10$v3CJZftIyDW/XZpktXXdMOuN1IxMoVmaofcIqEB6kBV....", :created_at=>"2014-04-05 21:42:09", :updated_at=>"2014-04-05 21:43:25", :account_id=>9}
You need to understand that hashes when inspected and stored as string aren't of the form:
"#<User id: 17, email: \"ray.johnson#breakfs.com\", encrypted_password: \"$2a$10$v3CJZftIyDW/XZpktXXdMOuN1IxMoVmaofcIqEB6kBV....\", created_at: \"2014-04-05 21:42:09\", updated_at: \"2014-04-05 21:43:25\", account_id: 9>"
But should be of the form:
"{id: 17, email: \"ray.johnson#breakfs.com\", encrypted_password: \"$2a$10$v3CJZftIyDW/XZpktXXdMOuN1IxMoVmaofcIqEB6kBV....\", created_at: \"2014-04-05 21:42:09\", updated_at: \"2014-04-05 21:43:25\", account_id: 9}"
You can eval and get back the hash if you modify your string to the format above. Refer to my three line example above.
When I comment out my after_save call back, my ActiveRecord associations work just fine. In Rails Console, you'd see:
> #report = Report.create :name => "foo"
=> #<Report id: 9, name: "foo", created_at: "2013-03-05 09:51:55", updated_at: "2013-03-05 09:51:55">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 18, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 09:52:32", updated_at: "2013-03-05 09:52:32", additive: false, instructions: nil>
> #report.questions
=> [#<Question id: 18, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 09:52:32", updated_at: "2013-03-05 09:52:32", additive: false, instructions: nil>]
> #question.reports
=> [#<Report id: 9, name: "foo", created_at: "2013-03-05 09:51:55", updated_at: "2013-03-05 09:51:55">]
However, the associations stop working when I add the following after_save callback to question.rb:
def create_matching_surveys
self.reports.each do |report|
report.reviews.each do |review|
review.competitors.each do |competitor|
competitor.surveys.find_or_create_by_question_id(self.id)
end
end
end
end
Then, in Rails Console, you get:
> #report = Report.create :name => "foo"
=> #<Report id: 13, name: "foo", created_at: "2013-03-05 10:20:51", updated_at: "2013-03-05 10:20:51">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 24, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 10:21:02", updated_at: "2013-03-05 10:21:02", additive: false, instructions: nil>
> #report.questions
=> [#<Question id: 24, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 10:21:02", updated_at: "2013-03-05 10:21:02", additive: false, instructions: nil>]
> #question.reports
=> []
This happens whether or not the report has reviews that have competitors.
The strange thing is I thought the callback was meant to happen after the question was saved? So by rights the association should save too before any of this happens, right?
How do I fix it?
UPDATE
I think I have to call the callback in the right spot in the object's life cycle, but I can't find that spot. Here's why I think this:
> #report = Report.create :name => "foo"
=> #<Report id: 20, name: "foo", created_at: "2013-03-05 12:29:35", updated_at: "2013-03-05 12:29:35">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 31, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 12:30:14", updated_at: "2013-03-05 12:30:14", additive: false, instructions: nil>
> #question.reports
=> []
> #question.update_attributes :description => "foo"
=> true
> #question.reports
=> [#<Report id: 20, name: "foo", created_at: "2013-03-05 12:29:35", updated_at: "2013-03-05 12:29:35">]
BTW, the method is now in question_observer.rb:
class QuestionObserver < ActiveRecord::Observer
def after_save(model)
model.reload
model.reports.reload
model.reports.each do |report|
report.reviews.each do |review|
review.competitors.each do |competitor|
competitor.surveys.find_or_create_by_question_id(model.id)
end
end
end
return true
end
end
The answer was to use a neat new callback hook called after_commit which was introduced with Rails 3.
See http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_commit.
The only issue is after_commit doesn't work "out of the box" with transactional fixtures, but there are plenty of solutions out there, and I found this one worked well for me: https://supportbee.com/devblog/2012/01/14/testing-after_commitafter_transaction-with-rspec/
If you look at the four method calls below, Service.first returns a Service object, Salon.first returns a Salon object, etc. But TransactionItem.first returns a Service object. Why could this be?
ruby-1.8.7-p334 :001 > Service.first
=> #<Service id: 147, name: "Fub", salon_id: 2, created_at: "2011-08-10 18:00:07", updated_at: "2011-08-10 18:00:12", price: nil, active: true, archived: true>
ruby-1.8.7-p334 :002 > Salon.first
=> #<Salon id: 1, name: "The Cheeky Strut", created_at: nil, updated_at: nil, address_id: nil, email: nil>
ruby-1.8.7-p334 :003 > Product.first
=> #<Product id: 1, name: "Herbal Essences Shampoo", retail_price: #<BigDecimal:10305f1f0,'0.1E2',9(18)>, wholesale_price: nil, sku: "", salon_id: 2, created_at: "2011-07-08 01:35:48", updated_at: "2011-07-08 01:35:48", archived: false>
ruby-1.8.7-p334 :004 > TransactionItem.first
=> #<Service id: 63, created_at: "2011-08-30 20:05:57", updated_at: "2011-08-30 20:05:57", price: #<BigDecimal:10303eba8,'0.18E2',9(18)>>
ruby-1.8.7-p334 :005 >
This is what my app/models/transaction_item.rb looks like:
class TransactionItem < ActiveRecord::Base
belongs_to :transaction
belongs_to :stylist
end
I blew away the TransactionItem table via a migration, then created a brand new migration to re-create it. That seems to have fixed the problem.