What is the best way to perform `sum_by_sql`? - ruby-on-rails

In Rails 2.3.8 there is a class method ActiveRecord::Base.count_by_sql which allows to perform custom SELECT count(*) .... Is it save to execute customized SELECT sum(...) ... query with this method? If not, what should be done to execute such a query? Is ActiveRecord::Base.connection.execute the only option?
Thanks.
EDIT: Query I want to perform has another query nested. That's why I believe methods from ActiveRecord::Calculations are not sufficient.

Check ActiveRecord::Calculations. The API docs are here.
You can do something like this (assuming you have User model):
User.maximum(:updated_at)
# Generates:
# SELECT max(`users`.updated_at) AS max_updated_at FROM `users`

select_value from ActiveRecord::ConnectionAdapters::DatabaseStatements module is the answer. It returns the value present in the first column of the first row returned by query. select_value returns String, so conversion may be necessary.
Example:
ActiveRecord::Base.connection.select_value("Some complicated sql")

Related

Prepend DISTINCT ON in select statement in an active record scope

I am trying to use distinct on in rails with a scope, I've created a method in my model like this:
def self.fetch_most_recent_by_user(scope)
scope.where(guid: scope.except(:select).select("DISTINCT ON (eld_logs.user_id) user_id, eld_logs.guid").order("user_id, eld_logs.created_at desc").map(&:guid))
end
When I execute this I get and error like:
TestModel.fetch_most_recent_by_user(TestModel.includes(:user))
ERROR: syntax error at or near "DISTINCT"
LINE 1: SELECT guid, DISTINCT ON (user_id) user_id...
On searching on DISTINCT ON I found out that it should be the first element in a select statement for postgres to make it work.
I want to prepend the DISTINCT ON in the select statement. I have tried clearing the old select statements using except(:select) which I got from here, but it doesn't work because the includes(:user) prepends users attributes first while doing a left join.
I am using Rails 4.0.13 and Postgres 9.4.12. Any help is appreciated.
I found that if the includes was meddling with the distinct my sub query, because which DISTINCT ON failed. I modified my method to this and it works:
def self.fetch_most_recent_eld_log_by_user(scope, include_associations = { })
scope.where(guid: scope.except(:select).select("DISTINCT ON (eld_logs.user_id) eld_logs.user_id, eld_logs.guid").order("eld_logs.user_id, eld_logs.created_at desc").map(&:guid))
.includes(include_associations)
end
Still it'll be good if someone can provide a way to prepend something in the select statement of active record scope.

Get SQL for activerecord conditions without main select

Is there any way to get the sql for a single condition or chain of conditions so Booking.where(active: true).to_conditional_sql would return WHERE bookings.active='t' instead of SELECT * FROM bookings WHERE bookings.active='t'
I think you want Scopes:
You can find more about scopes here:
http://guides.rubyonrails.org/active_record_querying.html#scopes

Rails Model.select(attribute * attribute AS new_attribute) - new_attribute not in hash?

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?

Using Rails update_all method to update fields with value from another column

I'm trying to update a field in using update_all. However I need the value to be taken from another field which is re-written to my specific format.
If I have something like this in my model:
def self.clean_mac_address()
clean_mac_address = :macaddress.gsub(/[^0-9a-z]/i, '')
end
When I run this:
Radacct.update_all("mac_clean = #{clean_mac_address}")
I get an error:
NoMethodError: undefined method `gsub' for :macaddress:Symbol
Any thoughts how I can do this? Or is there a simpler way to update the field?
update_all generates a single SQL query to run - it can't do clever stuff like change arbitrary bits of ruby into equivalent SQL.
You either need to load all you instances (via find_each for example) and fix them one by one (ie don't use update_all), for example
Foo.find_each do |foo|
# update foo here
foo.save!
end
Or find a way of expressing that cleaning operation in SQL. For example Postgres has a regexp_replace function
Foo.update_all("some_column = regexp_replace(some_column, 'your_regexp_here', '','g')")
Which would remove everything replacing that regexp. Obviously you'll need to check the documentation for your database to see whether it supports such a feature.
While the accepted answer provides a nice way to update_all, what I'd use is
read_with_clean_addr = Radacct.where(mac_clean: :macaddress.gsub(/[^0-9a-z]/i, ''))
read_with_clean_add.update_all(mac_clean: "#{clean_mac_address}")

Change default finder select statement in Rails 3.1

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.

Resources