ROR: MySQL query expression - mysql

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.

Related

convert mysql query into rails query

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

Ruby on rails database interface

I'm used to MySQL but trying to use Ruby on Rails right now. In MySQL, I would have two tables, with one containing a reference to another ("posts" referring to "topic"). A MySQL query doing what I want would be similar to "SELECT * FROM Posts WHERE posts.topic="topic" ("topic" here is a variable).
However, trying to work with the Ruby model stuff has me confused. The variables being passed between the controller and view are null because they are empty tables.
In my controller:
def topic
#topic = Topic.where(params[:topic])
#posts = Post.where(topic: #topic.object_id)
end
I don't know how to select the posts which have the topic defined by the "topic" variable.
In the view:
<% #posts.each do |post| %>
<p><%= post.title %></p>
<% end %>
The migration files:
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.string :text
t.references :topic
t.timestamps
end
end
end
class CreateTopics < ActiveRecord::Migration
def change
create_table :topics do |t|
t.string :topic
t.timestamps
end
end
end
Given that Post and Topic are related, according to your migrations at least, in the models you should be stating"
class Topic
has_many :posts
and
class Post
belongs_to :topic
Given that you then have an instance of Topic, #topic, you can retrieve all the related records with:
#posts = #topic.posts
I think those methods you put in your controller are fine where they are, but keep in mind that the Rails way is "fat models, skinny controllers." If you put that logic in the model as a method, it's much easier to read in the controller. Also, you should look into scopes, as they'll help you with queries like this down the line too.
In any case, you should stick the following in your Topic model:
scope :by_name, ->(name) { where(topic: name) }
That's essentially the same as doing the following:
def self.by_name(name)
where(topic: name)
end
On your posts model, you'd be able to do the following:
scope :by_topic, ->(topic) { where(topic_id: topic) }
The other problem with what you've stuck in your controller is that when you use scopes, or a "where", it returns an array that contains all of the different records that match your query terms. So, when you call #topic = Topic.where(params[:topic]), you're getting back an array of objects. Therefore, when you do a #topic.id, you're trying to get back the id of an array instead of one object.
Based off of what I showed you before, it makes much more sense for you to do this:
def topic
#topic = Topic.by_name(params[:topic]).first #this returns the first record
#post = Post.by_topic(#topic.id)
end
That will return an array of posts that match the first topic name that you query for.
Alright, first a primer on how database design and how Rails (really, ActiveRecord) works. Basically, you should be connecting posts.topic_id = topic.id, not posts.topic = topic.topic.
Your migration is correct as is, create_table automatically includes an :id PRIMARY KEY column. That said you should know that these are all equivalent:
t.references :topic
t.belongs_to :topic
t.integer :topic_id
In your view, instead of embedding topic.topic and passing that to the controller when the form is submitted, embed topic.id (the documentation for the select helper has a good example of this) and in your controller:
#topic = Topic.find params[:id]
#posts = #topic.posts

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)

Query with OR condition through an association

I'm trying to facilitate finding a user record via either username or email. THe only issue is that the username is stored on an associated UserDetail model.
class User
has_one :user_detail
def self.find_by_username_or_email(value)
# This is effectively pseudocode for the query I'd like to write.
query = "lower(email) = :value OR user_detail.username = :value"
where([query, { value: value }]).first
end
end
How can I write a query which matches either the email or the username on the associated user_detail record?
You were almost there!
This should work for you:
class User < ActiveRecord::Base
has_one :user_detail
def self.find_by_username_or_email(value)
query = "lower(users.email) = :value OR user_details.username = :value"
includes(:user_detail).where(query, value: value).first
end
end
seems like you need to write
self.includes(:user_detail).where(" user.email = ? OR user_detail.user_name =?", param[1], param[2])

ActiveRecord find identical set in many_to_many models

I have an anti-pattern in my Rails 3 code and I was wondering how to do this properly.
Let's say a customer orders french fries and a hamburger. I want to find out if such an order has been placed before. To keep it simple each item can only be ordered once per order (so no "two hamburgers please") and there are no duplicate orders either.
The models:
Order (attributes: id)
has_many :items_orders
has_many :items, :through => :items_orders
Item (attributes: id, name)
has_many :items_orders
has_many :orders,:through => :items_orders
ItemsOrder (attributes: id, item_id, order_id)
belongs_to :order
belongs_to :item
validates_uniqueness_of :item_id, :scope => :order_id
The way I do it now is to fetch all orders that include at least one of the line items. I then iterate over them to find the matching order. Needless to say that doesn't scale well, nor does it look pretty.
order = [1, 2]
1 and 2 correspond to the Item ids of fries and hamburgers.
candidates = Order.find(
:all,
:include => :items_orders,
:conditions => ["items_orders.item_id in (?)", order])
previous_order = nil
candidates.each do |candidate|
if candidate.items.collect{|i| i.id} == order
previous_order = candidate
break
end
end
I'm using MySQL and Postgress so I'm also open for a standard SQL solution that ignores most of ActiveRecord.
Assuming you only want to find identical orders, I'd be tempted to use a hash to achieve this. I'm not able to test the code I'm about to write, so please check it before you rely on it!
Something like this:
- Create a new attribute order_hash in your Order model using a migration.
- Add a before_save hook that updates this attribute using e.g. an MD5 hash of the order lines.
- Add a method for finding like orders which uses the hash to find other orders that match quickly.
The code would look a little like this:
class Order < ActiveRecord
def before_save
self.order_hash = calculate_hash
end
def find_similar
Order.where(:order_hash => self.calculate_hash)
end
def calculate_hash
d = Digest::MD5.new()
self.items.order(:id).each do |i|
d << i.id
end
d.hexdigest
end
end
This code would allow you to create and order, and then calling order.find_similar should return a list of orders with the same items attached. You could apply exactly the same approach even if you had item quantities etc. The only constraint is that you have to always be looking for orders that are the same, not just vaguely alike.
Apologies if the code is riddled with bugs - hopefully you can make sense of it!