Change default finder select statement in Rails 3.1 - ruby-on-rails

I'd like to change the default statement that ActiveRecord uses to query a model's table. By default, it queries a table "cables" for example by...
this_cable = Cable.first
results in
SELECT "cables".* FROM "cables" LIMIT 1
I would like to find a way for it to wind up with
SELECT *,askml(wkb_geometry) as kml FROM "cables" LIMIT 1
This way i can call a database function and have that behave like a field on the object.
this_cable.kml
=> "<LineString><coordinates>-73.976879999999994,40.674999999999997 -73.977029999999999,40.674779999999998 -73.977170000000001,40.674770000000002 -73.97775,40.67501</coordinates></LineString>"
This can be accomplished by adding a scope
scope :with_kml, "*,askml(wkb_geometry) as kml"
But I figure that's kind of messy. I would like this "kml" column to always be there, without having to call the "with_kml" scope.
Any ideas?

Have you tried using default_scope for this, or do you actually want this to be present on all your models?
Something like this might solve your problem:
default_scope select("*, askml(wkb_geometry) as kml")
You might want to change that to cables.* for it to work properly with joins and such, though.

Related

parameter based joins not susceptible for sql injection

I want to do non-standard query with join based on the parameters.
For example I have 2 tables: a_examples and b_examples both with fields: field_x and field_y.
I want to join rows when both tables have the same values on field_x(or field_y).
Example query can looks like this:
AExample.joins('INNER JOIN b_examples ON b_examples.field_x = a_examples.field_x')
The problem occurs when I have field name based on parameter.
For example I have variable field_name and want to use it for query. I can do it like this:
AExample.joins("INNER JOIN b_examples ON b_examples.#{field_name} = a_examples.#{field_name}")
This query works, but is susceptible for sql injection.
For where clause we have special syntax with ? to avoid sql injection but there isnt any such thing for joins. How can I make this query safe?
Do not attempt this:
(explanation below)
You can use the ActiveRecord::Sanitization module and write something like the following, inside your ActiveRecord model:
AExample.joins("INNER JOIN b_examples ON b_examples.#{sanitize_sql(field_name)} = a_examples.#{sanitize_sql(field_name)}")
Or you can include the module somewhere else and use it there (e.g. your controller).
Do use this, instead: (included from another answer)
AExample.joins("INNER JOIN b_examples ON b_examples.#{ActiveRecord::Base.connection.quote_column_name(field_name)} = a_examples.#{ActiveRecord::Base.connection.quote_column_name(field_name)}")
It will raise an error if the column is not found, preventing malicious code from entering your query.
However I wouldn't do this in my app as it looks suspicious, other programers may not understand what is happening, it may be implemented wrong, solid testing should be included, it may have bugs and such. In your problem, you only need to construct two different queries, with that information I would write something like:
case dynamic_field
when 'field_x'
AExample.joins('INNER JOIN b_examples ON b_examples.field_x = a_examples.field_x')
when 'field_y'
AExample.joins('INNER JOIN b_examples ON b_examples.field_y = a_examples.field_y')
else
raise "Some suspicious parameter was sent!: #{dynamic_field}"
end
Or even write scopes on your model and avoid this code to be flying around.
With problems of this nature, as with encryption, try to find a workaround and avoid implementing your own solutions as much as possible.
EDIT:
The method sanitize_sql is intended to sanitize conditions for a WHERE clause (ActiveRecord::Sanitization):
Accepts an array or string of SQL conditions and sanitizes them into a valid SQL fragment for a WHERE clause.
It is not an option as you try to sanitize for an INNER JOIN, or an ON clause.
Note that the ActiveRecord::Sanitization module only has options for WHERE, SET, ORDER and LIKE clauses. I was unable to find a sanitization method for a column name, an INNER JOIN or an ON clause. Perhaps is a useful funcionality that should be added on Rails on further version.
Using sanitize_sql with a string passes it almost unfiltered, so if the field_name variable has some malicious code as:
"field_x = a_examples.field_x; DROP TABLE a_examples; --"
It will be included in your query, without any error being raised.
This solution is not safe, and is for reasons like these that we should avoid writing code of this nature. Perhaps you find something helpful with Arel or other gems, but I would strongly advice not to.
EDIT 2:
Added the working solution to escape a column name. It raises an error if malicious code is being entered, as the column with that name will not be found.
You can sanitize parameters using ActiveRecord::Base.connection.quote(string) or even .quote_column

Rails 4 update_all and set value from another field

I need to do some bulk updates in some models and set value of a field as value of another field.
Right now I can do that with raw sql like this:
ActiveRecord::Base.connection.execute("UPDATE `deleted_contents` SET `deleted_contents`.`original_id` = `deleted_contents`.`id` WHERE `deleted_contents`.`original_id` is NULL")
This is working fine, however I need to do this using ActiveRecord query interface due to many reasons.
I tried:
DeletedContent.where(original_id: nil).update_all(original_id: value_of_id_column)
For value_of_id_column I tried :id, self.id, id, etc, nothing works. What should I set for value_of_id_column to get the original query generated by rails? Is this possible, or using the raw sql is the only solution?
Also I do not want to iterate over each record and update. This is not a valid solution for me:
DeletedContent.where(original_id: nil).each do |deleted_content|
update_each_record
end
I'm pretty sure you cannot obtain that query by passing a hash to update_all.
The closest to what you want to obtain would be:
DeletedContent.where(original_id: nil).update_all("original_id = id")

Rails - why can't I use activerecord exists in my scope?

I'm trying to utilize .exists?() to return true or false, pretty simple. It would be nice to do the one-liner scope like so:
scope :any_alternates, lambda{|apikey| Track.exists?(:track_id => apikey)}
Or even using this scope syntax:
scope :any_alternates, ->(apikey) {Track.exists?(:track_id => apikey)}
But for some reason, the above scopes will return all rows in my db table when there's not a match. It works how it should when it finds a match however, but breaks if none...
I'm forced to create a method, which (to my knowledge) should be doing the same thing in the above scope:
def self.any_alternates(apikey)
return Track.exists?(:track_id => apikey)
end
Any idea why .exists?() isn't working inside of my scope?
After some testing...
If there is no match, then the scope will return all rows in the DB... (I updated above to mention that). I checked the generated query on both the scope and method to see if there's a difference, but they're the same:
SELECT 1 AS one FROM `tracks` WHERE `tracks`.`track_id` = '_btbd_uUmQT8hYUK3SrJ9Q' LIMIT 1
Update:
even though I'm searching on a column called track_id, this column is not setup as a relationship to another model. I know this is confusing, but that's how this table got setup (for good reason, beyond this issue so not worth touching on here)
Are you passing in nil? That would cause all records to be returned. You can drop the Track in the scope, like this:
scope :any_alternates, lambda{|apikey| exists?(:track_id => apikey)}
Here is what happens when you pass in nil:
> Track.any_alternates(nil).count
Track Exists (1.7ms) SELECT 1 AS one FROM "track" WHERE "tracks"."track_id" IS NULL LIMIT 1
instead of passing in a value:
> Track.any_alternates('X4DBA36gbtqgWl4F1')
Track Exists (0.4ms) SELECT 1 AS one FROM "tracks" WHERE "tracks"."track_id" = $1 LIMIT 1 [["track_id", "X4DBA36gbtqgWl4F1"]]
Are you sure that you have the right search? A Track having a track_id implies that there is a relation between Track and another track model which would be interesting.
Also the scope syntax is off. A scope is just a query of the table, so traditionally it is only things associated with the current model.
scope :any_alternates, ->(api_key) { |api_key| where(track_id: api_key }

How do I order database records in rails by most recent?

I want to order all the items in a model Item so it displays the most recent ones first. According to the Rails guide, the following code should work:
Item.order("created_at DESC")
However, when I type that (or varieties) in the terminal, the most recent item always shows up last, and that's how they show up on my page. How do I efficiently retrieve them with he most recent first? (I'm going to display only some of them at a time on each page.)
Note that my default scope for items is oldest first.
Update:
This is the SQL I get:
SELECT "comments".* FROM "comments" ORDER BY comments.created_at ASC, created_at DESC
So I guess I shouldn't use default scopes...
The query you posted is correct
Item.order("created_at DESC")
The only reason why it would not work is if there is anything else conflicting with it. In general, the conflict is represented by a default_scope.
If you have a default scope that overrides your order, you should first unscope the query
Item.unscoped { Item.order("created_at DESC") }
If you are using default scopes, I strongly encourage you to avoid them. They are very hard to debug and unscope.
There are very few cases where default scopes make sense. You can simply pass the (default) scope at select time in the controller or create a custom method for it.
I realise this is a really old question, but none of the answers contain the solution without writing raw SQL, which is available since Rails 3+:
Item.order(created_at: :desc)
or using the reverse_order method:
Item.order(:created_at).reverse_order
See more at http://guides.rubyonrails.org/active_record_querying.html#ordering
and
http://guides.rubyonrails.org/active_record_querying.html#reverse-order.
I modified CDub's answer with reverse so it now works:
Item.order(:created_at).reverse
I'm still not sure why the Rails guide's way doesn't work. Also the above method doesn't work well with pagination.
Item.unscoped.order('created_at DESC') should work.Using reverse might decrease the performance when the number of records increases
Correct one and tested
#purchase_orders = current_company.purchase_orders.order(:date)
#purchase_orders = #purchase_orders.reverse_order
you can add You can also define the default order in Item model
default_scope order('created_at DESC')

Rails: Getting column value from query

Seems like it should be able to look at a simple tutorial or find an aswer with a quick google, but I can't...
codes = PartnerCode.find_by_sql "SELECT * from partner_codes where product = 'SPANMEX' and isused = 'false' limit 1"
I want the column named code, I want just the value. Tried everything what that seems logical. Driving me nuts because everything I find shows an example without referencing the actual values returned
So what is the object returned? Array, hash, ActiveRecord? Thanks in advance.
For Rails 4+ (and a bit earlier I think), use pluck:
Partner.where(conditions).pluck :code
> ["code1", "code2", "code3"]
map is inefficient as it will select all columns first and also won't be able to optimise the query.
You need this one
Partner.where( conditions ).map(&:code)
is shorthand for
Partner.where( conditions ).map{|p| p.code}
PS
if you are often run into such case you will like this gem valium by ernie
it gives you pretty way to get values without instantiating activerecord object like
Partner.where( conditions ).value_of :code
UPDATED:
if you need access some attribute and after that update record
save instance first in some variable:
instance=Partner.where( conditions ).first
then you may access attributes like instance.code and update some attribute
instance.update_attribute || instance.update_attributes
check documentation at api.rubyonrails.org for details

Resources