I want to add a comment to every request send by active record in order to found source in mysql slow query. How can I modify the request before ActiveRecord sends it?
For example i want to have this in my central mysql slow query log.
SELECT * FROM articles
-- File: refresh-article.rb
ActiveRecord already logs db requests with timing information to your app log.
I solve the problem with monkey patch
ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
def execute_with_log(sql, name=nil)
sql = "-- Script: #{$0}\n#{sql}"
execute_without_log(sql, name)
end
alias_method_chain :execute, :log
end
In your rails app, you can see your queries with timing in log/(production|development).log.
However if you want anything more than that, I suggest checking out NewRelic in development mode. It is free, and it shows your the source of where that query was executed(which looks like what you want). It really is one of the best logging/performance analyzer out there.
I found a solution by monkey patch MySQL2::execute
ActiveRecord 6 allows queries to be annotated
User.annotate("selecting user names").select(:name)
# SELECT "users"."name" FROM "users" /* selecting user names */
User.annotate("selecting", "user", "names").select(:name)
# SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-annotate
You could combine this with the caller_locations kernel method:
User.annotate("#{caller_locations(1,1).first}").select(:name)
https://www.rubydoc.info/stdlib/core/2.0.0/Kernel:caller_locations
Related
I am working on an app that allows Members to take a survey (Member has a one to many relationship with Response). Response holds the member_id, question_id, and their answer.
The survey is submitted all or nothing, so if there are any records in the Response table for that Member they have completed the survey.
My question is, how do I re-write the query below so that it actually works? In SQL this would be a prime candidate for the EXISTS keyword.
def surveys_completed
members.where(responses: !nil ).count
end
You can use includes and then test if the related response(s) exists like this:
def surveys_completed
members.includes(:responses).where('responses.id IS NOT NULL')
end
Here is an alternative, with joins:
def surveys_completed
members.joins(:responses)
end
The solution using Rails 4:
def surveys_completed
members.includes(:responses).where.not(responses: { id: nil })
end
Alternative solution using activerecord_where_assoc:
This gem does exactly what is asked here: use EXISTS to to do a condition.
It works with Rails 4.1 to the most recent.
members.where_assoc_exists(:responses)
It can also do much more!
Similar questions:
How to query a model based on attribute of another model which belongs to the first model?
association named not found perhaps misspelled issue in rails association
Rails 3, has_one / has_many with lambda condition
Rails 4 scope to find parents with no children
Join multiple tables with active records
You can use SQL EXISTS keyword in elegant Rails-ish manner using Where Exists gem:
members.where_exists(:responses).count
Of course you can use raw SQL as well:
members.where("EXISTS" \
"(SELECT 1 FROM responses WHERE responses.member_id = members.id)").
count
You can also use a subquery:
members.where(id: Response.select(:member_id))
In comparison to something with includes it will not load the associated models (which is a performance benefit if you do not need them).
If you are on Rails 5 and above you should use left_joins. Otherwise a manual "LEFT OUTER JOINS" will also work. This is more performant than using includes mentioned in https://stackoverflow.com/a/18234998/3788753. includes will attempt to load the related objects into memory, whereas left_joins will build a "LEFT OUTER JOINS" query.
def surveys_completed
members.left_joins(:responses).where.not(responses: { id: nil })
end
Even if there are no related records (like the query above where you are finding by nil) includes still uses more memory. In my testing I found includes uses ~33x more memory on Rails 5.2.1. On Rails 4.2.x it was ~44x more memory compared to doing the joins manually.
See this gist for the test:
https://gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1
where.missing (Rails 6.1+)
Rails 6.1 introduces a new way to check for the absence of an association - where.missing.
Please, have a look at the following code snippet:
# Before:
Post.left_joins(:author).where(authors: { id: nil })
# After:
Post.where.missing(:author)
And this is an example of SQL query that is used under the hood:
Post.where.missing(:author)
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NULL
As a result, your particular case can be rewritten as follows:
def surveys_completed
members.where.missing(:response).count
end
Thanks.
Sources:
where.missing official docs.
Pull request.
Article from the Saeloun blog.
Notes:
where.associated - a counterpart for checking for the presence of an association is also available starting from Rails 7.
See offical docs and this answer.
Recently I have tried using Hanami, Ruby framework. I would like to execute migration with "bulk insert".
I checked following issue discussion.
Proposal: multi_create method for bulk records #406
But, I don't understand how to call ROM object from Hanami. Would you please explain how to do that and any web site to refer ?
Finally I have realized meaning of code.
At first, I wrote bulk_insert as instance method.
somes represents SQL table's name, I could use this with symbol
Repository sample
class SomeRepository < Hanami::Repository
def bulk_insert(data)
command(:create, somes, use: [:timestamps], result: :many).call(data)
end
end
Bulk insert sample
# we can pass array of hash
SomeRepository.new.bulk_insert(some_array)
SomeRepository.new.bulk_insert([{name: "sample1"}, {name: "sample2"}, {name: "sample3"}])
I have 2 models, an example:
class Report ...
belongs_to :answer_sheet
end
class AnswerSheet ...
has_one :report
end
When I do a:
#answersheet.report = Report.create(:data => 'bleah')
#answersheet.save
# and then create another report and assign it to the same #answersheet
# assuming at this stage #answersheet is already reloaded
#answersheet.report = Report.create(:data => 'new data')
#answersheet.save
# (irb) #answersheet.report returns the first report with the data 'bleah' and not
# the one with the new data.
Is this supposed to be the correct behavior?
If I want to update the association to the later report, how should I go about doing it?
It took me a few tries to see what you were talking about. But I got it now.
Take a look at the SQL and you'll find ActiveRecord is doing a select and then adding ASC and LIMIT 1. There can be more than one report records that refer to the same answer_sheet.
You can prevent this situation by adding a validation that checks for uniqueness of answer_sheet_id.
You should also start using save! and create! (note the bang operators) so exceptions are thrown during validation.
Lastly, calling Report.create followed by #answersheet.save performs two database transactions, whereas Report.new followed by #answersheet.save would perform just one.
I'm using Mongoid (v3) to access MongoDB, and want to perform this action:
db.sessionlogs.update(
{sessionid: '12345'}, /* selection criteria */
{'$push':{rows: "new set of data"}}, /* modification */
true /* upsert */
);
This works fine in the mongo shell. It's also exactly what I want since it's a single atomic operation which is important to me as I'm going to be calling it a lot. I don't want to have to do two operations -- a fetch and then an update. I've tried a bunch of things through mongoid, but can't get it to work.
How can I get MongoID out of the way and just send this command to MongoDB? I'm guessing there's some way to do this at the Moped level, but the documentation of that library is basically non-existent.
[Answer found while writing the question...]
criteria = Sessionlogs.collection.find(:sessionid => sessionid)
criteria.upsert("$push" => {"rows" => datarow})
Here is one way to do it:
session_log = SessionLog.new(session_id: '12345')
session_log.upsert
session_log.push(:rows, "new set of data")
Or another:
SessionLog.find_or_create_by(session_id: '12345').
push(:rows, "new set of data")
#push performs an atomic $push on the field. It is explained on the
Atomic Persistence page.
(Note: the examples use UpperCamelCase and snake_case as is Ruby convention.)
Don't go down to moped just yet, you can use find and modify operation to achieve the same thing (with all the default scope and inheritance goodies)
Sample to save an edge in a graph if not existed
edge = {source_id: session[:user_id],dest_id:product._id, name: edge_name}
ProductEdge.where(edge).find_and_modify(ProductEdge.new(edge).as_document,{upsert:true})
Is there any way that I can fire a raw mongo query directly in Ruby instead of converting them to the native Ruby objects?
I went through Ruby Mongo Tutorial, but I cannot find such a method anywhere.
If it were mysql, I would have fired a query something like this.
ActiveRecord::Base.connection.execute("Select * from foo")
My mongo query is a bit large and it is properly executing in the MongoDB console. What I want is to directly execute the same inside Ruby code.
Here's a (possibly) better mini-tutorial on how to get directly into the guts of your MongoDB. This might not solve your specific problem but it should get you as far as the MongoDB version of SELECT * FROM table.
First of all, you'll want a Mongo::Connection object. If
you're using MongoMapper then you can call the connection
class method on any of your MongoMapper models to get a connection
or ask MongoMapper for it directly:
connection = YourMongoModel.connection
connection = MongoMapper.connection
Otherwise I guess you'd use the from_uri constructor to build
your own connection.
Then you need to get your hands on a database, you can do this
using the array access notation, the db method, or get
the current one straight from MongoMapper:
db = connection['database_name'] # This does not support options.
db = connection.db('database_name') # This does support options.
db = MongoMapper.database # This should be configured like
# the rest of your app.
Now you have a nice shiny Mongo::DB instance in your hands.
But, you probably want a Collection to do anything interesting
and you can get that using either array access notation or the
collection method:
collection = db['collection_name']
collection = db.collection('collection_name')
Now you have something that behaves sort of like an SQL table so
you can count how many things it has or query it using find:
cursor = collection.find(:key => 'value')
cursor = collection.find({:key => 'value'}, :fields => ['just', 'these', 'fields'])
# etc.
And now you have what you're really after: a hot out of the oven Mongo::Cursor
that points at the data you're interested in. Mongo::Cursor is
an Enumerable so you have access to all your usual iterating
friends such as each, first, map, and one of my personal
favorites, each_with_object:
a = cursor.each_with_object([]) { |x, a| a.push(mangle(x)) }
There are also command and eval methods on Mongo::DB that might do what you want.
In case you are using mongoid you will find the answer to your question here.
If you're using Mongoid 3, it provides easy access to its MongoDB driver: Moped. Here's an example of accessing some raw data without using Models to access the data:
db = Mongoid::Sessions.default
# inserting a new document
collection = db[:collection_name]
collection.insert(name: 'my new document')
# finding a document
doc = collection.find(name: 'my new document').first
# "select * from collection"
collection.find.each do |document|
puts document.inspect
end