Translate SQL to Rails queries - ruby-on-rails

How can I write this SQL statement the 'Rails way'?
SELECT users.*, count(invitations.id) AS invitations_count
FROM users
LEFT OUTER JOIN invitations ON invitations.sender_id = users.id
GROUP BY users.id
HAVING count(invitations.sender_id) < 5 AND users.email_address NOTNULL
ORDER BY invitations_count
Should I be using squeel gem for these kinds of queries?

This is available if you want to just transfer the SQL:
User.find_by_sql("...")
However, if you follow rails conventions with your naming (invitations.user_id), and store the invitation count on users (and update it when you add invitations) rather than doing a join to get it each time, you could do this:
On users:
scope :emailable, where('users.email_address IS NOT NULL')
scope :low_invitations, where('users.invitation_count < 5')
Then to query users with under 5 invites and an email address, ordered by no of invites:
#users = User.emailable.low_invitations.order('invitation_count asc')
Then access the invitations for a user with something like:
#user.invitations
#user.invitations.count
etc
For the above, you would have to add an invitation_count col to users, change sender_id to user_id, and add some scopes to the user model. You could also probably use joins to get a count without having a denormalised invitation_count.
If you are going to use rails it's far easier to go with these conventions than against them, and you might find it worthwhile setting up a small experimental app and playing with relations, plus reading the associations guide:
http://guides.rubyonrails.org/association_basics.html

You need to define proper models for Invitations and Users, then connect them via n*1 or n*n relationships (via belongs_to, has_many, etc). Then you'll be able to write code that will generate this or similar query "under the hood".
When using Rails you have to stop thinking SQL and start thinking Models, Views, Controllers.

Related

How to design database tables for different types of Users?

I'm on a project with Rails, Postgresql and Active Record, where I have Users, that can be either Influencers, or Creators.
The Users have common columns such as email, password, first_name and last_name, but :
influencers will have followers, eg_rate, account columns
creators will have SIRET_number and specialty columns
How can I design my database so Influencers and Creators are kind of "child" of the Users table ? I mean, is it possible to have a db where I can access a User' followers or a User's specialty with one query, or I'll always have to do multiple queries to achieve this ? I've tried to create three tables for each, with a foreign key user_id in Influencers table and Creators table. I also tried to add a user_type column to my User table where I pass the values of either "influencer" or "creator", but I'm kind of lost on how to link every tables...
Thank you everyone.
Your approach is right.
You can create a table users with the common columns and add a foreign key to influencers and creators tables.
Then when you need to retrieve the data, you can use ActiveRecord relations to easily fetch data and use ActiveRecord's includes method for relations.
For example:
class Creator < ActiveRecord::Base
# The relation must be set
has_one :user
end
# To fetch data anywhere else:
Creator.find_by(SIRET_number: 1234).includes(:user)
If you need to retrieve a creator or influencer by an attribute from related users table, you can use joins:
Creator.joins(:users).where(users: {email: "foo#bar.com"})
Make sure you have the relations set in both User and Creator models.
Check out this topic for more info.
By using includes or joins instead of using creator.user you'll avoiding the unnecessary additional query. The downside is the syntax is now longer, you can maybe create your own getters to easily retrieve data instead of writing "includes" everytime.
Also assuming you're not aware of this method, I suggest you to read about the common N+1 problem with Rails & ActiveRecord. The same methods can solve a lot of problems for you.

ActiveRecord — Query all and include association which meets condition

Environment: Rails 3.2.22
Question:
Lets say I have the models Topics, Posts, and User.
Posts belongs to Topics
User has many Posts
I want to make a query of Topic.all, but includes all posts associated to a user.
I've tried include and eager_load with a where condition for the user id, but only topics with a post which meets the condition are return.
What I want is all topics return and include only posts which match the user_id condition.
After playing around with ActiveRecord I figured out how to do the query. It requires the left join as pointed out by #pshoukry, but it is missing two items.
AND statement is required to include only posts for a specific user.
An ActiveRecord method select needs to be appended at the end to include the fields you want.
To include all fields:
Topic.joins("LEFT JOIN posts ON posts.topic_id = topics.id AND posts.user_id = ?", user.id).select('topics.*, posts.*')
Now for the caveat. For those using Postgres and on Rails version 3.2.* there is a bug where the joined table will only return strings for ALL columns, disregarding the data type set. This issue is not present with Rails 4. There was an issue posted in the Rail's Github repo, but I can't seem to locate it. Since 3.2 is no longer supported they have no intention of fixing it.
Try using left join in your relation
Topic.joins("LEFT JOIN posts ON topics.id = posts.topic_id")

Active records complex relationship query

I have the following scenario:
User->HABTM->businesses
Suppliers->HABTM->businesses
Suppliers->HAS_MANY->Payments
I am having real trouble working out how to get all the payments for a user through the HABTM relationships that describe the business->supplier and User->business relationship.
I am after all payments that belong to the user through the supplier business relationship.
I can do this with SQL very easily but am having trouble doing it the rails way.
It's similar to a post on getting all the comments that belong to a user via a post model.
Any help would be appreciated.
Is it even possible?
I am doing this at the moment:
has_many :payments,:finder_sql => Proc.new {
%Q{
SELECT DISTINCT *
FROM payments
INNER JOIN businesses_users ON businesses_users.user_id
INNER JOIN businesses_suppliers ON businesses_suppliers.business_id
WHERE payments.supplier_id = businesses_suppliers.supplier_id AND businesses_users.user_id = #{id}
ORDER BY payments.created_at
}}
This lets me do user.payments
Kinda pressed for time, but wanted to chime in. First, your models seem to suggest that there might be an IS A relationship between User and Supplier, in which case you could employ polymorphic associations. If that's not the case, I'd look into the includes option within the activerecord query interface. In that manner, you can basically force AR to eager load the relationships down the chain. An example might look like:
all_users_and_their_pmts = User.includes(:businesses => {:suppliers => :payments })
An alternative, but highly inefficient, way to do this would be:
user_record.businesses.map {|b| b.suppliers.map {|s| s.payments}}.flatten
which would give you an array of payments. Using raw sql like you have above will be far more efficient than this, since activerecord can't chain the calls within map{}. I think :include would be a more idiomatic way for you to go, but your solution isn't horrible.

ActiveRecord: how to do nested queries on foreign keys?

I have 3 models, "Coach", "Team", and "Event"
An event has attributes away_team and home_team, both of which belong to Team. A team belongs to a coach, and a coach can have many teams.
What I'd like to do is find all events where the home_team is not coached by a particular coach. So, something along the lines of:
Event.where("home_team.team.coach_id NOT ?", coach.id)
The problem is figuring out how to write up that syntax. I'm guessing I need to include the "team" model, but I'm not sure how to write an activerecord include that joins through a specific foreign key.
In short, any ideas on how to do this without getting SQL errors (which I've had countless of tonight) would be very appreciated.
Cheers.
If I understand you correctly you need this kind of sql:
SELECT events.* FROM events JOIN team on events.home_team_id = teams.id WHERE teams.coach_id != coach_id
so in rails it should looks something like that:
Event.joins("join teams on events.home_team_id = teams.id").where("teams.coach_id != ?", coach_id)
Of course I don't know your exact table namings, so you should fit this solution to your problem.
If you have in Event model association: belogns_to: home_team, you can use only joins(:home_team).where(....)

How to find all items not related to another model - Rails 3

I have a fairly complicated lookup i'm trying to do in Rails and I'm not entirely sure how hoping someone can help.
I have two models, User and Place.
A user is related to Place twice. Once for visited_places and once for planned_places. Its a many to many relationship but using has_many :through. Here's the relationship from User.
has_many :visited_places
has_many :visited, :class_name=>"Place", :through=>:visited_places, :source=>:place
has_many :planned_places
has_many :planned, :class_name=>"Place", :through=>:planned_places, :source=>:place
In place the relationship is also defined. Here's the definition there
has_many :visited_users, :class_name=>"User", :through=>:visited_places
has_many :planned_users, :class_name=>"User", :through=>:planned_places
I'm trying to write a find on Place that returns all places in the database that aren't related to a User through either visited or planned. Right now I'm accomplishing this by simply querying all Places and then subtracting visited and planned from the results but I want to add in pagination and I'm worried this could complicate that. Here's my current code.
all_places = Place.find(:all)
all_places = all_places - user.visited - user.planned
Anyone know how i can accomplish this in just a call to Place.find. Also this is a Rails 3 app so if any of the active record improvements make this easier they are an option.
How about something like:
unvisited_places = Place.find(:all, :conditions => "id NOT IN(#{visited_places.map(&:place_id)})")
That's the general idea -- it can be made more efficient and convenient depending on your final needs.
You don't show it but if I am right in assuming that the VisitedPlace and PlannedPlace models have a belongs_to :user relationships then those tables have a user_id secondary key, right?
So in that case I would think it would be most efficient to do this in the database in which case you are looking for a select across a table join of places, visited_places and planned_places where users.id is not in either of visited_places or planned_places
in sql:
select * from places where id not in
(
(select place_id from visited_places where user_id = ?)
union
(select place_id from planned_places where user_id=?)
)
If that query works, you can use as follows:
Places.find_by_sql(...the complete sql query ...)
I would not know how to write such a query, with an exclusion, in Rails 3 otherwise.
I ran into a similar desire recently... I wanted to get all Model1s that weren't associated with a Model2. Using Rails 4.1, here's what I did:
Model1.where.not(id: Model2.select(:user_id).uniq)
This creates a nested SELECT, like #nathanvda suggested, effectively letting the database do all the work. Example SQL produced is:
SELECT "model1s".* FROM "model1s" WHERE ("model1s"."id" NOT IN (SELECT DISTINCT "model2s"."model1_id" FROM "model2s"))

Resources