Rails: Find with join model - How to? - sql

I have the following models associations which I want to use in order to do some searches of players:
class Player < ActiveRecord::Base
belongs_to :user
has_many :abilities
has_many :sports, :through => :abilities
...
end
class User < ActiveRecord::Base
has_one :player
...
end
class Ability < ActiveRecord::Base
belongs_to :player
belongs_to :sport
has_one :level
...
end
class Sport < ActiveRecord::Base
has_and_belongs_to_many :category_sports
has_many :abilities
has_many :players, :through => :abilities
...
end
class CategorySport < ActiveRecord::Base
has_and_belongs_to_many :sports
end
I have a form where a user can fill two inputs: city and sport
The city field is in the User model as (#user.city) and the sport field can be either in CategorySport as (#category_sport.name) or in the Sport model as (#sport.name).
Right now I have the following to perform the search in the Player model:
def search
self.find(:all,:include => 'user',:conditions => ['users.city LIKE ?', "%#{city}%"])
end
I would like to know how would I add the join model (ability) and related (sport, categorysport) in this query in order to find by sport too. So, not just find for user city but also by sport.

Try this:
def search
self.includes(:user, :abilities => {:sport => :category_sports}).
where(['users.city LIKE ? OR category_sports.name=? OR sports.name=?',
"%#{city}%", sport, sport])
end

Related

Rails ActiveRecord query on existing collection

Suppose I have a result from a query:
allCourses = Course.all
Then I also have another set:
myCourses = current_user.courses.all
How can I get a set of items that are in allCourses and NOT in myCourses?
Here are the models:
class Student < ActiveRecord::Base
has_many :student_enrollments, dependent: :destroy
has_many :courses, through: :student_enrollments
end
class Course < ActiveRecord::Base
has_many :student_enrollments, dependent: :destroy
has_many :students, through: :student_enrollments
end
class StudentEnrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
I can always write raw SQL script to achieve the result, but I prefer to find a Rails way to do it.
Thanks
Assume your fk is user_id.
ohterCourses = Course.where('user_id != ?', current_user.id)

Excluding results in has_many :through relation in Rails 3

I am trying to select groups which the #current_user is NOT a member of
The relevant parts of my models are as follows:
class Group < ActiveRecord::Base
belongs_to :user
has_many :group_memberships
has_many :members, :class_name => "User", :through=>:group_memberships
...
class User < ActiveRecord::Base
has_many :group_memberships, :foreign_key => 'member_id'
has_many :groups, :through => :group_memberships
...
class GroupMembership < ActiveRecord::Base
belongs_to :member, :class_name=>"User"
belongs_to :group
end
Thanks!
You could try and do something like:
#groups = Group.where("id NOT IN (?)", current_user.groups)
More information can be found in the Active Record Query.
have a method like this:
def group_ids_not_a_member_of
# get all group_ids and subtract out the ids that he is a member of
Group.pluck('id') - current_user.groups.map(&:id)
end
then
user.group_ids_not_a_member_of

Rails ActiveRecord Query (Cross model)

I have an app that lets users input dates & interests that relate to those dates .I need to send them deals (a few days before the date - Via Email) that are based off of their interests and location. I have all the models setup and recording the data properly, just wondering how to query the models for the dates and then send the appropriate deal based off of the city and interests.
Notes:
*Each city and interest category has only 1 deal
*I have several different models for types of dates (Holidays, Occasions, Friends Birthdays ect).. all are pretty much identical in structure.
*All interests for each type of date are stored in person_interests.
Models:
Class User
belongs_to :province
belongs_to :city
has_many :friends_bdays
has_many :occasions
has_many :person_interests, :as => :person
has_many :interests, :through => :person_interests
has_many :user_holidays
has_many :holidays, :through => :user_holidays
has_many :anniversaries
end
class Deal < ActiveRecord::Base
belongs_to :interest
belongs_to :city
belongs_to :store
end
class Store < ActiveRecord::Base
has_many :deals
belongs_to :city
belongs_to :province
end
class PersonInterest < ActiveRecord::Base
belongs_to :interest
belongs_to :person, :polymorphic => true
end
class Interest < ActiveRecord::Base
has_many :person_interests
has_many :deals
end
class Occasion < ActiveRecord::Base
belongs_to :user
belongs_to :admin_user
has_many :person_interests, :as => :person
has_many :interests, :through => :person_interests
end
class Anniversary < ActiveRecord::Base
belongs_to :user
has_many :person_interests, :as => :person
has_many :interests, :through => :person_interests
end
class Friend_bday < ActiveRecord::Base
belongs_to :user
has_many :person_interests, :as => :person
has_many :interests, :through => :person_interests
end
You can achieve this using a variation of the solution below:
Install the squeel gem
class User
def deals(reload=false)
#deals = nil if
#deals ||= Deal.where{
( (:city => city_id) | ( :interest_id => interest_ids) ) &
:deal_date => (Time.now..3.days.from_now)
}
end
end
Now, user.deals returns the deals that will be active in next 3 days matching the user's city OR interests.
Edit 1:
Based on your comment it looks like you don't need the squeel gem. You can achieve what you want using regular AR syntax.
class User
def deals(reload=false)
#deals = nil if reload
#deals ||= Deal.where(
:city => city_id,
:interest_id => interest_ids,
:deal_date => (Time.now..3.days.from_now)
)
end
end

rails 3.1 - has_one, through

Is it possible to make a has_one relationship work like this?
I would like to be able able to load records like this:
#person = Person.find(1) => {Person id: 1, favorite_house_id: 10}
#person.favorite_house => {House id: 10....)
class Person < ActiveRecord::Base
has_many :houses, through: :person_houses
has_one :favorite_house, through: :person_houses
end
class PersonHouse < ActiveRecord::Base
belongs_to :house
belongs_to :person
end
class House < ActiveRecord::Base
has_many :people, through: :person_houses
end
Replace the has_one relation of Person by:
belongs_to :favorite_house, :class_name => "House"
Do not forget to create a column favorite_house_id in the table of Person.

Multiple INNER JOINs in a Rails has_many relationship?

I have the following models:
class Image < ActiveRecord::Base
belongs_to :gallery
has_many :bookmarks
has_many :gallery_tags, :foreign_key => :gallery_id
end
class Bookmark < ActiveRecord::Base
belongs_to :user
belongs_to :image
has_many :gallery_tags, :through => :image, :source => :gallery_tags
end
class GalleryTag < ActiveRecord::Base
belongs_to :gallery
belongs_to :tag
end
class Gallery < ActiveRecord::Base
belongs_to :provider
has_many :images
belongs_to :user
has_many :gallery_tags
has_many :tags, :through => :gallery_tags
end
class Tag < ActiveRecord::Base
end
class User < ActiveRecord::Base
has_many :bookmarks
has_many :galleries
end
I'd like to be able to do
User.find(1).bookmarked_tags
and retrieve all tags associated with all of the user's bookmarked images via the galleries associated with the images. Tags are associated with galleries.
The query would end up looking like this:
SELECT
tags.*
FROM
tags
INNER JOIN gallery_tags ON gallery_tags.tag_id = tags.id
INNER JOIN images ON gallery_tags.gallery_id = images.gallery_id
INNER JOIN bookmarks ON images.id = bookmarks.image_id
WHERE
bookmarks.user_id = 1
GROUP BY
tags.id;
I've tried adding
has_many :tags, :through => :gallery_tags, :foreign_key => :gallery_id
to Image, which causes Image.find(34).tags to result in
SELECT `tags`.* FROM `tags` INNER JOIN `gallery_tags` ON `tags`.id = `gallery_tags`.tag_id WHERE ((`gallery_tags`.gallery_id = 34))
(it's not using the image's gallery_id in the query), and then I've tried adding
has_many :bookmarked_tags, :through => :bookmarked_images, :source => :tags
to User, which causes User.find(1).bookmarked_tags to result in
ActiveRecord::HasManyThroughSourceAssociationMacroError: Invalid
source reflection macro :has_many :through for has_many
:bookmarked_tags, :through => :bookmarked_images. Use :source to
specify the source reflection.
So: how can I get Rails to run the query I posted with User.find(1).bookmarked_tags?
there are two solutions
First Solution:
Create a view inside your database that acts like a join table:
CREATE VIEW user_bookmarks_tags (
SELECT
bookmarks.user_id AS user_id
gallery_tags.tag_id AS tag_id
FROM
gallery_tags
INNER JOIN images ON gallery_tags.gallery_id = images.gallery_id
INNER JOIN bookmarks ON images.id = bookmarks.image_id
GROUP BY
user_id, tag_id
)
(you can do this inside a migration)
This view acts like a "table" with the columns user_id | tag_id
Now we can adapt our models using has_and_belongs_to_many:
user.rb
class User < ActiveRecord::Base
has_many :bookmarks
has_many :galleries
# we use our magic (view) join table here!
has_and_belongs_to_many :tags, :join_table => :user_bookmarks_tags
end
tag.rb
class Tag < ActiveRecord::Base
# we use our magic (view) join table here
has_and_belongs_to_many :users, :join_table => :user_bookmarks_tags
end
Now you can do: User.find(1).tags or Tag.find(1).users :)
Second Solution
Do the join manually without a view:
define the missing relations (needed for the automated joins foreign_key lookup):
tag.rb
class Tag < ActiveRecord::Base
has_many :gallery_tags
end
gallery_tag.rb
class GalleryTag < ActiveRecord::Base
belongs_to :gallery
belongs_to :tag
# new:
has_many :images, :through => :gallery
end
Now we can add a bookmarked_tags method to our user.rb
class User < ActiveRecordBase
has_many :bookmarks
has_many :galleries
def bookmarked_tags
Tag.joins(:gallery_tags => {:images => :bookmarks}).where('bookmarks.user_id = ?', self.id).group('tags.id')
end
end