This is kind of a tough problem to explain, because I don't really know what is going on. I've made a repository with instructions of how to reproduce:
https://github.com/bricker/cache_nested_serialized_attributes
Basically:
User has_many Posts
Post has serialized attribute :metadata
And to reproduce:
users = User.first(2)
users.first.posts.first
users.last.posts.first
dump = Marshal.dump(users)
Marshal.load(dump)
=> [#<User id: 1, name: "bricker", created_at: "2013-04-24 06:26:03", updated_at: "2013-04-24 06:26:03">,
:#new_record]
You can see the unexpected output in that final line. It only seems to occur under those specific conditions. Just calling users.first.posts works fine. Not calling .posts at all works fine. It's only when I load a specific post from a user before dumping that this happens.
The main difference I notice between Rails 4 (which works properly) and Rails 3.2.13 (which doesn't work) is that when calling Marshal.dump(users) in Rails 3, the Posts are reloaded:
dump = Marshal.dump(users)
Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1
Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 2
... which doesn't happen in Rails 4.
I wish I could explain better but it's such an obscure problem. Please ask questions if you need clarification, and look at or clone the repo.
This is tested and confirmed on a vanilla Rails 3.2.13 (see repository). This behavior does not happen in Rails 4.
Thanks!
This issue is fixed on Rails master, and a simple patch is pending review on 3-2-stable. I've applied the patch manually in an initializer and it fixes the problem.
See here for the patch: https://github.com/rails/rails/issues/10322#issuecomment-16913855
Related
I want to remove an element from a has_many relationship collection without destroying the element.
This works for me with this method:
current_user.flats.delete(Flat.find(7))
When I try to do a similar thing on the rails console, it destroys the whole object in the database:
irb(main):018:0> current_user.houses.delete(House.find(10))
SQL (13.4ms) DELETE FROM "cities_houses" WHERE "cities_houses"."city_id" = ? [["city_id", 10]]
SQL (0.8ms) DELETE FROM "houses" WHERE "houses"."id" = ? [["id", 10]]
As you can see, it removes the whole house object from it's own table.
What makes even less sense: It tries to remove an entry on the join table "cities_houses" using the given house_id (10) as parameter for the city_id to remove the element?
I don't get in general why it tries to update this join table. My command had nothing to do with it...
I'm using Rails version: 5.1.1 and Ruby version: 2.4.1 (x86_64-linux)
I found a solution to solve the problem!
This doesn't work, when dependent: :destroy is enabled.
current_user.houses.delete(House.find(10))
The solution is pretty obvious: for a has_many/belongs_to-association, you can just update the value user_id of the flat to nil. I knew this way, but what I've tried first didn't worked:
House.find(10).user_id = nil
House.find(10).save
The updated value will be changed and the change is immediately forgotten, if it is not stored in a variable.
This works:
house = House.find(10)
house.user_id = nil
house.save
Another solution, without loading the house model first:
House.where(id: 10).update_all(user_id: nil)
When testing some things in the rails console I noticed this strange thing happening when I call a record and attempt to save it.
2.1.5 :026 > p = WorkOrder.first
WorkOrder Load (0.4ms) SELECT `work_orders`.* FROM `work_orders` ORDER BY `work_orders`.`id` ASC LIMIT 1
=> #<WorkOrder id: 3, client_id: 4, created_at: "2015-06-17 17:12:07", updated_at: "2015-06-17 17:12:07", dueDate: "2015-07-17", number: "0221506-003", project_type_id: 2, monthlySequenceNumber: "003", projectDescription: "Project", status_id: 1, labels_id: nil>
2.1.5 :027 > p.save
(0.2ms) BEGIN
ProjectType Load (0.5ms) SELECT `project_types`.* FROM `project_types` WHERE `project_types`.`id` = 2 LIMIT 1
(0.1ms) COMMIT
=> true
Why does it appear to be performing a select on the associated object? Also the records are not being committed back to the database. What am I missing that causes it to behave in such a seemingly strange way?
EDIT:
What prompted me to start try to save records that I pulled from the database was that I had an identical issue doing something like
p.delete
and then
p.save
which would return true, but would only perform that strange select on the Project Type
Why does it appear to be performing a select on the associated object?
This is possibly caused by validation code or callbacks in the WorkOrder model.
Also the records are not being committed back to the database. What am I missing that causes it to behave in such a seemingly strange way?
You have not modified the record, so the only field I would expect to be updated would be updated_at. It is possible to disable the timestamp feature for ActiveRecord. Have you done that? (reference Is there a way to avoid automatically updating Rails timestamp fields?)
UPDATE
The same thing happens when I tested p.delete followed by p.save, the result is true. This could be a bug, but I have not researched it enough to determine that yet.
After a quick look in the ActiveRecord source I think that what happens is that since you have first deleted your record (p), 0 rows in the database match that record's id (p.id). That means that when you run p.save 0 rows get updated (update instead of insert because the record is considered persisted). That number of rows gets compared with false here so that 0 != false returns true.
Due to the fact that you haven't changed any attribute, just try p.touch instead of p.save. This should save the record anyway.
I know i'm mis-understanding how ActiveRecord maintains integrity/works: hoping someone can clarify for me explaining why the following is not working?
We have an abnormal situation: our rails app calls a compiled binary that accesses (and creates a new table rows, it does not update existing) in a shared database. We therefore run config.use_transactional_fixtures = false in rails_helper (other wise we get savepoint errors).
The data needs to commit within the scenario so this legacy app can access the data in the database during the test.
During a test we are setting up data via eval(rubyexpression) (see below for full code)
"provider = Provider.create({:provider_reseller_phone_number => '0200000000', :provider_registered_business_name => 'ProviderReseller', :provider_name => 'providerwithzero'})"
NOTE:
i know we should be using factorygirl for this, thats a different
long story
there is no additional provider model code e.g. callbacks,
hooks are anything
using debugger to pause the test (line 22), the data is not saved to the database, but it is there once the rspec completes.
We cannot figure out why!? surely data is committed after each transaction e.g. eval?
appreciate any guidance / learnings?
we've tried
using new + "save" on the provider variable but it isn't populated by the eval.
config.use_transactional_fixtures = true but this breaks our other need e.g. external process accessing DB
searching for ways to flush (But only seems to apply to "transactions')
tried searching for ways of committing "save_points" (no luck)
provider.create!
based on ()Rails 3: ActiveRecord observer: after_commit callback doesn't fire during tests, but after_save does fire run_callbacks(:commit)
spec_test_eval.rb
require 'rails_helper'
describe 'trying to test using rails populated data for external process' do
it 'populates provider and tests external process' do
initial_data = "provider = Provider.create({:provider_reseller_phone_number => '0200000000', :provider_registered_business_name => 'ProviderReseller', :provider_name => 'providerwithzero'})"
eval(initial_data)
debugger
expect Provider.all.count.eql?(1)
# using mysql to check providers table its empty
exec_path_str = "#{EXTERNALPROCESS} 1 1"
stdop_content = `#{exec_path_str}`
end
end
test.log output
ActiveRecord::SchemaMigration Load (0.2ms) SELECT `schema_migrations`.* FROM `schema_migrations`
(0.1ms) BEGIN
(0.1ms) SAVEPOINT active_record_1
SQL (0.2ms) INSERT INTO `providers` (`created_at`, `provider_name`, `provider_registered_business_name`, `provider_reseller_phone_number`, `updated_at`) VALUES ('2014-12-27 03:33:21', 'providerwithzero', 'ProviderReseller', '0200000000', '2014-12-27 03:33:21')
(0.1ms) RELEASE SAVEPOINT active_record_1
(0.2ms) SELECT COUNT(*) FROM `providers`
(0.4ms) ROLLBACK
So it seems that its the DatabaseCleaner gem thats causing this behaviour.
Having understood truncation, transaction etc. differences (database cleaner strategies we had transaction enabled which forces the surrounding transaction and rollback. But using DatabaseCleaner.strategy = :truncation) allows each ActiveRecord action to commit to the database. At a speed cost.
Given this does slow up the tests and we only need on some special tests, now searching solutions for different strategies based on flags/attributes.
I'm encountering a strange issue that must be user error on my part but can't figure it out.
I'm using Ruby 1.9.3-p194, Rails 4.01, PostgreSQL.
I have a model, Customer, with a column called data that is a hstore type. For some reason, I am not able to update the data (hstore) column with any new key/values nor can I update an existing key's value. I can do an insert and specify any key/values w/o any issue.
Customer id: 1, first_name: "Mark", last_name: "Test", data: {"balance"=>"0"}, created_at: "2013-11-27 14:39:09", updated_at: "2013-11-27 14:39:09"
c.data["balance"] = "100"
c.save
(0.2ms) BEGIN
(0.3ms) COMMIT
=> true
If I do an update_attributes, it does save it.
c.update_attributes({:data => {"balance" => "343"}})
I don't see any errors or exceptions when I used c.save!. Anyone have any ideas?
I still needed this to work now and couldn't wait for the bug to be solved, so here's my workaround:
c.data["balance"] = "100"
c.data_will_change!
c.save
... and it will save to the DB!
The "attribute_name_will_change!" method isn't that well documented and can be found in the introduction to the Active Model Dirty module: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
Ok finally found an answer to my question. Turns out that it's a bug within Rails 4.0.
"ActiveRecord Hstore bug: can't update a key in the hash"
https://github.com/rails/rails/issues/6127
Upgraded Hstore docs:
http://edgeguides.rubyonrails.org/active_record_postgresql.html#hstore
They're working to use the same solution for Hstore and Json object updating.
This will be fixed on Rails 4.2.
Pull-request: https://github.com/rails/rails/pull/15674
I have a Model called Invitation which has an attribute called code. The application's goal is to find the correct invitation with a code that is entered somewhere.
My problem is, however, that even though the input is correct, ActiveRecord can't seem to find any results while querying in the database. I've created this small test to illustrate the problem:
ruby-1.9.2-p290 :003 > code = Invitation.first.code
Invitation Load (0.4ms) SELECT "invitations".* FROM "invitations" LIMIT 1
=> "86f50776bf"
So at this point I've loaded this invitation's code in a variable
ruby-1.9.2-p290 :004 > i = Invitation.where(:code => code)
Invitation Load (0.2ms) SELECT "invitations".* FROM "invitations" WHERE "invitations"."code" = '86f50776bf'
=> []
And the response of the query is an empty array, even though the code comes straight from the database. When using code == Invitation.first.code to see if the values are equal, it returns true. I already checked both the Ruby and database's data types, they're all Strings.
What can cause this? and how can I fix this?
Based on your comment, it could be the case that the column is not VARCHAR but CHAR, or it contains trailing spaces that are being trimmed off by the ActiveRecord ORM layer or the database driver. 'foo' and 'foo ' are not equivalent, but they are LIKE enough to match.
You may want to switch that column to variable length, or to adjust your query to test: RTRIM(code)=?
I found the solution when I stumbled upon this answer:
In Ruby 1.9, all strings are now encoded. By default, all strings should be UTF-8, however, SecureRandom.hex(30) returns an encoding of ASCII-8BIT.
Adding .force_encoding('UTF-8') to the key when it's being executed solves the problem :)
#Marco,
How you declare the code variable? As String?
Example:
code = "86f50776bf"
or
code = '86f50776bf'
?