Help in ActiveRecord find with include on conditions - ruby-on-rails

I have a Coach Model which:
has_many :qualifications
I want to find all coaches whose some attribute_id is nil and they have some qualifications. Something which is like.
def requirement
legal_coaches = []
coaches = find_all_by_attribute_id(nil)
coaches.each do |coach|
legal_coaches << coach if coach.qualifications.any?
end
legal_coaches
end
Is there a way to get all such records in one line ?

find_all_by_attribute_id(nil).select(&:qualification)

I think you can't do that via purely ruby syntax. I can only think of the following (ugly) way
Coach.find(:all, :conditions => "attribute_id IS NULL AND EXISTS(SELECT * FROM qualifications WHERE coach_id = coaches.id)")

Related

Primary key is null when left join is used

When I use this request
#questions = Question.joins("left join answers as a on a.user_id = #{current_user.id} and a.question_id = questions.id").select('questions.*, a.*')
Question.id is null. Does anyone know why? Maybe it needs an alias or something like that.
My schema:
class Answer:
belong_to: User
belongs_to: Question
end
class User:
has_many: answers
end
class Question:
has_one: answer
end
The problem is that the answers table probably also has a column named id, which is shadowing the question's id. Although in the result set one column is called questions.id and the other a.id, active record just looks at the last part ('id')
There's no good way around this that I am aware of other than aliasing problematic columns from the answers table, which unfortunately means explicitly naming all of them (although you can of course automate this to an extent)
This is probably because of the ID columns of two tables conflicting with one another.
Update:
Simply changing the order of select columns will help. So instead of .select("questions.*", "answers.*"), try .select("answers.*", "questions.*").
Old answer:
Try the following:
# Assuming the following:
# Question.table_name == "questions"
# Answer.table_name == "answers"
question_columns = Question.column_names # or %w{questions.*}
answer_columns = Answer.column_names.map { |c| "answer_#{c}" }
columns = question_columns + answer_columns
#questions =
Question.
joins("LEFT OUTER JOIN answers ON (
answers.question_id = questions.id
AND
answers.user_id = #{current_user.id}
)").
select(*columns)
#questions.first.id #=> should return some integer
#questions.first.answer_id #=> may or may not return something
However, unless you absolutely need a LEFT JOIN along with those select-columns, it would be much cleaner to accomplish your task using the following:
class Answer
belongs_to :question
end
class User
has_many :answers
has_many :questions, through: :answers
end
current_user.questions

Is there a simpler way to return a belongs_to relation of an ActiveRecord result set?

I have two models:
Answers:
belongs_to: user
User:
has_many: answers
Is there a way in Ruby or Rails to do the following in one go instead of creating an array and pushing the needed object into it?
def top_experts
answers = Answer.where(some constraints)
users = []
answers.each do |answer|
users << answer.user
end
users
end
You can user joins
def top_experts
Answer.where(some constraints).includes(:user).collect{|x| x.user}
# Will return an Array of users
end
EDIT:
Use includes for eager loading. It will reduce the no of queries executed to get user.
Hi you can use a select clause to select what should be returned along with the where clause
Vote.select(user_id).where(some contraints)
Use sub-query to get users:
User.where( :id => Answer.where(some constraints).select(:user_id) )
Refer: subqueries in activerecord

Self-referential find in controller count relations

I'm having real trouble pulling out a set of records that are self-referentially related to a user in order to show these on a user's 'show' page.
Here's the idea:
Users (current_user) rate the compatibility between two other users (user_a and user_b). They can rate compatibility either positively or negatively: rating two users "compatible" creates a positive_connection between user_a and user_b, and rating them "incompatible" creates a negative_connection. So there are models for positive_connection, negative_connection and user.
Now I need to display only users that are overall_positively_connected_to(#user) (i.e. where positive_connections_to(#user).count > negative_connections_to(#user).count).
This is where I've got to, but I can't get any further:
User model:
def overall_positive_connected_to(user)
positive_connections_to(user).count > negative_connections_to(user).count
end
def positive_connections_to(user)
positive_connections.where("user_b_id = ?", user)
end
def negative_connections_to(user)
negative_connections.where("user_b_id = ?", user)
end
Controller
#user.user_bs.each do |user_b|
if user_b.overall_pos_connected_to(#user)
#compatibles = user_b
end
end
The code in the controller is clearly wrong, but how should I go about doing this? I'm completely new to rails (and sql), so may have done something naive.
Any help would be great.
So am I right in saying you have 3 models
User (id, name)
PositiveConnection (user_a_id, user_b_id)
NegativeConnection (user_a_id, user_b_id)
Or something of that sort.
I think you just want 2 models
and for convenience I'm going to rename the relations as "from_user" and "to_user"
User (id, name)
Connection (value:integer, from_user_id, to_user_id)
Where value is -1 for a negative
and +1 for a positive.
Now we can have do something like
(note: you need to sort out the exact syntax, like :foreign_key, and :source, and stuff)
class User
has_many :connections, :foreign_key => "from_user_id"
has_many :connected_users, :through => :connections, :source => :to_user
def positive_connections
connections.where(:value => 1)
end
def negative_connections
...
end
end
But we also now have a framework to create a complex sql query
(again you need to fill in the blanks... but something like)
class User
def positive_connected_users
connected_users.joins(:connections).group("from_user_id").having("SUM(connections.value) > 0")
end
end
this isn't quite going to work
but is kind of pseudo code for a real solution
(it might be better to think in pure sql terms)
SELECT users.* FROM users
INNER JOIN connections ON to_user_id = users.id
WHERE from_user_id = #{user.id}
HAVING SUM(connections.value) > 0

Difference Between find and Where with Relationships

I wouldn't think there is a difference when it comes to active record and finding data.
Here are my models
class User < ActiveRecord::Base
has_many :shows
end
class Show < ActiveRecord::Base
belongs_to :user
end
When I use the rails console I can do the following and it works.
u = User.find(1)
u.shows
It gives me all the shows for that user.
However when I do
u = User.where("username = ?", "percent20")
u.shows # this is doesn't work gives me a now instance error
I get the same user and relevant information, but not the relationship. The only problem I can see is maybe I am doing something wrong because there is some difference between where and find.
Any help is appreciated.
The problem is not the relationship.
u = User.find(1)
returns one User
#return a Set of users. In your case its only one user.
u = User.where("username = ?", "percent20")
The result type is ActiveRecord::Relation --> [User, User, User]
use e.g. first to get the first User
#returns the first user
u = User.where("username = ?", "percent20").first
u.class.name
=> "User"
User.find(1) is retrieving a specific record with its ID, whereas User.where("username = ?", "percent20") is retrieving the set of records that match the condition.
Try:
u = User.where("username = ?", "percent20").first
u.shows
The where is method that returns an array of objects. So, in your case try
u.each { |user| user.shows }

'Splitting' ActiveRecord collection

Let's say I have two models Post and Category:
class Post < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :posts
end
Is there a method that will allow me to do something like
posts = Post.find(:all)
p = Array.new
p[1] = posts.with_category_id(1)
p[2] = posts.with_category_id(2)
p[3] = posts.with_category_id(3)
...
or
p = posts.split_by_category_ids(1,2,3)
=> [posts_with_category_id_1,
posts_with_category_id_2,
posts_with_category_id_3]
In other words, 'split' the collection of all posts into arrays by selected category ids
Try the group_by function on Array class:
posts.group_by(&:category_id)
Refer to the API documentation for more details.
Caveat:
Grouping should not performed in the Ruby code when the potential dataset can be big. I use the group_by function when the max possible dataset size is < 1000. In your case, you might have 1000s of Posts. Processing such an array will put strain on your resources. Rely on the database to perform the grouping/sorting/aggregation etc.
Here is one way to do it(similar solution is suggested by nas)
# returns the categories with at least one post
# the posts associated with the category are pre-fetched
Category.all(:include => :posts,
:conditions => "posts.id IS NOT NULL").each do |cat|
cat.posts
end
Something like this might work (instance method of Post, untested):
def split_by_categories(*ids)
self.inject([]) do |arr, p|
arr[p.category_id] ||= []
arr[p.category_id] << p if ids.include?(p.category_id)
arr
end.compact
end
Instead of getting all posts and then doing some operation on them to categorize them which is a bit performance intensive exercise I would rather prefer to use eager loading like so
categories = Category.all(:include => :posts)
This will generate one sql query to fetch all your posts and category objects. Then you can easily iterate over them:
p = Array.new
categories.each do |category|
p[1] = category.posts
# do anything with p[1] array of posts for the category
end
Sure but given your model relationships you I think you need to look at it the other way around.
p = []
1.upto(some_limit) do |n|
posts = Category.posts.find_by_id(n)
p.push posts if posts
end

Resources