I'm trying to understand how to use hstore and it seems the database isn't updated if I try to modify a value in my hash.
In my rails console I do
u = User.new
u.hash_column = {'key' => 'first'}
u.save
I get a message in my console
(0.4ms) BEGIN
SQL (2.0ms) UPDATE "users" SET "hash_column" = $1, "updated_at" = $2 WHERE ...
(18.0ms) COMMIT
and when I check in the DB the column has the correct data
now when I try
u.hash_column['key'] = 'second'
the model seems correct in the console
puts u.hash_column
gives
{"key"=>"second"}
however when I try to save this
u.save
in my console I just get
(0.3ms) BEGIN
(0.2ms) COMMIT
with no update statement and when I check the DB the data hasn't changed.
if I change another attribute on the user model and save it hash_column still doesn't get updated. The only way I can change the hash_column in the database seems to be to assign to the complete hash like
u.hash_column = {'key' => 'second'}
is this how it is meant to work or am I doing something wrong?
This is due to the way that rails currently tracks changes. It will only track changes when you use the attributes setter (u.hash_column). If you update a value in the hash, you have to notify Rails that the value changed by calling u.hash_column_will_change!. This marks the value as dirty and the next time save is called, it will be persisted.
This also effects arrays and strings and dates. If you call u.string_value.gsub! or u.array_column << value those changes will not be persisted without calling the <column>_will_change! method for those columns
Related
I am pretty new to Ruby on Rails and I'm having some problems.
I have already checked out: This stackoverflow question about cloning a model
I am creating a new method for a model that looks like this:
def copy(new_period)
#copy = self.clone
#copy.report_id = Report.maximum(:report_id).next
#copy.period_id = new_period
#copy.save
end
I am trying to create a new instance of report that can be moved to the next year(period). When I run this method in irb I get:
irb(main):003:0> c = r.copy(5)
(1.8ms) SELECT MAX("reports"."report_id") AS max_id FROM "reports"
(0.8ms) BEGIN
(1.0ms) UPDATE "reports" SET "period_id" = 5 WHERE "reports"."report_id" = 438
(1.1ms) COMMIT
=> true
When I look in pgAdmin the new report isn't there. Could someone please explain to me what is going on when the console says "commit" and "=> true"? Does this not mean it has saved to the database?
You're updating the old model because the id of the model is still set. You can see that because your console shows an UPDATE instead of an INSERT statement.
From the Docs for ActiveRecord::Core#clone:
Identical to Ruby's clone method. This is a “shallow” copy. Be warned that your attributes are not copied. That means that modifying attributes of the clone will modify the original, since they will both point to the same attributes hash. If you need a copy of your attributes hash, please use the dup method.
ActiveRecord::Core#dup however results in a new record:
Duped objects have no id assigned and are treated as new records. Note that this is a “shallow” copy as it copies the object's attributes only, not its associations. The extent of a “deep” copy is application specific and is therefore left to the application to implement according to its need. The dup method does not preserve the timestamps (created|updated)_(at|on).
About your side questions:
BEGIN and COMMIT are used to start and end transactions in SQL.
true is the result of r.copy, which implicitly returns the value of #copy.safe, which returns true if the model was saved successfully.
Make the method as this and try:
def copy(new_period)
#copy = self.dup
#copy.report_id = Report.maximum(:report_id).next
#copy.period_id = new_period
#copy.save
end
Because to get a copy, use the clone (or dup for rails 3.1) method:
# rails < 3.1
new_record = old_record.clone
#rails >= 3.1
new_record = old_record.dup
So what is happening currently you are cloning and it is not making a new record it is just updating the previous one each time. If you read your queries carefully:
(1.8ms) SELECT MAX("reports"."report_id") AS max_id FROM "reports"
(0.8ms) BEGIN
(1.0ms) UPDATE "reports" SET "period_id" = 5 WHERE "reports"."report_id" = 438
(1.1ms) COMMIT
It is first selecting the record and then just updating it not creating a new record (INSERT) query.
Hope this helps.
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 have a model named "Person".
In ruby console, I first declare an instance of Person, then I update the attributes, then save.
person = Person.last
person.name = "jeff"
person.save
After doing these, I got message like this:
(9.9ms) BEGIN
(7.5ms) ROLLBACK
=> false
What are "BEGIN", "ROLLBACK", "false" refers to separately?
I googled, but nothing came out.
When you do save in Rails, it wraps the database operation in a transaction. BEGIN is written to the log when the transaction starts and ROLLBACK is logged if the operation fails (because all write operations in the transaction—UPDATE, INSERT or DELETE—are "rolled back").
false is the value that save returns when the operation fails.
You usually want to use save! instead of save because it will raise an (informative) exception if the operation fails.
Also look at update_attributes:
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html
I was wondering if there was any way in rails to return a new_attribute in a select as statement in rails.
For simplified example Books.select("'tuds' as new_attribute").first where new_attribute isn't in the DB, just returns a bunch of empty active record objects.
Seems like this should work, but I'm not having any luck. Any thoughts!?
Thanks!
-Mario
`
UPDATE: I'm a goof. I wasn't actually looking at the actual object and was just looking at the log in my console.
Works for me using Rails 3.2:
irb(main):001:0: User.select('full_name as whatever').first.whatever
User Load (0.5ms) SELECT id, full_name as whatever FROM `users` LIMIT 1
=> "Zap Brannigan"
Is this what you want to achieve?
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'
?