I write query to find out how much the user has reduced calories in a week, but I have this error.
How to avoid mistakes?
def self.calories_burned(current_user)
week = ((created_at - current_user.first_program_started_at.utc.beginning_of_day) / 86400 / 7).ceil.to_i || 1
find_by_sql("
SELECT
count(*) as cnt,
WEEK(#{week}) as week_number
FROM
user_daily_updates
WHERE
user_id=#{current_user.id}
GROUP BY
week_number
")
end
When you write this:
def self.calories_burned(current_user)
etc...
end
it's a method that can only be called like this:
MyUserModel.calories_burned(some_user)
In this case you are running the code on the class before it is instantiated, this means that the model hasn't even attached itself to a connection to the database and because of that you will not be able to access attributes that pertain to your model.
On the other hand, if you write this:
def calories_burned
etc...
end
You don't need to pass the user to the method, you simply call it on the controller after instantiating your model, like this:
id = 123
current_user = MyUserModel.find(id)
current_user.calories_burned
where current_user.calories_burned will return the value you are looking for based on the current user.
After taking a closer look at your method
it should look more like this:
def calories_burned
week = ((created_at - first_program_started_at.utc.beginning_of_day) / 86400 / 7).ceil.to_i || 1
UserDailyUpdate.where(["user_id = ?", id]).where(["week = ?",week]]).count(:all, :group => 'week_number')
end
If I understood correctly what you were trying to do with your query, this should give you the same result. Now, I should mention I am assuming that when you created the table user_daily_updates, you also created a model UserDailyUpdate.
You should take a look at ActiveRecord's documentation for searching using conditions (all of section 2) in order to have a better understanding of what I just did.
Apneadiving is right, but I would also look to refactor your code:
def self.calories_burned(current_user)
week = ((##created_at## - current_user.first_program_started_at.utc.beginning_of_day) / 86400 / 7).ceil.to_i || 1
select("#{self.table_name}.*, count(*) as cnt, WEEK(#{week}) as week_number").where(user_id: current_user.id).group(:week_number)
end
You'll need to pull created_at from the db, as it won't be present unless you have an instance of a class already loaded
Related
I'm building a reporting system for a chat made in Ruby on Rails but received some comments telling me that my approach is inefficient.
Here's a little sample of how my reports work:
I have a handler that is called each month which calls a Report mailer Like this:
ReportMailer.monthly_report(user).deliver_later
This is how the mailer looks:
class ReportMailer < ApplicationMailer
default from: ENV["DEFAULT_MAILER_FROM"],
template_path: 'mailers/report_mailer'
def monthly_report(agent)
#agent = agent
#organization = agent.organization
#report = Report.new #organization
mail(to: agent.email, subject: #report.email_subject)
end
end
I'm trying to calculate the data using a "plain old" Ruby class:
module Reports
class Component < Report
def initialize(subject)
#component = subject
#cache = {}
end
attr_reader :component
# DELEGATIONS
# -----------------------
delegate :chat_messages, to: :component
def response_count
count = 0
explore_msgs { |msg, next_msg| count += 1 if response? msg, next_msg }
return count
end
def response_time
time = 0
explore_msgs { |msg, next_msg| time += time_difference msg, next_msg if response? msg, next_msg }
return time.to_i.seconds
end
def avg_response_time
#cache[__method__] ||= (response_time / response_count if response_count > 0)
end
private
def response?(msg, next_msg)
next_msg&.user_type == 'Agent' && msg.user_type == 'User' && msg.conversation_id == next_msg.conversation_id && time_difference(msg, next_msg).seconds < 8.hours
end
def time_difference(msg, next_msg)
(next_msg.created_at - msg.created_at).abs
end
def explore_msgs
chat_messages.each_with_index do |msg, i|
next_msg = chat_messages[i+1]
yield msg, next_msg
end
end
end
end
I'm concerned with improving performance. I implemented a simple caching system into the class in charge of making the calculations which made huge improvements in the system efficiency, however, I'm concerned that making these calculations in Ruby might create bottlenecks or that it might not be a scalable solution.
It could be faster. The problem I see is that you are looking a one record and the following record. So how would you get the database to compare the two records?
In straight SQL, I would join the table to itself, group by the first instance of the table and do a min(created_at) on the second instance of the table.
Using our companies table, the SQL looks like this:
select rc1.id, rc1.created_at, min(rc2.created_at)
from companies rc1 inner join companies rc2 on rc1.created_at < rc2.created_at
group by rc1.id
You can add the difference to the select.
This will be certainly be slow if the created_at field is not indexed and the number of records in the table is large.
You can add the test for Agent and User to the having clause.
The query is tricky and the database might not be able to do this fast. It will also be tricky if you try to get ActiveRecord to build the query for you.
However, I think everything you are trying to do in your code can be done this way by the database.
Your query might look like this:
select chat_messages.*,
min(next_msg.created_at) as next_created_at,
next_msg.created_at - chat_messages.created_at as created_at_diff
from chat_messages inner join chat_messages next_msg
on chat_messages.created_at < chat_messages.created_at
and chat_messages.user_type = 'User'
group by chat_messages.id
having next_msg.user_type = 'Agent'
and TIMESTAMPDIFF(HOUR, min(next_msg.created_at), chat_messages.created_at) < 8
I'm looking to get a .count based format, by grouping a method on the model.
What I've tried is along the lines of
Model.all.group(:age_at_x).count
and
Model.all.group_by { |v| v.age_at_x }.count
With age_at_x defined in the model. I know that this tries to group in MySQL with a method, but I can't seem to figure out to actually group by the method?
def age_at_x
date = Time.new(2016, 6, 25)
dob = self.birth
date.year - dob.year - ((date.month > dob.month || (date.month == dob.month && date.day >= dob.day)) ? 0 : 1)
end
I'm looking to count the different ages, and see how many of each - not the number of ages.
Furthermore in the future, to count by 18+ and under 18.
If I understand your question, you are trying to count people by diferentes ages, try that:
Model.select(:birth).distinct.count
I have a relation between two objects. Let's say it like this: Model1 has_many Model2 (That doesn't really matter)
And say, I want to filter-out some of the results:
a = Model1.find(123)
b = a.model2
And now, for example, I want to select only EVEN records (by ID)
If I do following: b.select {|x| x.id % 2 == 0} then it returns all even records as expected. And NO additional database queries created.
But if I define a class method in the Model2:
def self.even_records
select {|x| x.id % 2 == 0}
end
Then, for some magic reason it makes an additional query to database, that looks like it re-instantiated the "b" variable (re-loads the relation):
Model2 Load (0.4ms) SELECT `model2`.* FROM `model2` WHERE `model2`.`model1_id` = 123
Why it behaves so ? Is there any way I can fix it ?
P.S I have no fishy callbacks, like after_find or whatsoever defined in any of models.
ActiveRecord scopes are evaluated lazily, i.e. scope is evaluated when its result is necessary. When you try this code in console, inspect method are called implicitly on every evaluated object, including ActiveRecord::Relation instance returned from
b = a.model2
call. After calling inspect on ActiveRecord::Relation, scope is evaluated and DB query is created since it's necessary to show inspect return value properly.
On the contrary, when you run your code outside rails console,
b = a.model2
won't produce DB query, thus there will probably be only one database query.
The Basic difference between these two is that when you call select method on b which is an array, than it calls the enumerable method select.
b.select {|x| x.id % 2 == 0}
and when you write in a method, it calls the select method of activerecord query interface.
def self.even_records
select {|x| x.id % 2 == 0}
end
BTW Ruby have methods like even? and odd?, so you can directly call them :
even_records = b.select{|x| x.id.even?}
odd_records = b.select{|x| x.id.odd? }
Edit: I found a simple solution for you, you can define a scope in your model Model2 like below,
scope :even_records, -> { where ('id % 2 == 0') }
and now if you will call :
Model2.even_records
you will have your even_records.
Thanks
I don't know if I'm just looking in the wrong places here or what, but does active record have a method for retrieving a random object?
Something like?
#user = User.random
Or... well since that method doesn't exist is there some amazing "Rails Way" of doing this, I always seem to be to verbose. I'm using mysql as well.
Most of the examples I've seen that do this end up counting the rows in the table, then generating a random number to choose one. This is because alternatives such as RAND() are inefficient in that they actually get every row and assign them a random number, or so I've read (and are database specific I think).
You can add a method like the one I found here.
module ActiveRecord
class Base
def self.random
if (c = count) != 0
find(:first, :offset =>rand(c))
end
end
end
end
This will make it so any Model you use has a method called random which works in the way I described above: generates a random number within the count of the rows in the table, then fetches the row associated with that random number. So basically, you're only doing one fetch which is what you probably prefer :)
You can also take a look at this rails plugin.
We found that offsets ran very slowly on MySql for a large table. Instead of using offset like:
model.find(:first, :offset =>rand(c))
...we found the following technique ran more than 10x faster (fixed off by 1):
max_id = Model.maximum("id")
min_id = Model.minimum("id")
id_range = max_id - min_id + 1
random_id = min_id + rand(id_range).to_i
Model.find(:first, :conditions => "id >= #{random_id}", :limit => 1, :order => "id")
Try using Array's sample method:
#user = User.all.sample(1)
In Rails 4 I would extend ActiveRecord::Relation:
class ActiveRecord::Relation
def random
offset(rand(count))
end
end
This way you can use scopes:
SomeModel.all.random.first # Return one random record
SomeModel.some_scope.another_scope.random.first
I'd use a named scope. Just throw this into your User model.
named_scope :random, :order=>'RAND()', :limit=>1
The random function isn't the same in each database though. SQLite and others use RANDOM() but you'll need to use RAND() for MySQL.
If you'd like to be able to grab more than one random row you can try this.
named_scope :random, lambda { |*args| { :order=>'RAND()', :limit=>args[0] || 1 } }
If you call User.random it will default to 1 but you can also call User.random(3) if you want more than one.
If you would need a random record but only within certain criteria you could use "random_where" from this code:
module ActiveRecord
class Base
def self.random
if (c = count) != 0
find(:first, :offset =>rand(c))
end
end
def self.random_where(*params)
if (c = where(*params).count) != 0
where(*params).find(:first, :offset =>rand(c))
end
end
end
end
For e.g :
#user = User.random_where("active = 1")
This function is very useful for displaying random products based on some additional criteria
Strongly Recommend this gem for random records, which is specially designed for table with lots of data rows:
https://github.com/haopingfan/quick_random_records
Simple Usage:
#user = User.random_records(1).take
All other answers perform badly with large database, except this gem:
quick_random_records only cost 4.6ms totally.
the accepted answer User.order('RAND()').limit(10) cost 733.0ms.
the offset approach cost 245.4ms totally.
the User.all.sample(10) approach cost 573.4ms.
Note: My table only has 120,000 users. The more records you have, the more enormous the difference of performance will be.
UPDATE:
Perform on table with 550,000 rows
Model.where(id: Model.pluck(:id).sample(10)) cost 1384.0ms
gem: quick_random_records only cost 6.4ms totally
Here is the best solution for getting random records from database.
RoR provide everything in ease of use.
For getting random records from DB use sample, below is the description for that with example.
Backport of Array#sample based on Marc-Andre Lafortune’s github.com/marcandre/backports/ Returns a random element or n random elements from the array. If the array is empty and n is nil, returns nil. If n is passed and its value is less than 0, it raises an ArgumentError exception. If the value of n is equal or greater than 0 it returns [].
[1,2,3,4,5,6].sample # => 4
[1,2,3,4,5,6].sample(3) # => [2, 4, 5]
[1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size
[].sample # => nil
[].sample(3) # => []
You can use condition with as per your requirement like below example.
User.where(active: true).sample(5)
it will return randomly 5 active user's from User table
For more help please visit : http://apidock.com/rails/Array/sample
The following situation:
I have a poi model, which has many pictures (1:n). I want to recalculate the counter_cache column, because the values are inconsistent.
I've tried to iterate within ruby over each record, but this takes much too long and quits sometimes with some "segmentation fault" bugs.
So i wonder, if its possible to do this with a raw sql query?
If, for example, you have Post and Picture models, and post has_many :pictures, you can do it with update_all :
Post.update_all("pictures_count=(Select count(*) from pictures where pictures.post_id=posts.id)")
I found a nice solution on krautcomputing.
It uses reflections to find all counter caches of a project, SQL queries to find only the objects that are inconsistent and use Rails reset_counters to clean things up.
Unfortunately it only works with "conventional" counter caches (no class name, no custom counter cache names) so I refined it:
Rails.application.eager_load!
ActiveRecord::Base.descendants.each do |many_class|
many_class.reflections.each do |name, reflection|
if reflection.options[:counter_cache]
one_class = reflection.class_name.constantize
one_table, many_table = [one_class, many_class].map(&:table_name)
# more reflections, use :inverse_of, :counter_cache etc.
inverse_of = reflection.options[:inverse_of]
counter_cache = reflection.options[:counter_cache]
if counter_cache === true
counter_cache = "#{many_table}_count"
inverse_of ||= many_table.to_sym
else
inverse_of ||= counter_cache.to_s.sub(/_count$/,'').to_sym
end
ids = one_class
.joins(inverse_of)
.group("#{one_table}.id")
.having("MAX(#{one_table}.#{counter_cache}) != COUNT(#{many_table}.id)")
.pluck("#{one_table}.id")
ids.each do |id|
puts "reset #{id} on #{many_table}"
one_class.reset_counters id, inverse_of
end
end
end
end