Rails multiple model association - ruby-on-rails

I have a rails application with the following Models:
User (id)
Version (id, post_id, creator_id)
Post (id)
So far setup is as follows:
User.rb:
has_many :versions
Version.rb:
belongs_to :creator, :class_name => "User"
belongs_to :post
Post.rb:
has_many :versions
Now i would like to link a user to the posts he has through the versions table, and to make it worst this connection must be called questions. I was thinking something like this:
Added to User.rb:
has_many :questions, :class_name => "Post", :source => :post, :through => :versions
Problem is this doesn't work and probably shouldn't since it doesn't know what the user key's name is in the versions table.
Error message:
SQLite3::SQLException: no such column: versions.user_id: SELECT COUNT(*) FROM "posts" INNER JOIN "versions" ON "posts"."id" = "versions"."post_id" WHERE "versions"."user_id" = 1
I'm at a loss, help!
Note: The only relationship that doesn't work is the final one users <==> posts a.k.a users.questions

This setup should work for you:
user.rb
has_many :versions, :foreign_key => 'creator_id'
has_many :questions, :through => :versions
version.rb
belongs_to :creator, :class_name => "User"
belongs_to :question, :class_name => "Post", :foreign_key => 'post_id'
post.rb
has_many :versions
Now you can access the questions like so: User.first.questions

Related

Why am I getting error FROM-clause entry for table error?

I am writing an app in Rails 4.2.1 using jRuby 1.7.16.1
I've got User and Project models. Project has one leader and many users through join model:
class Project < ActiveRecord::Base
has_many :users_projects_join
has_many :users, :through => :users_projects_join
has_many :active_users,
-> { where(users_projects_join: {user_status: true}) },
:through => :users_projects_join,
:source => :user
belongs_to :leader, :class_name => 'User', :foreign_key => :leader_id
end
I wanted to have :active_users so I wrote the query showed above but it gives me
ERROR: missing FROM-clause entry for table "users_projects_join"
Position: 167: SELECT "users".* FROM "users"
INNER JOIN "users_projects_joins"
ON "users"."id" = "users_projects_joins"."user_id"
WHERE "users_projects_joins"."project_id" = 20
AND "users_projects_join"."user_status" = 't'
But when I execute this query strictly in the Postgres it gives me all users with user_status true. I read that I need to add joins(:users_projects_join).where(...) but it didn't help.
Try now :
class Project < ActiveRecord::Base
has_many :users_projects_joins
has_many :users, :through => :users_projects_joins
has_many :active_users,
-> { where(users_projects_joins: {user_status: true}) },
:through => :users_projects_joins,
:source => :user
belongs_to :leader, :class_name => 'User', :foreign_key => :leader_id
end

What is the best way to handle 4 way relation between 2 models?

I have two models: Company and User
This is the situation:
Company can follow another company
User can follow a company
User can follow another user
What is the best way to define the relationships and how will the join model look like?
Also, are there any best practises when addressing such situations?
Update
Sorry, to have not mentioned this earlier. I am aware of the various relationship types available. My question is 'which is the best fit'?
Regarding your question I would suggest you to go through couple of Railscasts videos:
http://railscasts.com/episodes/47-two-many-to-many
http://railscasts.com/episodes/154-polymorphic-association
And this is described very well on RubyonRails website
http://guides.rubyonrails.org/association_basics.html
I would say look these for your case:
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
http://guides.rubyonrails.org/association_basics.html#the-has_and_belongs_to_many-association
I hope this will help you.
Thanks to polymorphic associations, we can put all relations into one table which like this:
create_table :follows do |t|
t.references :followable, :polymorphic => true
t.references :followed_by, :polymorphic => true
end
Then the models are:
class User < ActiveRecord::Base
has_many :following_objects, :class_name => 'Follow', :as => :followed_by
has_many :followed_objects, :class_name => 'Follow', :as => :followable
end
class Company < ActiveRecord::Base
has_many :following_objects, :class_name => 'Follow', :as => :followed_by
has_many :followed_objects, :class_name => 'Follow', :as => :followable
end
class Follow < ActiveRecord::Base
belongs_to :followable, :polymorphic => true
belongs_to :followed_by, :polymorphic => true
end
Sorry for the ugly names.
A basic idea would be to use two self-referencing assocations:
User -> Friendship <- User
Company -> Partnership <- Company
models/user.rb
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
models/friendship.rb
belongs_to :user
belongs_to :friend, :class_name => "User"
models/company.rb
has_many :partnerships
has_many :partners, :through => :partnerships
has_many :inverse_partnerships, :class_name => "Partnership", :foreign_key => "partner_id"
has_many :inverse_partners, :through => :inverse_partnerships, :source => :company
models/partnership.rb
belongs_to :company
belongs_to :partner, :class_name => "Company"
And one many-to-many assocation:
User -> CompanyUser <- Company
models/user.rb
has_and_belongs_to_many :companies
models/company.rb
has_and_belongs_to_many :users
So for this implementation you will need 5 tables (users, friendships, companies, partnerships and companies_users) if you are using a RDBMS.
You can get a nice example in this screencast:
http://railscasts.com/episodes/163-self-referential-association

Rails: fetch records at once for has_many and has_many :through associations

Given the following models:
User
class User < ActiveRecord::Base
has_many :given_loans, :class_name => "Loan", :foreign_key => "lender_id"
has_many :received_loans, :class_name => "Loan", :foreign_key => "borrower_id"
has_many :borrowed_books, :class_name => "Book", :foreign_key => "borrower_id", :through => :received_loans
has_many :own_books, :class_name => "Book", :foreign_key => "owner_id"
end
Book
class Book < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
has_many :loans, :foreign_key => "borrowed_book_id"
has_many :borrowers, :through => "loans", :foreign_key => "borrowed_book_id"
end
Loan
class Loan < ActiveRecord::Base
belongs_to :borrowed_book, :class_name => "Book", :foreign_key => "borrowed_book_id"
belongs_to :borrower, :class_name => "User", :foreign_key => "borrower_id"
belongs_to :lender, :class_name => "User", :foreign_key => "lender_id"
end
These relationships seem to work fine.
Now I'd like to query all books for a user, including both the borrowed books and the owned books, which I'm currently doing like this:
def books
own_books + borrowed_books
end
This method naturally causes two SQL queries, though:
Book Load (0.4ms) SELECT "books".* FROM "books" WHERE ("books".owner_id = 1)
Book Load (0.3ms) SELECT "books".* FROM "books" INNER JOIN "loans" ON "books".id = "loans".borrowed_book_id WHERE (("loans".borrower_id = 1))
Furthermore I'm not able to run active record methods such as limit or order against this selection, which would be nice to have. After all I'm fetching the books from the same table.
I'm assuming that there's a better (more "Rails'") and more efficient way of doing this. Anyone care to point me into the right direction? Cheers!
P.S.: A possible solution that came to mind was to define a query for a has_many :books relationship, but that didn't seem quite right either.
Here's what I ended up doing.
Using the meta_where gem the following method is possible to improve the situation:
def books
Book.includes(:loans).where({ "owner_id" => self.id } | { "loans.borrower_id" => self.id })
end
This method returns a relation object and produces only one SQL query. Any opinions where this method should be located? Right now I'm having it in the User class.
Use this in the model:
def as_json(options={})
super(include: { trades: { include: :trades } })
end

has_many through self referential association

I want to (as an example) create a has_many association to all posts by friends of a person, something like has_many :remote_posts to give me something like person > friends > person > posts.
..here is how I would go about it
script/generate model post title:string person_id:integer
script/generate model friendship person_id:integer friend_id:integer
script/generate model person name:string
class Person < ActiveRecord::Base
has_many :posts
has_many :friendships, :foreign_key => 'friend_id'
has_many :people, :through => :friendships
has_many :remote_posts, :class_name => 'Post', :through => :people, :source => :posts
end
class Friendship < ActiveRecord::Base
belongs_to :person
#also has a 'friend_id' to see who the friendship is aimed at
end
class Post < ActiveRecord::Base
belongs_to :person
end
# generate some people and friends
{'frank' => ['bob','phil'], 'bob' => ['phil']}.each {|k,v|
v.each {|f|
Friendship.create(
:person_id => Person.find_or_create_by_name(f).id,
:friend_id => Person.find_or_create_by_name(k).id
)
}
}
# generate some posts
Person.all.each {|p|
p.posts.create({:title => "Post by #{p.name}"})
}
Now,
Person.first.friendships # ..works
Person.first.people # (friends) ..works
Person.first.posts # ..works
Person.first.remote_posts #....
...and I get this error..
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: people.person_id: SELECT "posts".* FROM "posts" INNER JOIN "people" ON "posts".person_id = "people".id WHERE (("people".person_id = 1))
Aside from the foreign key error - seems like the friendships association isn't coming into play at all. I was thinking that this might be because of the :source => :posts, since the posts association would come into it twice.
I could write some finder sql (and that is what I have working at the moment), though I'd sooner do it this way.
Any ideas of how to get this to work?
How about this:
In the FriendShip class, add:
has_many :posts, :through => :person
and in the Person class, change the remote_posts to:
has_many :remote_posts, :class_name => 'Post',
:through => :friendships, :source => :person
How about a nested has_many :through relationship. This seems to work for me:
class Friendship < ActiveRecord::Base
belongs_to :person
belongs_to :friend, :class_name => 'Person'
has_many :posts, :through => :friend, :source => :posts
end
class Person < ActiveRecord::Base
has_many :posts
has_many :friendships, :foreign_key => 'friend_id'
has_many :people, :through => :friendships
has_many :remote_posts, :through => :friendships, :source => :posts
end
Note: this requires this nested_has_many_through plugin. (Note: direct linking to github repos seems to be broken... but that repo is there despite the error message.)

Rails - Two-way "friendship" model (cont'd)

This is a continuation of this question:
Original Question (SO)
The answer to this question involved the following set of models:
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :through => :friendships #...
end
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => 'User', :foreign_key => 'friend_id'
end
<% for friendship in #user.friendships %>
<%= friendship.status %>
<%= friendship.friend.firstname %>
<% end %>
This works fine if say, I have a user and I want to get all the "friendships" for which his or her id is the :user_id FK on the Friendship model. BUT, when I run something like
#user.friendships.friends
I would like it to return all User records for which that User is either the :user or the :friend in the friendship - so, in other words, return all friendships in which that user is involved.
Hopefully the above makes sense. I'm still quite new to rails and hope there is a way to do this elegantly without making just a standard link table or providing custom SQL.
Thank you!
Tom
railscasts episode on this topic
You cannot just use #user.friendships here because it will only give you those friendships where #friendship.user_id == #user.id.
The only thing I can think of right now is just to do
Friendship.find_by_user_id(#user.id).concat Friendship.find_by_friend_id(#user.id)
my solution is a a scope:
# model
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => "User"
belongs_to :recipient, :class_name => "User"
scope :of_user, lambda { |user_id| where("sender_id = ? or recipient_id = ?",
user_id, user_id) }
end
# in controller
#messages = Message.of_user(current_user)
From the railscast link:
# models/user.rb
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user

Resources