Arguments to update_attribute change in SQL statement - sql

The gist of it all
This method call:
user.update_attribute('image_url', 'arch.png')
Generates this SQL statement:
UPDATE "users" SET "image_url" = ?, [["image_url", "rock.jpg"]]
That SQL statement is not what the desired outcome is.
The desired outcome is this SQL statement:
UPDATE "users" SET "image_url" = ?, [["image_url", "arch.png"]]
Notice that the SQL statement generated has the wrong argument for image_url.
How is that possible though?
How can I fix it?
Background
I use the form_for tag to generate a form where users can change their profile picture. The form sends a post request to the controller which calls another method, passing along the parameters. The final method calls update_attribute on the record, saving the changes.
I'm using the gem "carrierwave" for the images.
Debugging
I've done some debugging and it looks like everything goes well, until the last method calls update_attribute. That call generates a SQL statement which is incorrect.
For reference purposes, the old picture's file name is "rock.jpg", and the new one is "arch.png".
The gist of it is that these two lines are executed:
puts "\n\nSending #{attribute} and #{value} to update_attribute\n\n"
if user.update_attribute(attribute, value)
Which result in:
Sending image_url and arch.png to update_attribute
UPDATE "users" SET "image_url" = ?, WHERE "users"."id" = ? [["image_url", "rock.jpg"], [...]]
I took out the updated_at part of the generated SQL statement, since it isn't important. The full SQL statement as well as more output from the helper can be seen just below.
How is the image_url in the SQL statement different from the value passed to it?
Detailed output from log
Received update request!!!!!
---------------------------
Attribute - image_url
Value - arch.png
--------------------------
Sending image_url and arch.png to update_attribute
(0.1ms) begin transaction
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 186]]
SQL (0.2ms) UPDATE "users" SET "image_url" = ?, "updated_at" = ? WHERE "users"."id" = ? [["image_url", "rock.jpg"], ["updated_at", "2016-04-27 01:25:49.476024"], ["id", 186]]
(90.4ms) commit transaction
Code
Form:
<%= form_for(#current_member, html: {id: 'image_form'}) do %>
<%= file_field_tag :image_url,
type: 'file', html: {id: 'file_field'} %>
<%= hidden_field_tag 'updateParam', 'image_url' %>
<%= submit_tag %>
<% end %>
Controller:
def update
update_account(#current_member, params[:updateParam], params)
end
Helper:
def update_account(user, attribute, parameters)
puts "\n\n\n\n\n"
puts "Received update request!!!!!\n
puts "---------------------------\n"
puts "Attribute - #{attribute}\n"
puts "Value - #{parameters[attribute]}\n"
puts "--------------------------\n\n\n"
if editable attribute
if valid(attribute, parameters)
value = parameters[attribute]
puts "\n\nSending #{attribute} and #{value} to update_attribute\n\n"
if user.update_attribute(attribute, value)
# ...
# ...
The gist of it all
This method call:
user.update_attribute('image_url', 'arch.png')
Generates this SQL statement:
UPDATE "users" SET "image_url" = ?, [["image_url", "rock.jpg"]]
That SQL statement is not what the desired outcome is.
The desired outcome is this SQL statement:
UPDATE "users" SET "image_url" = ?, [["image_url", "arch.png"]]
Notice that the SQL statement generated has the wrong argument for image_url.
How is that possible?
How can I fix it?

Extending comments to answer:
It looks, there is some callback in your model which is changing the value before save/update.
To skip callbacks, you can use update_column instead of update_attribute.
Note: update_column also skips validations and does not update the updated_at attribute.
Edit from author
This answer was right, a callback changed the result to something that could be saved in the database.
Using update_column I was able to see that my parameters were passed in correctly, but were not correct. The carrierwave gem uses a callback which checks to see if the parameter is valid, if not, it reverts the value to what it was previous to the call. If the value is valid (an UploadedFile), then it saves the file in the save_dir and changes the value to be a url to point to the file.
I used this to debug further and find that the value I passed to update_attribute wasn't valid because the form_for didn't include multipart: true, which is necessary for forms using carrierwave to upload images. Adding this solved the problem.
I had to go back to using update_attribute instead of update_column so that carrierwave's callback would still be executed and the uploaded file would be saved in the database.

There is a lot of clutter in your question, this makes it hard to read and help.
The goal of such a bug hunt is to reduce the elements involved to have as few things to worry about as possible.
Bypass Rails
Run the statement in a SQL console. Just to be sure that you don't have any fancy triggers and functions in place. From the output you showed, this should not be the case. But... It costs you 1min. and gives you some security.
Bypass View/Controller
Run the code on the Rails console. I tend to write everything on one line, with a reload! statement in front. So changes in the code can be checked fast:
reload!; user = User.find(12345); user.update_attribute('image_url', 'arch.png'); User.find(12345).image_url
What does it print?
I assume that your problem stems from callbacks. So it should print the wrong value for image_url already. So you can be sure that the problem is not caused by View/Controller layer.
Bypass View
If everything is fine, then you can be sure that the problem is somewhere in View/Controller. In this case i'd not use params to build the update statement but use hardcoded values. Like this you bypass the View layer.
Bypass callbacks
Use update_column instead of update_attribute
Callbacks
First: don't use callbacks. Second: don't use them. And if you use them be damn sure you know what you do:-)
If update_column solves the problem, then it probably is because of a callback.
Search your code for the attribute name and callback names to see if there are any.

Related

Should I query using objects or their IDs? [duplicate]

I am new to rails. What I see that there are a lot of ways to find a record:
find_by_<columnname>(<columnvalue>)
find(:first, :conditions => { <columnname> => <columnvalue> }
where(<columnname> => <columnvalue>).first
And it looks like all of them end up generating exactly the same SQL. Also, I believe the same is true for finding multiple records:
find_all_by_<columnname>(<columnvalue>)
find(:all, :conditions => { <columnname> => <columnvalue> }
where(<columnname> => <columnvalue>)
Is there a rule of thumb or recommendation on which one to use?
where returns ActiveRecord::Relation
Now take a look at find_by implementation:
def find_by
where(*args).take
end
As you can see find_by is the same as where but it returns only one record. This method should be used for getting 1 record and where should be used for getting all records with some conditions.
Edit:
This answer is very old and other, better answers have come up since this post was made. I'd advise looking at the one posted below by #Hossam Khamis for more details.
Use whichever one you feel suits your needs best.
The find method is usually used to retrieve a row by ID:
Model.find(1)
It's worth noting that find will throw an exception if the item is not found by the attribute that you supply. Use where (as described below, which will return an empty array if the attribute is not found) to avoid an exception being thrown.
Other uses of find are usually replaced with things like this:
Model.all
Model.first
find_by is used as a helper when you're searching for information within a column, and it maps to such with naming conventions. For instance, if you have a column named name in your database, you'd use the following syntax:
Model.find_by(name: "Bob")
.where is more of a catch all that lets you use a bit more complex logic for when the conventional helpers won't do, and it returns an array of items that match your conditions (or an empty array otherwise).
Model.find
1- Parameter: ID of the object to find.
2- If found: It returns the object (One object only).
3- If not found: raises an ActiveRecord::RecordNotFound exception.
Model.find_by
1- Parameter: key/value
Example:
User.find_by name: 'John', email: 'john#doe.com'
2- If found: It returns the object.
3- If not found: returns nil.
Note: If you want it to raise ActiveRecord::RecordNotFound use find_by!
Model.where
1- Parameter: same as find_by
2- If found: It returns ActiveRecord::Relation containing one or more records matching the parameters.
3- If not found: It return an Empty ActiveRecord::Relation.
There is a difference between find and find_by in that find will return an error if not found, whereas find_by will return null.
Sometimes it is easier to read if you have a method like find_by email: "haha", as opposed to .where(email: some_params).first.
Since Rails 4 you can do:
User.find_by(name: 'Bob')
which is the equivalent find_by_name in Rails 3.
Use #where when #find and #find_by are not enough.
The accepted answer generally covers it all, but I'd like to add something,
just incase you are planning to work with the model in a way like updating, and you are retrieving a single record(whose id you do not know), Then find_by is the way to go, because it retrieves the record and does not put it in an array
irb(main):037:0> #kit = Kit.find_by(number: "3456")
Kit Load (0.9ms) SELECT "kits".* FROM "kits" WHERE "kits"."number" =
'3456' LIMIT 1
=> #<Kit id: 1, number: "3456", created_at: "2015-05-12 06:10:56",
updated_at: "2015-05-12 06:10:56", job_id: nil>
irb(main):038:0> #kit.update(job_id: 2)
(0.2ms) BEGIN Kit Exists (0.4ms) SELECT 1 AS one FROM "kits" WHERE
("kits"."number" = '3456' AND "kits"."id" != 1) LIMIT 1 SQL (0.5ms)
UPDATE "kits" SET "job_id" = $1, "updated_at" = $2 WHERE "kits"."id" =
1 [["job_id", 2], ["updated_at", Tue, 12 May 2015 07:16:58 UTC +00:00]]
(0.6ms) COMMIT => true
but if you use where then you can not update it directly
irb(main):039:0> #kit = Kit.where(number: "3456")
Kit Load (1.2ms) SELECT "kits".* FROM "kits" WHERE "kits"."number" =
'3456' => #<ActiveRecord::Relation [#<Kit id: 1, number: "3456",
created_at: "2015-05-12 06:10:56", updated_at: "2015-05-12 07:16:58",
job_id: 2>]>
irb(main):040:0> #kit.update(job_id: 3)
ArgumentError: wrong number of arguments (1 for 2)
in such a case you would have to specify it like this
irb(main):043:0> #kit[0].update(job_id: 3)
(0.2ms) BEGIN Kit Exists (0.6ms) SELECT 1 AS one FROM "kits" WHERE
("kits"."number" = '3456' AND "kits"."id" != 1) LIMIT 1 SQL (0.6ms)
UPDATE "kits" SET "job_id" = $1, "updated_at" = $2 WHERE "kits"."id" = 1
[["job_id", 3], ["updated_at", Tue, 12 May 2015 07:28:04 UTC +00:00]]
(0.5ms) COMMIT => true
Apart from accepted answer, following is also valid
Model.find() can accept array of ids, and will return all records which matches.
Model.find_by_id(123) also accept array but will only process first id value present in array
Model.find([1,2,3])
Model.find_by_id([1,2,3])
The answers given so far are all OK.
However, one interesting difference is that Model.find searches by id; if found, it returns a Model object (just a single record) but throws an ActiveRecord::RecordNotFound otherwise.
Model.find_by is very similar to Model.find and lets you search any column or group of columns in your database but it returns nil if no record matches the search.
Model.where on the other hand returns a Model::ActiveRecord_Relation object which is just like an array containing all the records that match the search. If no record was found, it returns an empty Model::ActiveRecord_Relation object.
I hope these would help you in deciding which to use at any point in time.
Suppose I have a model User
User.find(id)
Returns a row where primary key = id. The return type will be User object.
User.find_by(email:"abc#xyz.com")
Returns first row with matching attribute or email in this case. Return type will be User object again.
Note :- User.find_by(email: "abc#xyz.com") is similar to User.find_by_email("abc#xyz.com")
User.where(project_id:1)
Returns all users in users table where attribute matches.
Here return type will be ActiveRecord::Relation object. ActiveRecord::Relation class includes Ruby's Enumerable module so you can use it's object like an array and traverse on it.
Both #2s in your lists are being deprecated. You can still use find(params[:id]) though.
Generally, where() works in most situations.
Here's a great post: https://web.archive.org/web/20150206131559/http://m.onkey.org/active-record-query-interface
The best part of working with any open source technology is that you can inspect length and breadth of it.
Checkout this link
find_by ~> Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself. If no record is found, returns nil.
find ~> Finds the first record matching the specified conditions , but if no record is found, it raises an exception but that is done deliberately.
Do checkout the above link, it has all the explanation and use cases for the following two functions.
I will personally recommend using
where(< columnname> => < columnvalue>)

How do I get the results of the Plucky Query inside my controller?

I'm missing something simple - I do not want to access the results of this query in a view.
Here is the query:
#adm = Admin.where({:id => {"$ne" => params[:id].to_s},:email => params[:email]})
And of course when you inspect you get:
#adm is #<MongoMapper::Plugins::Querying::DecoratedPluckyQuery:0x007fb4be99acd0>
I understand (from asking the MM guys) why this is the case - they wished to delay the results of the actual query as long as possible, and only get a representation of the query object until we render (in a view!).
But what I'm trying to ascertain in my code is IF one of my params matches or doesn't match the result of my query in the controller so I can either return an error message or proceed.
Normally in a view I'm going to do:
#adm.id
To get the BSON out of this. When you try this on the Decorated Query of course it fails:
NoMethodError (undefined method `id' for #<MongoMapper::Plugins::Querying::DecoratedPluckyQuery:0x007fb4b9e9f118>)
This is because it's not actually a Ruby Object yet, it's still the query proxy.
Now I'm fundamentally missing something because I never read a "getting started with Ruby" guide - I just smashed my way in here and learned through brute-force. So, what method do I call to get the results of the Plucky Query?
The field #adm is set to a query as you've seen. So, to access the results, you'll need to trigger execution of the query. There are a variety of activation methods you can call, including all, first, and last. There's a little documentation here.
In this case, you could do something like:
adm_query = Admin.where({:id => {"$ne" => params[:id].to_s},:email => params[:email]})
#adm_user = adm_query.first
That would return you the first user and after checking for nil
if #adm_user.nil?
# do something if no results were found
end
You could also limit the query results:
adm_query = Admin.where( ... your query ...).limit(1)

Sorting a Rails database table by a column in an associated model with additional scoping

I am new to Rails. I am trying to implement something like Ryan Bates' sortable table columns code (Railscast #228) on a legacy database. My question is very similar to "Sorting a Rails database table by a column in an associated model", but I can't seem to solve mine based on the answers there.
I want to be able to sort my list of projects in the index view by the udtid in the entityudfstorage table (class Ent), i.e., by project.ent.udtid. I have an additional consideration, in that each project matches a number of ent rows, so I need to scope to match to where ent.rowindex != 0.
The Models:
class Project < ActiveRecord::Base
has_many :ent, :foreign_key => "attachtoid"
has_many :samples, :foreign_key => "projectid"
class Ent < ActiveRecord::Base
set_table_name("entityudfstorage")
belongs_to :project, :foreign_key => "attachtoid"
scope :rowindex, where('entityudfstorage.rowindex != ? ', "0")
project index view:
<tr>
<th><%= sortable "name", "Name" %></th>
<th><%= sortable "projecttype", "Project Type" %> </th>
</tr>
<tr>
<td><%= project.name %></td>
<td><%= project.ent.rowindex.first.udtid %></td>
</tr>
project controller
def list
#projects = Project.order(sort_column + " " + sort_direction)
end
I've been trying to figure out what I can put in the "sort_column" for projecttype which would get it to sort by the associated field project.ent.rowindex.first.udtid (the same way that "name" works in the controller to sort by project.name).
I tried putting in a scope in projects of
scope :by_udtids, Project.joins("left join ent on projects.projectid = ent.attachtoid").where('ent.rowindex != ?', 0).order("ent.udtid DESC")
and then tried this in the project controller.
if sort_column == "projecttype"
#projects = Project.by_udtids
else
#projects = Project.order(sort_column + " " + sort_direction)
The result is that the project index page shows up with the proper data in the columns, but when I click on the "Project Type" link header, it does not sort (whereas, if I click on the "Name" link header, it does sort. The logs I can see in the server terminal are the same for both clicks, and the query's seem correct..
Started GET "/projects?direction=asc&sort=projecttype" for 128.208.10.200 at 2013-08-29 07:47:52 -0700
Processing by ProjectsController#index as HTML
Parameters: {"direction"=>"asc", "sort"=>"projecttype"}
Project Load (1.5ms) SELECT "project".* FROM "project" ORDER BY name asc
Ent Load (0.4ms) SELECT "entityudfstorage".* FROM "entityudfstorage" WHERE "entityudfstorage"."attachtoid" = 602 AND (entityudfstorage.rowindex != '0' ) LIMIT 1
CACHE (0.0ms) SELECT "entityudfstorage".* FROM "entityudfstorage" WHERE "entityudfstorage"."attachtoid" = 602 AND (entityudfstorage.rowindex != '0' ) LIMIT 1
(0.3ms) SELECT COUNT(*) FROM "sample" WHERE "sample"."projectid" = 602
Ent Load (0.3ms) SELECT "entityudfstorage".* FROM "entityudfstorage" WHERE "entityudfstorage"."attachtoid" = 603 AND (entityudfstorage.rowindex != '0' ) LIMIT 1
CACHE (0.0ms) SELECT "entityudfstorage".* FROM "entityudfstorage" WHERE "entityudfstorage"."attachtoid" = 603 AND (entityudfstorage.rowindex != '0' ) LIMIT 1
(0.2ms) SELECT COUNT(*) FROM "sample" WHERE "sample"."projectid" = 603
Rendered projects/list.html.erb within layouts/admin (478.7ms)
Completed 200 OK in 487ms (Views: 398.7ms | ActiveRecord: 87.9ms)
[2013-08-29 07:55:27] WARN Could not determine content-length of response body. Set content- length of the response or set Response#chunked = true
Started GET "/assets/jquery.js?body=1" for 128.208.10.200 at 2013-08-29 07:55:28 -0700
Served asset /jquery.js - 304 Not Modified (0ms)
[2013-08-29 07:55:28] WARN Could not determine content-length of response body. Set content- length of the response or set Response#chunked = true
Much appreciate any insight!
You don't actually say what "doesn't work" means in this context. I would guess that it either means you get an error message or that the Projects aren't sorted the way you expect.
If it's an error message, you should include it with your question, but the first thing I'd do in your place is probably to start using the Rails conventions for naming and referring to things. If your associated class is UserDefinedField, Rails is going to expect the table to be named user_defined_fields and the association to be specified as has_many :user_defined_fields. It also expects fields to be in snake case (attach_to_id, not attachtoid), but aside from having to specify every foreign key everywhere, that probably won't cause errors. Looking at your code, I would expect Rails to be complaining about the association name any time it loaded the Project model. You should either change these things to match the conventions, or (if you have a good reason to use these names), tell Rails what's up by specifying the class and table names in your associations.
If it's an incorrect response order, I'm less certain what the issue is. One thing that jumps out immediately is that you're going to get the same Project returned multiple times with this scope, once for each associated UserDefinedField. If you don't want that to happen, you'll need to add a group clause to your scope and some sort of aggregation for the userdefinedfields.udtids. This might look something like:
scope :by_udtids, Project.
joins("left join userdefinedfields on projects.projectid = userdefinedfields.attachtoid").
where('userdefinedfields.rowindex != ?', 0).
group('projects.id').
order("max(userdefinedfields.udtid) DESC")
Edit:
It looks like your current problem is that the scope isn't getting used at all. Here's a couple of reasons that might be the case:
I notice that your controller action is called ProjectsController#list, but that your log says the request with the parameters is being processed by ProjectsController#index. The call to the list action doesn't appear to be running any queries, which sounds as though perhaps it isn't doing anything that triggers actually loading the objects. Could this simply be a routing or template error?
Given that you say the query being run is the same in both cases, if the correct action is being called, it seems likely that your conditional (sort_column == "projecttype") is returning false, even when you pass the parameters. Even if the scope wasn't quite correct, you would otherwise still see at least a different query there. Is it possible that you're simply forgetting to set sort_column = params[:sort]? Try temporarily removing the conditional - just always use #projects = Project.by_udtid. See if you get a different query then.
Side note on the scope/query itself. If I understand your comment correctly, you want to sort by the udtid of the Ent with the lowest nonzero rowindex. This is going to be tricky, and the details will depend on your database (grouping, especially complex grouping, is one of the things that works fairly differently in mySQL vs. PostGreSQL, the two most common databases for Rails apps). The concept is called a 'groupwise maximum', and your best bet is probably to search for that phrase in conjunction with your database name.

Create Ransack object without querying DB

I'd like to be able to create a Ransack::Search object to be passed into a search_form_for, but the initial creation of the search object queries the database, wich I don't want.
I want to show an initial blank form with Ransack::Search options to search, without calling the database.
How can I do that ?
thanks,
regards
Arel relations (ie. queries) are lazily-executed on first reference to the results, so you should find that you can create a search object and pass it to the form, without it calling the db, so long as you don't reference the .result method anywhere.
e.g.
// in your controller
my_query = MyModelClass.where{ id.gt(0) }
#q = my_query.search( params[:q] )
// in your view
search_form_for( #q, (...other options...) )
Any of these will trigger the db query to be actually performed:
- #q.results.each do |result|
- for result in #q.results
- #q.results.count
- #q.results.to_a
- #q.results.size
// ....etc
But so long as you only use the search object for your form, it should not get executed.
Of course, if you're testing this from the console, make sure that you put ;nil at the end of the line, otherwise the console will print the last thing evaluated, which will cause the query to be run!

Why does this Rails named scope return empty (uninitialized?) objects?

In a Rails app, I have a model, Machine, that contains the following named scope:
named_scope :needs_updates, lambda {
{ :select => self.column_names.collect{|c| "\"machines\".\"#{c}\""}.join(','),
:group => self.column_names.collect{|c| "\"machines\".\"#{c}\""}.join(','),
:joins => 'LEFT JOIN "machine_updates" ON "machine_updates"."machine_id" = "machines"."id"',
:having => ['"machines"."manual_updates" = ? AND "machines"."in_use" = ? AND (MAX("machine_updates"."date") IS NULL OR MAX("machine_updates"."date") < ?)', true, true, UPDATE_THRESHOLD.days.ago]
}
}
This named scope works fine in development mode. In production mode, however, it returns the 2 models as expected, but the models are empty or uninitialized; that is, actual objects are returned (not nil), but all the fields are nil. For example, when inspecting the return value of the named scope in the console, the following is returned:
[#<Machine >, #<Machine >]
But, as you can see, all the fields of the objects returned are set to nil.
The production and development environments are essentially the same. Both are using a SQLite database. Here is the SQL statement that is generated for the query:
SELECT
"machines"."id",
"machines"."machine_name",
"machines"."hostname",
"machines"."mac_address",
"machines"."ip_address",
"machines"."hard_drive",
"machines"."ram",
"machines"."machine_type",
"machines"."use",
"machines"."comments",
"machines"."in_use",
"machines"."model",
"machines"."vendor_id",
"machines"."operating_system_id",
"machines"."location",
"machines"."acquisition_date",
"machines"."rpi_tag",
"machines"."processor",
"machines"."processor_speed",
"machines"."manual_updates",
"machines"."serial_number",
"machines"."owner"
FROM
"machines"
LEFT JOIN
"machine_updates" ON "machine_updates"."machine_id" = "machines"."id"
GROUP BY
"machines"."id",
"machines"."machine_name",
"machines"."hostname",
"machines"."mac_address",
"machines"."ip_address",
"machines"."hard_drive",
"machines"."ram",
"machines"."machine_type",
"machines"."use",
"machines"."comments",
"machines"."in_use",
"machines"."model",
"machines"."vendor_id",
"machines"."operating_system_id",
"machines"."location",
"machines"."acquisition_date",
"machines"."rpi_tag",
"machines"."processor",
"machines"."processor_speed",
"machines"."manual_updates",
"machines"."serial_number",
"machines"."owner"
HAVING
"machines"."manual_updates" = 't'
AND "machines"."in_use" = 't'
AND (MAX("machine_updates"."date") IS NULL
OR MAX("machine_updates"."date") < '2010-03-26 13:46:28')
Any ideas what's going wrong?
This might not be related to what is happening to you, but it sounds similar enough, so here it goes: are you using the rails cache for anything?
I got nearly the same results as you when I tried to cache the results of a query (as explained on railscast #115).
I tracked down the issue to a still open rails bug that makes cached ActiveRecords unusable - you have to choose between not using cached AR or applying a patch and getting memory leaks.
The cache works ok with non-AR objects, so I ended up "translating" the stuff I needed to integers and arrays, and cached that.
Hope this helps!
Seems like the grouping may be causing the problem. Is the data also identical in both dev & production?
Um, I'm not sure you're having the problem you think you're having.
[#<Machine >, #<Machine >]
implies that you have called "inspect" on the array... but not on each of the individual machine-objects inside it. This may be a silly question, but have you actually tried calling inspect on the individual Machine objects returned to really see if they have nil in the columns?
Machine.needs_updates.each do |m|
p m.inspect
end
?
If that does in fact result in nil-column data. My next suggestion is that you copy the generated SQL and go into the standard mysql interface and see what you get when you run that SQL... and then paste it into your question above so we can see.