Rails 3+, Querying polymorphic association from both ends - mysql

I'm trying to querying on polymorphic associations in Rail, and come across with this problem. I have a few models, User, MorningVisit, AfternoonVisit, NightVisit, and Result
class User < ActiveRecord::Base
attr_accessible :name
has_one :morning_visit
has_one :afternoon_visit
has_one :night_visit
end
class MorningVisit < ActiveRecord::Base
belongs_to :user
has_many :results, as: :visitable
end
class AfternoonVisit < ActiveRecord::Base
belongs_to :user
has_many :results, as: :visitable
end
class NightVisit < ActiveRecord::Base
belongs_to :user
has_many :results, as: :visitable
end
class Result < ActiveRecord::Base
attr_accessible :type
belongs_to :visitable, polymorphic: true
end
Everything stores perfectly, but now I need to do some search and querying on User using filterrific.
If I do
includes(:visitable => :user).order("user.name")
I get
Can not eagerly load the polymorphic association :visitable
So I tried to manually joined MorningVisit with
.joins( "JOIN morning_visits ON lab_results.visitable_id = morning_visit.id AND lab_results.visitable_type = 'MorningVisit'")
.joins( "INNER JOIN `users` ON `morning_visits`.`user_id` = `users`.`id` ")
And order("user.name") works.
But if I added
.joins( "JOIN afternoon_visits ON lab_results.visitable_id = afternoon_visits.id AND lab_results.visitable_type = 'AfternoonVisit'")
It returns nothing. And if I tried outer join, it keep telling me my SQL syntax was wrong.
Does anyone have a solution for this?

Related

Rails has_and_belongs_to with primary key delete all records on destroy

I've 3 models in Rails 5:
Product
class Product < ApplicationRecord
belongs_to :category
has_and_belongs_to_many :stores
end
Store
class Store < ApplicationRecord
has_many :admin_users
has_and_belongs_to_many :roles
has_and_belongs_to_many :products
end
ProductsStores
class ProductsStores < ApplicationRecord
self.primary_key = :store_id
end
When I try to delete one record of the join table ProductsStores with the command ProductsStores.first.destroy all records in the join table are destroyed. Why?
Could anyone help me?
Thank you in advance!
At first, you have to understand differences between HABTM and has_many through:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
If you created linkedin model and wanna using this model, you have to use has_many through: association, and add to this model belongs_to :product and belongs_to :store. Then with flag dependent: :destroy you will can destroy or not destroy linked models as default.

Call two models dependent on each other in one controller for a view

I am new to Rails so I am going to try and explain this the best I can.
I have three models: artist, fest, and festival_artist
artist contains only an ID and an artist_name
fest contains only an ID and a festival_name
festival_artist contains an ID, a artist_id, and a festival_id
I created Fest using a scaffold so that is where my controller and show.html.erb is.
Below are my models:
class Artist < ApplicationRecord
belongs_to :festival_artist
end
class Fest < ApplicationRecord
belongs_to :festival_artist
end
class FestivalArtist < ApplicationRecord
has_many :artists
has_many :fests
end
In my fests_controller.rb I have:
def show
#festival_artists = FestivalArtist.where(festival_id: #fest.id)
end
I tried to add:
def show
#festival_artists = FestivalArtist.where(festival_id: #fest.id)
#artists = Artist.where(id: #festival_artists.artist_id)
end
However, that throws an undefined method artist_id for # error.
The goal is to display the Artist's name in the Fest's show.html.erb page for the festival that that artist belongs to.
In SQL it would be:
SELECT A.artist_name
FROM festival_artists AS FA
INNER JOIN artists AS A
ON FA.artist_id = A.id
Any suggestions? Even telling me what to Google would help out because I'm not sure my terminology is correct.
Let me know if you need anymore information.
Guess your models structure is not 100% correct. Try to check out http://guides.rubyonrails.org/association_basics.html for details.
There are to ways to handle your associations in Rails:
HABTM (has and belongs to many), as noticed in #grizzthedj answer.
has_many :through association
In this case your code will look like
class Artist < ApplicationRecord
has_many :festival_artists
has_many :fests, through: :festival_artists
end
class Fest < ApplicationRecord
has_many :festival_artists
has_many :artists, through: :festival_artists
end
class FestivalArtist < ApplicationRecord
belongs_to :artists
belongs_to :fests
end
So you can access artists in the controller
def show
#festival_artists = #fest.artists
end
I'm not sure that you need the FestivalArtist model. If you use "has_and_belongs_to_many" in Artist and Fest models, this will implement the many-to-many relation that you are looking for.
# fest.rb
class Fest < ActiveRecord::Base
has_and_belongs_to_many :artists
end
# artist.rb
class Artist < ActiveRecord::Base
has_and_belongs_to_many :fests
end

Why isn't this second-level eager loading working?

I have three models, Subrating, Thing, and Category. Subrating belongs_to Thing, and Thing has_many Categories.
I have a list of Subratings, and I'm trying to eager-load the Categories that are associated with the Things that are associated with each Subrating.
This is what I tried:
controller
#subratings = Subrating.all( :include => { :thing => :categories })
view
<% #subratings.sort_by(&:rating).reverse.each do |subrating| %>
<%= subrating.thing.categories.sort_by(&:thing_count).second.name %>
<% end %>
But it doesn't solve my n+1 problem. I'm not even sure whether the database is lazy-loading Things or Categories or both, but this is the line that keeps reappearing hundreds of times in my server:
CACHE (0.0ms) SELECT COUNT(*) FROM "things" INNER JOIN "category_things" ON "things"."id" = "category_things"."thing_id" WHERE "category_things"."category_id" = $1 [["category_id", 1]]
What am I doing wrong?
associations
class Subrating < ActiveRecord::Base
belongs_to :thing
end
class Thing < ActiveRecord::Base
has_many :category_things
has_many :categories, :through => :category_things
has_many :subratings
end
class Category < ActiveRecord::Base
has_many :category_things
has_many :things, :through => :category_things
end
class CategoryThing < ActiveRecord::Base
belongs_to :category
belongs_to :thing
end
Maybe you should try to call includes on the Model class like:
#subratings = Subrating.includes(thing: [:categories])
as documented in http://apidock.com/rails/ActiveRecord/QueryMethods/includes
although it also should work the way you are trying it, since .all is an alias for .find(:all) but then i would propose to try:
#subratings = Subrating.all( :includes => { :thing => [:categories] })
(note that i changed include to includes and made thing point to an array)
#subratings = Subrating.includes(:thing => :categories)
This will also work for you and give you all records of substrings table.

change association from one to many to many to many

I have two models lets suppose
class A < ActiveRecord::Base
has_many :bs
end
class B < ActiveRecord::Base
belogns_to :a
end
now because of some system changes I need to convert this association to many to many some thing like this
class A < ActiveRecord::Base
has_and_belongs_to_many :bs
end
class B < ActiveRecord::Base
has_and_belongs_to_many :as
end
OR
class A < ActiveRecord::Base
has_many :cs
has_many :bs, through: :cs
end
class B < ActiveRecord::Base
has_many :cs
has_many :as, through: :cs
end
class C < ActiveRecord::Base
belongs_to :a
belongs_to :b
end
what is best way to do this and most importantly I DO NOT WANT TO LOSE MY EXISTING DATA. Existing records should automatically adopt these changes. Thanks in advance.
many to many means you have connected table(model) between two other, so you could just create this one and write relation to it, after that you could remove garbage ids from B.
A, B are not good names;)
imagine you have User and Comments, and you decide that comments can have also many users, so it can look like:
class User
has_many :comments # will be changed
has_many :user_comments
end
class UserComments
belong_to :user
belong_to :comment
end
class Comment
belong_to :user # id will be removed from table and relation will be changed
has_many :user_comments
end
# as direction for import could be something like:
User.all.each do |user|
user.comments.each do |comment|
UserComments.create user: user, comment: comment
end
end

Find all without association through association

class Campaign < ActiveRecord::Base
has_many :posts
has_many :users
end
class Post < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :campaign
end
class User < ActiveRecord::Base
has_and_belongs_to_many :posts, :join_table => "users_posts"
belongs_to :campaign
end
If user saw this post I add him to post.users. I need find first unwatched post of user campaign. Smth like this:
current_user.campaign.posts.where('posts.users not include current_user')
MrYoshiji, Thanks a lot! You remember me about include. Before I tried join and it did not work with nil because INNER JOIN, I solved my problem with instance method:
def unwatched_post
campaign.posts.includes(:users).where('users.id != ? OR users.id IS NULL', self.id).first
end