Multiple Has Many Through One Relationships Pointing to Same Models - Getting data out in ruby - ruby

I have two separate has many through one relationships pointing to the same objects.
Users have many photos through photo_relationships
Users have many photos through votes
In my controller I'm trying to show all of the photos for the user through this code:
#user = User.find(params[:id])
#photos = #user.photos
However, the Inner Join is being controlled by whatever has_many relationships is mentioned last in the User model, in this case votes. Is there a way to specify what inner join is used such as:
#photos = #user.photos( joins: :photo_relationships)

Do something like this:
class User
...
has_many :voted_photos, class_name: 'Photo', through: :votes
has_many :relationship_photos, class_name: 'Photo', through: :photo_relationships
end

Related

searching for records based off of results from other results

I have four kinds of records. Teams. Coaches. Players. and Families.
Teams have a one to many association with Coaches.
Coaches have a one to one association with Players.
Players have a many to one association with Families.
What I need to find are specific families that are associated with certain Teams. However due to their separation i can't just go Team.last.family.
Is there a way to leap over the Players and Coaches and do a query from the clinics straight to the family?
Most likely you will need to make use of the joins method.
If you provide more details of your model code and schema we could provide more details, but it will be something along the lines of.
Family.joins(some_join_object).where(some_sql_search_string)
Once you have a join table that includes all the columns you want, specify the conditions you would like to filter on.
Family.joins(:players, :coaches, :teams).where("families.name='Wesley' and teams.name='Crushers'")
Here is an example from the provided link.
class Category < ApplicationRecord
has_many :articles
end
class Article < ApplicationRecord
belongs_to :category
has_many :comments
has_many :tags
end
class Comment < ApplicationRecord
belongs_to :article
has_one :guest
end
class Guest < ApplicationRecord
belongs_to :comment
end
class Tag < ApplicationRecord
belongs_to :article
end
12.1.3.2 Joining Nested Associations (Multiple Level)
Category.joins(articles: [{ comments: :guest }, :tags])
This produces:
SELECT categories.* FROM categories INNER JOIN articles ON
articles.category_id = categories.id INNER JOIN comments ON
comments.article_id = articles.id INNER JOIN guests ON
guests.comment_id = comments.id INNER JOIN tags ON tags.article_id =
articles.id
Or, in English: "return all categories that have articles, where those
articles have a comment made by a guest, and where those articles also
have a tag."

Activerecord/Datamapper - Have one child belong to many parents

How would you set up an activerecord/datamapper association for the following scenario:
A user creates a "bookshelf" which has many books(a book object just has an isbn that is used to query an api, and has_many review objects associated with it). Let's say Jack creates a "bookshelf" with a book object. Then, lets say that Jill creates a "bookshelf" with the same book object(it has the same id and the same reviews). The book object has the following code as of now:
class Book < ActiveRecord::Base
has_many :reviews
end
Then, when you view the page for a book (you click the link to it from the "bookshelf" created by Jack) you should see the same book object when you clicked the link to it from Jill's "bookshelf" (e.g. both "bookshelves" have a link to /books/23 because they have the same book object).
I have not been able to figure this out with the has_many association because that requires me to make a new book each time a user adds a book to their "bookshelf." I have trouble understanding the has_and_belongs_to_many relationship, is that what should be used here? I was not able to find any similar questions on SO, so any help is greatly appreciated.
I am using Rails 4 with Ruby 2.1.
Here is a drawing of what I would like to accomplish:
Drawing
Yes, you would have to define many-to-many relationship between a Bookshelf and a Book. There are two ways to achieve this in Rails:
Option 1) Use has_and_belongs_to_many
See guide
According to official documentation has_and_belongs_to_many association:
Specifies a many-to-many relationship with another class. This associates two classes via an intermediate join table. Unless the join table is explicitly specified as an option, it is guessed using the lexical order of the class names. So a join between Developer and Project will give the default join table name of “developers_projects” because “D” precedes “P” alphabetically.
So, your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_and_belongs_to_many :books
end
class Book < ActiveRecord::Base
has_and_belongs_to_many :bookshelves
has_many :reviews
end
Add a join table generation to your migrations:
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
end
end
end
This will create a books_bookshelves table in your database. The table will have no primary key. There would be two foreign keys to your models Book and Bookshelf.
So, if you call self.books in the context of an user's bookshelf, you will get a list of books in the bookshelf. Vice versa, calling self.bookshelves in the context of a book will return a set of bookshelves the book belongs to.
The problem with this approach is that every time you add a new book to the bookshelf a new record is created in the database. If you are okay with that, there is no easier option than using has_and_belongs_to_many association. Otherwise, I recommend you to go with the Option #2.
Option 2) Use has_many :through
Another option is to use has_many, :through association (see guide). You would have to define one more model to do that, but it might come handy in some use cases (see below for an example).
Your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_many :books, through: :books_bookshelves
has_many :books_bookshelves
end
class Book < ActiveRecord::Base
has_many :bookshelves, through: :books_bookshelves
has_many :books_bookshelves
has_many :reviews
end
class BooksBookshelf < ActiveRecord::Base
belongs_to :book
belongs_to :bookshelf
end
Probably the best thing about using has_many :through association is that it allows you to add custom columns to the join table (e.g. add column count to keep track how many books of the same type are there in the bookshelf).
The migration would look pretty much the same as the one we used in Option 1, except for the fact we are adding an unique constraint on the foreign keys (please note that adding the constraint is optional):
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
# add your custom columns here
end
add_index :books_bookshelves, [:book_id, :bookshelf_id], unique: true # to make sure you won't create duplicate records
end
end
By going with this approach, adding a new would be a bit more complicated as you would have to make sure you are not inserting duplicate records in the join table. (However, you may remove the unique constraint from the migration, to achieve exactly the same kind of behavior as you would get with has_and_belongs_to_many.)

Rails association, I want something but the other model shouldn't care

So I think I need help with what I am doing with my asscoiations. I have two models (well many but for the purposes here). One is a Provider. Each provider can have a designation. Though the Designations is just a list it can appear in many different providers so it is not one to one. My models are as follows:
Provider.rb
class Provider < ActiveRecord::Base
has_and_belongs_to_many :groups
has_one :designation
has_one :specialty
end
Designation.rb
class Designation < ActiveRecord::Base
end
The error I get is this:
SQLite3::SQLException: no such column: designations.provider_id: SELECT "designations".* FROM "designations" WHERE "designations"."provider_id" = ? LIMIT 1
Which tells me my associations are off cause the designations should not have the provider_id, if anything the provider should have the designation_id.
Do I ditch the has_one and just put has_many in the Designation?
If I do that, I am new to rails, do I have to create a migration so the database is correctly updated?
Try using
models/provider.rb
belongs_to :designation
Then in
models/designation.rb
has_many :providers
It may feel a little strange but the belongs_to just lets you know which model the id column needs to go in. In this case the provider model, so you'll need:
rails g migration add_designation_id_to_provider designation_id:integer

Rails join on 2 tables

I have 3 tables which are
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
Now I want to retrieve all the comments of a particular user.
I have tried it using find_by_sql like
Comment.find_by_sql "SELECT c.* FROM comments c, posts p, users u WHERE c.post_id = p.id and p.user_id = u.id and u.id=6"
This works fine. But I want few details from "posts" table along with comments.
Does anyone have idea how to do that?
Corrected answer..
Now that I've properly understood the scenario, a portion of my previous answer still makes sense with one addition:
Alter the User model like so:
class User < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
end
This type of relationship will perform a join between comments a posts, where the user_id of posts is the current user. Quite literally, like you said in the comments, "the connection between User and Comment [is] through Post".
Similar to what I said before, the center of the universe in this scenario is the user object. With this addition, to grab all the user's comments would simply be:
user.comments
If you look at the log, the SQL output is:
SELECT `comments`.* FROM `comments` INNER JOIN `posts` ON `comments`.`post_id` = `posts`.`id` WHERE `posts`.`user_id` = 1
Which is similar to your original query.
Now that you retrieved the comments for this user, you can simply access the Post model data through the belongs_to :post relationship, as normal.
In the end, unless you have some special reason for trying to do everything in pure SQL, this approach is more readable, maintainable, and the "Rails way". Plus it saves yourself a lot of hassle and typing.
More information on has_many ... through.

Ruby on Rails: Associations when a user likes a song

I'm trying to figure out the best way to setup my database and models for the following scenario.
A user can like an infinite number of songs.
A song can be liked once by an infinite number of users.
I have these tables:
songs, users, likes etc... Following RoR conventions.
The table named likes has these foreign keys: user_id, song_id. And also a field named 'time' to save a timestamp when the song was liked.
I'm not sure of how to do this, I would like to be able to use code like this in my controllers:
User.find(1).likes.all
This should not return from the likes table, but join the songs table and output all the songs that the user likes.
What are the best practises to achieve this in Ruby on Rails following their conventions?
Unless you need to act specifically on the likes table data, the model itself is probably not necessary. The relationship is easy:
class User < ActiveRecord::Base
has_and_belongs_to_many :songs
end
class Song < ActiveRecord::Base
has_and_belongs_to_many :users
end
This will join through the currently non-existent song_users table. But since you want it to join through likes you can change each one to this:
has_and_belongs_to_many :songs, :join_table => 'likes'
If you want to be able to call User.find(1).likes and get songs, then change the user's version to this:
class User < ActiveRecord::Base
has_and_belongs_to_many :likes, :join_table => 'likes', :class_name => 'Song'
end
And you could change the songs version to something like this:
class Song < ActiveRecord::Base
has_and_belongs_to_many :liked_by, :join_table => 'likes', :class_name => 'User'
end
This will give you Song.find(1).liked_by.all and give you the users (You could keep it to users if you wanted using the first version)
More details on habtm relationships can be found here: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
Edit to add
If you want to act on the join table for whatever reason (you find yourself needing methods specifically on the join), you can include the model by doing it this way:
class User < ActiveRecord::Base
has_many :songs, :through => :likes
has_many :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :song
end
class Song < ActiveRecord::Base
has_many :users, :through => :likes
has_many :likes
end
This will let you do User.find(1).songs.all, but User.find(1).likes.all will give you the join data

Resources