How is the Arel#extract method used in rails? - sql

I am trying to use the Arel#extract method. I have seen an example in a test case in test_extract.rb in the source but when I try to reproduce it in my app, I get undefined method.
table = Arel::Table.new :users
puts table[:created_at].extract('hour').to_sql
=> NoMethodError: undefined method `extract' for #<Arel::Attributes::Attribute:0x7..8>
I am using pg as the database.
Update:
My goal is to end up with this result in sql:
SELECT users.* FROM users WHERE EXTRACT(HOUR from users."created_at") = '1'
I would like to find all users that were created on the hour equal to one of any day. This works in sql but I am wondering how to create it in arel. Here is an example of how it's used in the arel test suite.

extract is node's method, you can cast it on any column(such as users[:id]), but not on Arel::Table instance.
So, to construct your query you need:
get Arel::Table instance users = Arel::Table.new(:users) or if you use ActiveRecord - users = User.arel_table
set SELECT statement on Arel::Table instance with project method: users = users.project(Arel.sql('*'))
set WHERE statement with where method: users.where(users[:created_at].extract(:hour).eq(1))
In one block:
query = User.arel_table.
project(Arel.sql('*')).
where(users[:created_at].extract(:hour).eq(1))
users = User.find_by_sql(query)
# => "SELECT * FROM \"users\" WHERE EXTRACT(HOUR FROM \"users\".\"created_at\") = 1"

I've had to perform an extract DOW on an instance of Arel::Nodes::NamedFunction, which does not expose the method #extract (as of Arel 6.0). I managed to achieve this by manually creating an instance of Arel::Nodes::Extract. Here is what worked for me, in case anyone have a similar issue:
Arel::Nodes::Extract.new(
Arel::Nodes::NamedFunction.new('some_function_name', [param1, param2, ...]),
:dow
)
You can use an Arel node directly with ActiveRecord's #where instead of building the full query through Arel as exemplified by Alexander Karmes's answer. So here is another way to perform the query required by the answer:
User.where(
Arel::Nodes::Extract.new(User.arel_table[:created_at], :hour).eq(1)
)
Which yields:
SELECT "users".* FROM "users" WHERE EXTRACT(HOUR FROM "users"."created_at") = 1
With the added benefit you can keep chaining other scopes defined in your User model.

Related

How to use update_all but each? Is there a better method?

Im current working on a small project and I want to seed my database faster. I migrated a new column called "grand_total_points" to my table of users. So originally I was using this code.
user = User.all
user.each do |x|
x.grand_total_points = x.total_points
x.save!
end
This takes me ages, because I have to update a million record.
Total_points have already been defined in my user model where it calculates all the users points that have been submitted. Forgive me for my explanation. Is there a way to use update_all method but with each included in it?
Yep, possible:
User.update_all('grand_total_points = total_points')
It will generate the following SQL query:
UPDATE "users" SET "grand_total_points" = 'total_points'
If total_points is not a column but an instance method, move the logic into update_all query.
User.update_all("grand_total_points = #{total_points calculation translated into SQL terms}")
I found something that could work. So basically i combine a ruby code with an execute SQL statement, and I put it in a migration file. Here's how the code works. I hope this helps. Make sure you follow the query according to your data.
class ChangeStuff < ActiveRecord::Migration
def change
points = Point.select('user_id, SUM(value) AS value').group(:user_id)
points.each do |point|
execute "UPDATE users SET grand_total_points = #{point.value} WHERE users.id = #{point.user_id}"
end
end
end
You should run bundle exec rake db:migrate after that. The normal way takes me 2-3hours. This only took me 2minutes.

ActiveRecord - Compare two values in same row

I have a table of call data and I want to query all unanswered calls, which means that the call start time is equal to the call end time. I currently use the following plain SQL which works as expected:
select * from calls where calls.start = calls.end
I was wondering if there is a more "rails" way to do this using the ActiveRecord Query Interface. Ideally I'd like to set up a scope in my Call model that returns me all unanswered calls. Something like:
scope :unanswered, -> { where(start: :end) }
The above doesn't work because Rails treats :end as a string instead of the end column in the DB.
I'm using PostgreSQL as my DB engine.
The SQL query
select * from calls where calls.start = calls.end
could be done in a rails way using a scope as follows:
scope :unanswered, -> { where('start = end') }
I think you can do the following:
scope :unanswered, -> (end) { where(start: end) }

Possible to use ActiveRecord methods against AR collections?

I would like to be able to pull all records from the db:
u = User.all
And then once loaded be able to apply AR methods to the resulting collection:
u.first
Is this possible in rails?
Once you actually query the database, the results become an array instead of an ActiveRecord::Relation. (Though #first would still work fine, since it's a method that also exists on Array).
If you just need a starting point to build an ActiveRecord::Relation though, you can use scoped:
# Doesn't execute a query yet
u = User.scoped
# This now executes a query similar to SELECT * FROM users LIMIT 1
u.first
Note that in Rails 4.0, #all now does the same thing as #scoped (whereas in Rails 3, it returns an array).
Why don't you try it?
User.all doesn't return an AR collection it returns an Array. Get rid of the .all and you will have a working example.

ActiveRecord .select(): Possible to clear old selects?

Is there a way to clear old selects in a .select("table.col1, ...") statement?
Background:
I have a scope that requests accessible items for a given user id (simplified)
scope :accessible, lambda { |user_id|
joins(:users).select("items.*")
.where("items_users.user_id = ?) OR items.created_by = ?", user_id, user_id)
}
Then for example in the index action i only need the item id and title, so i would do this:
#items = Item.accessible(#auth.id).select("polls.id, polls.title")
However, this will select the columns "items., items.id, items.title". I'd like to avoid removing the select from the scope, since then i'd have to add a select("items.") everywhere else. Am I right to assume that there is no way to do this, and i either live with fetching too many fields or have to use multiple scopes?
Fortunately you're wrong :D, you can use the #except method to remove some parts of the query made by the relation, so if you want to remove the SELECT part just do :
#items = Item.accessible(#auth.id).except(:select).select("polls.id, polls.title")
reselect (Rails 6+)
Rails 6 introduced a new method called reselect, which does exactly what you need, it replaces previously set select statement.
So now, your query can be written even shorter:
#items = Item.accessible(#auth.id).reselect("polls.id, polls.title")

Encapsulating SQL in a named_scope

I was wondering if there was a way to use "find_by_sql" within a named_scope. I'd like to treat custom sql as named_scope so I can chain it to my existing named_scopes. It would also be good for optimizing a sql snippet I use frequently.
While you can put any SQL you like in the conditions of a named scope, if you then call find_by_sql then the 'scopes' get thrown away.
Given:
class Item
# Anything you can put in an sql WHERE you can put here
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
end
This works (it just sticks the SQL string in there - if you have more than one they get joined with AND)
Item.mine.find :all
=> SELECT * FROM items WHERE ('user_id' = 887 and IS_A_NINJA() = 1)
However, this doesn't
Items.mine.find_by_sql 'select * from items limit 1'
=> select * from items limit 1
So the answer is "No". If you think about what has to happen behind the scenes then this makes a lot of sense. In order to build the SQL rails has to know how it fits together.
When you create normal queries, the select, joins, conditions, etc are all broken up into distinct pieces. Rails knows that it can add things to the conditions without affecting everything else (which is how with_scope and named_scope work).
With find_by_sql however, you just give rails a big string. It doesn't know what goes where, so it's not safe for it to go in and add the things it would need to add for the scopes to work.
This doesn't address exactly what you asked about, but you might investigate 'contruct_finder_sql'. It lets you can get the SQL of a named scope.
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
named_scope :additional {
:condtions => mine.send(:construct_finder_sql,{}) + " additional = 'foo'"
}
sure why not
:named_scope :conditions => [ your sql ]