convert mysql query into rails query - mysql

Hi all I have a problem converting mysql query into rails query.
I have these models -
class User < ApplicationRecord
has_many :comments, foreign_key: "commenter_id"
end
class Comment < ApplicationRecord
belongs_to :commenter, class_name: "User"
end
Can anyone help me out with converting following query into rails query-
UPDATE comments
INNER JOIN users on comments.commenter_id = users.id
SET comments.deleted_at = users.deleted_at
WHERE users.deleted_at IS NOT NULL
I am trying to make soft-delete comments whose commenter was softly deleted.
UPDATE 1:
so far I can able to do it by using this-
User.only_deleted.includes(:comments).find_each do |u|
u.comments.update_all(deleted_at: u.deleted_at)
end
But I want to do this on single query without having to iterate over the result.
UPDATE 2:
I am using acts_as_paranoid gem, so unscoping user is needed and my final query became:
User.unscoped{Comment.joins(:commenter).where.not(users: {deleted_at: nil}).update_all("comments.deleted_at = users.deleted_at")

This should work on MySQL:
Comment
.joins(:user)
.where.not(users: { deleted_at: nil })
.update_all("comments.deleted_at = users.deleted_at")
This won't work on Postgres since its missing a FROM clause for users.
A less performant but polyglot option is:
Comment
.joins(:user)
.where.not(users: { deleted_at: nil })
.update_all("deleted_at = ( SELECT users.deleted_at FROM users WHERE comments.id = users.id )")
This is still probably an order of magnitude better than iterating through the records in Ruby since you eliminate the traffic delay between your app server and the db.

From your comments, I think this is what you want:
Comment.where.not(user_id: nil).each { |comment| comment.update_attributes(deleted_at: comment.user.deleted_at)
Or slightly more readable:
Comment.all.each do |comment|
next unless comment.user.present?
comment.update_attributes(deleted_at: comment.user.deleted_at)
end

The code below should execute number of queries corresponding to deleted_users and without loading User and any associated Comments in memory
deleted_users_data_arr = User.only_deleted.pluck(:id, :deleted_at)
deleted_users_data_arr.each do |arr|
deleted_user_id = arr[0]
user_deleted_at = arr[1]
Comment.where(commenter_id: deleted_user_id).update_all(deleted_at: user_deleted_at)
end

Related

ActiveRecord- Inner Join two SQL tables + Where clauses

I have two tables in MySQL and I would like to call a SQL query using an inner join and multiple Where clauses based on a form's input. I am aware that you can execute a raw SQL Query via ActiveRecord::Base.connection, but I'd like to learn how to do so using ActiveRecord objects. The schema for the two MySQL tables are like so:
Describe Options;
'VIN','varchar(45)','NO','PRI',NULL,''
'LEATHER','varchar(45)','YES','',NULL,''
'4WD','varchar(45)','YES','',NULL,''
'FOGLIGHTS','varchar(45)','YES','',NULL,''
'DVD','varchar(45)','YES','',NULL,''
'SURROUND','varchar(45)','YES','',NULL,''
and
Describe Inventory;
'VIN','varchar(30)','NO','PRI',NULL,''
'MAKE','varchar(30)','NO','',NULL,''
'MODEL','varchar(30)','NO','',NULL,''
'TYPE','varchar(50)','NO','',NULL,''
I would like to execute a SQL script like so:
Select Inventory.* from Inventory
INNER JOIN Options
ON Inventory.VIN = Options.VIN
WHERE Inventory.Make = "Toyota"
AND Options.Leather = "Yes";
My Ruby classes in ActiveRecord are like so:
class Option < ActiveRecord::Base
self.table_name = "Options"
end
class Inventory < ActiveRecord::Base
self.table_name = "INVENTORY"
end
Again, I know that I can just input the query as a script, but I'd like to learn how to do it via Ruby best practices
You can change into this:
Inventory.joins("INNER JOIN Options ON Inventory.VIN = Options.VIN")
.where("Inventory.Make = ? AND Options.Leather = ?", "Toyota", "YES")
In my opinion, I'd say that you have to change you table into inventories and options for model Inventory and Option so you don't need to use set_table_name in each model. It's about rails convention style code. Then you can see the model like this.
class Inventory < ActiveRecord::Base
has_many :options, foreign_key: "VIN"
end
class Option < ActiveRecord::Base
belongs_to :inventory, foreign_key: "VIN"
end
I hope this help you.

ruby on rails public_activity gem fetch followed activity

i have used public_activity gem in my app and also the act_as_follower gem
where a user can follow other user
the logic i am using to fetch all the following activities is
#follow_activities = PublicActivity::Activity.where(trackable_type: 'follow', key: 'follow.create')
here #follow_activities is fetching all the records where the following has been done but i want to limit this query,
It should fetch the follow activities only of those users which the current_user has followed.
fetching all queries is a bad idea.
for elaboration i am showing you the models i have included
class User< ActiveRecord::Base
acts_as_follower
acts_as_followable
end
and the follow model is as
class Follow < ActiveRecord::Base
include PublicActivity::Model
tracked owner: ->(controller, model) { controller && controller.current_user }
extend ActsAsFollower::FollowerLib
extend ActsAsFollower::FollowScopes
# NOTE: Follows belong to the "followable" interface, and also to followers
belongs_to :followable, :polymorphic => true
belongs_to :follower, :polymorphic => true
def block!
self.update_attribute(:blocked, true)
end
end
Please tell me how can i limit the records fetching. Thankx in advance
I finally figured it out i need to do this
follow_activities = PublicActivity::Activity.where(trackable_type: 'follow', key: 'follow.create', owner_id: current_user.all_following)
now this will fetch the records of only those users which the current_user has followed

Rails 4: ActiveRecord or MySQL query where no related models have attribute

Having a tough time with this one. I have a Job model, and a JobStatus model. A job has many statuses, each with different names (slugs in this case). I need an 'active' method I can call to find all jobs where none of the associated statuses has a slug of 'dropped-off'.
class Job < ActiveRecord::Base
belongs_to :agent
has_many :statuses, :class_name => "JobStatus"
validates :agent_id,
:pickup_lat,
:pickup_lng,
:dropoff_lat,
:dropoff_lng,
:description,
presence: true
class << self
def by_agent agent_id
where(agent_id: agent_id)
end
def active
#
# this should select all items where no related job status
# has the slug 'dropped-off'
#
end
end
end
Job Status:
class JobStatus < ActiveRecord::Base
belongs_to :job
validates :job_id,
:slug,
presence: true
end
The closest I've gotten so far is:
def active
joins(:statuses).where.not('job_statuses.slug = ?', 'dropped-off')
end
But it's still selecting the Job that has a dropped-off status because there are previous statuses that are not 'dropped-off'. If i knew the raw sql, I could probably work it into activerecord speak but I can't quite wrap my head around it.
Also not married to using activerecord, if the solution is raw SQL that's fine too.
Job.where.not(id: JobStatus.where(slug: 'dropped-off').select(:job_id))
will generate a nested subquery for you.
Not the cleanest method, but you could use two queries.
# Getting the ID of all the Jobs which have 'dropped-off' JobStatuses
dropped_off_ids = JobStatus.where(slug: 'dropped-off').pluck(:job_id)
# Using the previous array to filter the Jobs
Job.where.not(id: dropped_off_ids)
Try this:
def active
Job.joins(:statuses).where.not('job_statuses.slug' => 'dropped-off')
end
or this:
def active
Job.joins(:statuses).where('job_statuses.slug != ?', 'dropped-off')
end
I think you may want to reevaluate your data model somewhat. If the problem is that you're turning up old statuses when asking about Job, you likely need to have column identifying the current status for any job, i.e. job.statuses.where(current_status: true)
Then you can very easily grab only the rows which represent the current status for all jobs and are not "dropped-off".
Alternatively, if I'm misunderstanding your use case and you're just looking for any job that has ever had that status, you can just go backwards and search for the status slugs first, i.e.
JobStatus.where.not(slug: "dropped-off").map(&:job)

rails 2.3 convert hash into mysql query

I'm trying to find out how rails converts a hash such as (This is an example please do not take this literally I threw something together to get the concept by I know this query is the same as User.find(1)):
{
:select => "users.*",
:conditions => "users.id = 1",
:order => "username"
}
Into:
SELECT users.* FROM users where users.id = 1 ORDER BY username
The closest thing I can find is ActiveRecord::Base#find_every
def find_every(options)
begin
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
instantiate_collection( (format.decode(connection.get(path, headers).body) || []), prefix_options )
end
rescue ActiveResource::ResourceNotFound
# Swallowing ResourceNotFound exceptions and return nil - as per
# ActiveRecord.
nil
end
end
I'm unsure as to how to modify this to just return what the raw mysql statement would be.
So after a few hours of digging I came up with an answer although its not great.
class ActiveRecord::Base
def self._get_finder_options options
_get_construct_finder_sql(options)
end
private
def self._get_construct_finder_sql(options)
return (construct_finder_sql(options).inspect)
end
end
adding this as an extension gives you a publicly accessible method _get_finder_options which returns the raw sql statement.
In my case this is for a complex query to be wrapped as so
SELECT COUNT(*) as count FROM (INSERT_QUERY) as count_table
So that I could still use this with the will_paginate gem. This has only been tested in my current project so if you are trying to replicate please keep that in mind.

ROR: MySQL query expression

I have set up the Model, but I don't know how to write the code in controller to get result of the following SQL query..
SELECT users.name, events.* FROM users, events WHERE users.id=events.uid
Thanks a lot.
I rename events.uid -> events.user_id
And set up the Model for both of them, attributes of events are
t.integer :user_id
t.string :title
t.datetime :hold_time
t.string :place
t.text :description
And new error is
undefined method title for #<User:0x3fb04d8>
Sorry for bothering you guys..
Assuming you have set up your associations you should be able to do it like so:
u = Users.find(:all, :include => :events, :conditions => "users.id == events.uid")
You can use the find_by_sql function to execute arbitrary SQL, so in your case you could do:
e = Event.find_by_sql( 'SELECT events.*, users.name as user_name from users, events where user.id = events.uid')
e would now contain all events with matching user names and you can access the user name just like any other attribute, e.g. puts e.user_name. Of course it is not possible to change the username since it was generated in a SQL query.
This approach may not be in accordance with Rails philosophy of DB agnostic applications, however I find that it is sometimes much easier (and probably faster) to use SQL directly instead of trying to achieve the same thing with ActiveRecord.
I cant comment yet (not enough rep), so I am posting an answer, but the:
"users.id == events.uid"
should be:
"users.id = events.uid"
I am not sure that will solve your problem, but it may.
if you want to find all users and events, you can do it like this:
users = User.all
users.each do |user|
= user.name
user.events.each do |event|
= event.name
end
end
and your models would look like this:
class User < ActiveRecord::Base
has_many :events
end
class Event < ActiveRecord::Base
belongs_to :user
end
I am sure there is another way to do it with event.uid, but I am not that advanced yet.