Sum column values under condition - ruby-on-rails-3

Suppose this:
Works table
Name
created_at
Size
I want to list all works and in the end sum up all Size values, but under one condition. created_at must be < 1 year ago.
I know
#works.sum(&:size)
works but it doesn't filter out the 'created_at' part.
Then i got to this
#works.sum(&:size, :conditions => ['created_at > ?', 1.year.ago])
But keep getting a compile error about expecting a ) instead of a ,
Help please? Any better way to accomplish this?

you can use scope to keep the model clean
#controller
class WorksController < ApplicationsController
def index
#works.recent.sum(&:size)
end
end
#model
class Work
scope :recent, ->{ where('created_at > ?', 1.year.ago) }
end

This worked for me:
#works.where('created_at > ?', 1_year_ago).sum(:size)

Related

Relation passed to #or must be structurally compatible. Incompatible values: [:joins]

I have this error message
Relation passed to #or must be structurally compatible. Incompatible values: [:joins]
in my user model: has_many :orders
in my order model: belongs_to :user, optional: true
How I am supposed to write my query to have either the users' names and the order id in the same search input?
def filter_orders
return if params[:query].blank?
#orders = Order.joins(:user).where('lower(users.first_name) LIKE ?', "%#{params[:query][:keyword]}%")
.or(Order.joins(:user).where('lower(users.last_name) LIKE ?', "%#{params[:query][:keyword]}%"))
.or(Order.where(id: "#{params[:query][:keyword]}.to_i"))
end
It sound like this is a know issue with .or. Try using SQL or you can override .or as seen in this answer: https://stackoverflow.com/a/40742512/10987825
It occurs when you try to combine two multi-active records of the same type, but one of them has a reference value or an includes value, or in your case a joins value, that the other does not.
Therefore we need to match the values between them, and I found a general way to do this without knowing the actual values in advance.
def filter_orders
return if params[:query].blank?
orders_1 = Order.joins(:user).where('lower(users.first_name) LIKE ?', "%#{params[:query][:keyword]}%")
orders_2 = Order.joins(:user).where('lower(users.last_name) LIKE ?', "%#{params[:query][:keyword]}%")
orders_3 = Order.where(id: "#{params[:query][:keyword]}.to_i")
joind_orders = orders_1.or(
orders_2
.joins(orders_1.joins_values)
.includes(orders_1.includes_values)
.references(orders_1.references_values)
)
#orders = joind_orders.or(
orders_3
.joins(joind_orders.joins_values)
.includes(joind_orders.includes_values)
.references(joind_orders.references_values)
)
end

How to Write Where Clause Condition in Rails using AND & OR Operator

def self.get_previous_feedback current_feedback
Feedback.where("feedbacks.id < ?", current_feedback.id).order('created_at asc').last
end
def self.get_next_feedback current_feedback
Feedback.where("feedbacks.id > ?", current_feedback.id).order('created_at asc').first
end
#current_feeedback is the show page of any feedback.( feedback/show/id=2)
I have got 3 tables in my DB. Feedback, User, Department are connected in one-many relation.
By running above codes I am able to navigate to next/previous Feedback.
My User (current_user) is logged in, and Now on clicking prev/next, I want to retrieve the next feedback from DB(where condition written above) + whose feedback.department_id = current_user.deparment_id.
For including department_id in need to write an AND statement. How to do that ?
Try this...
def self.get_previous_feedback(current_feedback,current_user)
Feedback.where("id < ? & department_id = ?", current_feedback.id, current_user.department_id).order('created_at asc').last
end
def self.get_next_feedback(current_feedback,current_user)
Feedback.where("id > ? & department_id = ?", current_feedback.id, current_user.department_id).order('created_at asc').first
end
Thanks for pointing me out the correct logic.
Above query needs bit modification to work correctly.
simply id does not link to the feedbacks table, hence had to use feedbacks.id
& needs to be replaced by AND.
Corrected Code :
Feedback.where("feedbacks.id < ? AND feedbacks.department_id = ?",
current_feedback.id, current_user.department_id).order('created_at
asc').last
Thanks Man ! :)
You can also chain the two conditions, that way you don't need use the AND and you can also re-use the department_id clause.
def self.get_department_feedback current_user
Feedback.where(department_id: current_user.deparment_id)
end
def self.get_previous_feedback(current_feedback,current_user)
get_deparment_feedback(current_user).where("feedbacks.id < ?", current_feedback.id).order('created_at asc').last
end
def self.get_next_feedback(current_feedback,current_user)
get_deparment_feedback(current_user).where("feedbacks.id > ?", current_feedback.id).order('created_at asc').first
end

Active Record: query based on another query in an association

I'm trying to do a complex search based on an query in an association.
Event belongs_to :user / User has_many :events
Along the literal lines of:
query = User.where(:name => 'Bob')
query = query.joins(:events).where('COUNT(events.start_at > #{Time.now}) = 0')
I have tried several approaches but none seem to work. Help greatly appreciated.
Thanks in advance.
You need to use scopes to allow this kind of chaining:
In User model:
scope :with_name, lambda { |name| where(:name => name) }
In code:
query = User.with_name('Bob')
query = query.joins(:events).where('COUNT(events.start_at > #{Time.now}) = 0')
I noticed a few issues with the way your query is written but I don't think fixing them will solve your problem. All the same, hopefully knowing this stuff will get you closer!
First, you need double quotes in order to get Ruby to respect your #{} interpolation:
Open IRB and try:
1.9.3p194 :001 > 'COUNT(events.start_at > #{Time.now}) = 0'
Which will output:
"COUNT(events.start_at > \#{Time.now}) = 0"
Notice that nothing changed, because we have single quotes.
Now lets try with double quotes, and we're a bit closer:
1.9.3p194 :002 > "COUNT(events.start_at > #{Time.now}) = 0"
This time we see:
"COUNT(events.start_at > 2012-09-24 14:47:52 -0700) = 0"
But that still won't work, because SQL wants quotes around your timestamp for comparison.
We could handle this manually, like this:
1.9.3p194 :003 > "COUNT(events.start_at > '#{Time.now}') = 0"
Which generates:
"COUNT(events.start_at > '2012-09-24 14:49:31 -0700') = 0"
But that may not work on all SQLs - I know that Postgres wants single quotes around literals, but MySQL may prefer double quotes, or may not matter.
The best way to handle this is to let Rails figure it out. The way we do that is to use the ? operator inside of our SQL query strings:
where('COUNT(events.start_at > ?) = 0', Time.now)
And the output Rails will generate, and send to your SQL database, will look like this:
"WHERE COUNT(events.start_at > '2012-09-24 14:49:31 -0700') = 0"
Hope that helps!
I think this is what you are looking for all in one line:
Event.joins(:user).where(:users => {:name => 'Bob'}, :events => {:start_at => Time.at(0)..Time.now })
You cannot use COUNT in a WHERE clause afiak. You'll want to group your results and use conditions to return only the results you want.
query = User.where(:name => 'Bob')
query = query.joins("LEFT JOIN (SELECT events.id FROM events WHERE events.start_at > '#{Time.now.to_s(:db)}') AS events ON events.user_id=users.id").group("users.id").where("events.id IS NULL")

Rails 3 query in multiple date ranges

Suppose we have some date ranges, for example:
ranges = [
[(12.months.ago)..(8.months.ago)],
[(7.months.ago)..(6.months.ago)],
[(5.months.ago)..(4.months.ago)],
[(3.months.ago)..(2.months.ago)],
[(1.month.ago)..(15.days.ago)]
]
and a Post model with :created_at attribute.
I want to find posts where created_at value is in this range, so the goal is to create a query like:
SELECT * FROM posts WHERE created_at
BETWEEN '2011-04-06' AND '2011-08-06' OR
BETWEEN '2011-09-06' AND '2011-10-06' OR
BETWEEN '2011-11-06' AND '2011-12-06' OR
BETWEEN '2012-01-06' AND '2012-02-06' OR
BETWEEN '2012-02-06' AND '2012-03-23';
If you have only one range like this:
range = (12.months.ago)..(8.months.ago)
we can do this query:
Post.where(:created_at => range)
and query should be:
SELECT * FROM posts WHERE created_at
BETWEEN '2011-04-06' AND '2011-08-06';
Is there a way to make this query using a notation like this Post.where(:created_at => range)?
And what is the correct way to build this query?
Thank you
It gets a little aggressive with paren, but prepare to dive down the arel rabbit hole
ranges = [
((12.months.ago)..(8.months.ago)),
((7.months.ago)..(6.months.ago)),
((5.months.ago)..(4.months.ago)),
((3.months.ago)..(2.months.ago)),
((1.month.ago)..(15.days.ago))
]
table = Post.arel_table
query = ranges.inject(table) do |sum, range|
condition = table[:created_at].in(range)
sum.class == Arel::Table ? condition : sum.or(condition)
end
Then, query.to_sql should equal
(((("sessions"."created_at" BETWEEN '2011-06-05 12:23:32.442238' AND '2011-10-05 12:23:32.442575' OR "sessions"."created_at" BETWEEN '2011-11-05 12:23:32.442772' AND '2011-12-05 12:23:32.442926') OR "sessions"."created_at" BETWEEN '2012-01-05 12:23:32.443112' AND '2012-02-05 12:23:32.443266') OR "sessions"."created_at" BETWEEN '2012-03-05 12:23:32.443449' AND '2012-04-05 12:23:32.443598') OR "sessions"."created_at" BETWEEN '2012-05-05 12:23:32.443783' AND '2012-05-21 12:23:32.443938')
And you should be able to just do Post.where(query)
EDIT
You could also do something like:
range_conditions = ranges.map{|r| table[:created_at].in(r)}
query = range_conditions.inject(range_conditions.shift, &:or)
to keep it a little more terse
I suggest you try the pure string form:
# e.g. querying those in (12.months.ago .. 8.months.ago) or in (7.months.ago .. 6.months.ago)
Post.where("(created_at <= #{12.months.ago} AND created_at >= #{8.months.ago} ) OR " +
"(created_at <= #{7.months.ago} AND created_at >= #{6.months.ago} )" )
In your case, I would suggest to use mysql IN clause
Model.where('created_at IN (?)', ranges)

Having a problem with my Model query

Pls i have the following code in my model
Letter.count(:id, :conditions => ["language_id = #{lang} AND :created_at => '#{start_date.to_date.strftime('%Y-%m-%d')}'..'#{end_date.to_date.strftime('%Y-%m-%d')}' " ])
I am trying to get the count of letters.id of different letters between the given dates.
This is the error an having...
Please those anyone know what am doing wrong...Thanks
SQLite3::SQLException: near ">": syntax error: SELECT COUNT("letters"."id") FROM "letters" WHERE ("letters".language_id = 1) AND (language_id = 1 AND :created_at => '2011-05-01'..'2011-05-08
This can be much simplified. A couple points:
You don't use the :created_at => ... format within a string
You need to use between ? and ? for dates.
You don't need to manually strftime your dates, Rails will handle this automatically.
In Rails 3, the preferred way is to use where(...) instead of a :conditions Hash for your count(...).
You should probably use Rails' safe interpolation for your language_id field too
Letter.where("language_id = ? AND created_at between ? and ?", lang, start_date.to_date, end_date.to_date).count