I'm trying to design a comment system allowing users to post on other users' pages, through comments.
A user will have a comment on his page, which is posted by another user called "commenter."
1) Is the following code legit/functional/decent design?
2) Is it OK to have a renamed "commenter" user, in conjunction with an un-renamed "user", or should all association names of user always be renamed semantically?
3) Are there better ways to implement this design intent (e.g. not doing a has_many through:)?
class User < ActiveRecord::Base
has_many :comments
has_many :users, through: :comments
has_many :commenters, through: :comments, class_name: 'User'
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commenter, class_name: 'User'
end
NOTE:
I wish to allow users to comment on users, but also on other models, (e.g. characters, celebrities). So I would think having the comments table be used in various has_many through associations is called for.
users has many commenters through comments
characters has many commenters through comments
celebrities has many commenters through comments
I believe your design is not going to work as it is - you are mixing has_many with has_many through. If I were you I would use an approach like this one:
class User < ActiveRecord::Base
has_many :owned_comments, class_name: 'Comments', foreign_key: 'owner_id'
has_many :posted_comments, class_name: 'Comments', foreign_key: 'commenter_id'
end
class Comment < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
belongs_to :commenter, class_name: 'User'
end
have to tried acts_as_commentable gem it give lot of other options as well like public,private comment https://github.com/jackdempsey/acts_as_commentable
I implemented a similar functionality in mongoid and rails. The models were User, Friendship and Request. Its like User sends friend request to another user.
class User
include Mongoid::Document
include Mongoid::Timestamps
devise :invitable, :database_authenticatable, :registerable, :recoverable,
:rememberable, :trackable, :validatable, :confirmable
...
has_many :requests_from, class_name: "Request", inverse_of: :requested_by
has_many :requests_to, class_name: "Request", inverse_of: :requested_to
has_many :friendships, inverse_of: :owner
def friends
#retrive all the friendships and collect users have sent me a request or being sent a request.
fs = Friendship.any_of({:friend_id.in => [self.id]}, {:owner_id.in => [self.id]}).where(state: 'accepted')
User.in(id: fs.collect{|i| [i.friend_id, i.owner_id]}.flatten - [self.id])
end
end#User
class Friendship
include Mongoid::Document
include Mongoid::Timestamps
field :state, type: String, default: 'pending'
field :pending, type: Boolean, default: true
belongs_to :owner, class_name: 'User'
belongs_to :friend, class_name: "User"
validates :state, inclusion: { in: ["pending", "accepted", "rejected"]}
...
end#Friendship
class Request
include Mongoid::Document
include Mongoid::Timestamps
field :state, type: String, default: 'pending'
belongs_to :requested_by, class_name: 'User', inverse_of: :requests_from
belongs_to :requested_to, class_name: 'User', inverse_of: :requests_to
validates :state, inclusion: { in: ["pending", "accepted", "rejected"]}
...
end#Request
I hope this helps.
Related
Is it possible to eager load polymorphic nested associations? How can I include doctor_profile's for Recommendation's and patient_profile's for Post's?
I'm able to call Activity.includes(:trackable).last(10) but not sure how to include the associated models past there. I've tried belongs_to :recommendation, -> { includes :patient_profile, :doctor_profile} with no luck
class Activity
belongs_to :trackable, polymorphic: true
end
class Recommendation
has_many :activities, as: :trackable
belongs_to :doctor_profile
end
class Post
has_many :activities, as: :trackable
belongs_to :patient_profile
end
with respect referenced this SO answer and comments
for your problem you can managed with foreign_type field from polymorphic table to reference which model that use it
class Activity
belongs_to :trackable, polymorphic: true
# below is additional info
belongs_to :recommendation, foreign_type: 'Recommendation', foreign_key: 'trackable_id'
belongs_to :post, foreign_type: 'Post', foreign_key: 'trackable_id'
end
and you can call it
Activity.includes(recommendation: :doctor_profile).last(10)
Activity.includes(post: :patient_profile).last(10)
Activity.includes(recommendation: :doctor_profile) means
Activity will join recommendation with foreign_type and trackable_id
and then from recommendation will join doctor_profile with doctor_profile_id
The above answer works, but the use of foreign_type isn't actually supposed to do what the commenter intended.
https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
foreign_type is used to specify the name for the column which determines the class type for the relation.
I think the intended result here is to instead use class_name to specify which table the relation is referring to. If the relation has the same name as the table, then class_name can actually be inferred (which is why the provided answer works in the first place)
In order to get the above answer to work, specifying inverse_of for the belongs_to and adding for the has_many associations got everything to work. For example:
class Activity
belongs_to :trackable, polymorphic: true
# below is additional info
belongs_to :recommendation, foreign_type: 'Recommendation', foreign_key: 'trackable_id', inverse_of: :activities
belongs_to :post, foreign_type: 'Post', foreign_key: 'trackable_id', inverse_of: :activities
end
On the Post model:
has_many :activities, inverse_of: :post
On the Recommendation model:
has_many :activities, inverse_of: :recommendation
In my application I have a simple relationship where a user that has many events:
class User < ApplicationRecord
has_many :events, foreign_key: 'created_by', dependent: :destroy
accepts_nested_attributes_for :events
end
class Event < ApplicationRecord
belongs_to :user, foreign_key: 'created_by', inverse_of: :events
validates :created_by, presence: true
end
When I am trying to create a user alongside with some events I am getting a validation error "Companies.created by can't be blank".
My params hash looks like that:
{"user"=>{"events_attributes"=>[{"name"=>"disney show"}]}}
When I remove validates :created_by, presence: true everything works as expected. Any help would be appreciated!
Try to specify explicitly bi-directional associations in your User model using inverse_of: :
class User < ApplicationRecord
has_many :events, foreign_key: 'created_by', inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :events
end
And your Event record will be created with present foreign key pointing to User.
What would be the best way to architect the model relationships and nested resources to build a game with: Users(from devise), Games, Players(join table w/ Games/Users). My problem is Users exist, but Players need to be created at the same time as games. Creating a game also has to create a player, which is possible but feels icky. Is there a better way to do this? I would like to avoid using transactions or filters to create new resources. Thanks.
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable
devise :omniauthable, :omniauth_providers => [:facebook, :twitter]
has_many :games
end
class Player < ActiveRecord::Base
validates :user_id, uniqueness: { scope: :game,
message: "can't join your own game" }
belongs_to :user
belongs_to :game
has_one :board
has_many :ships
end
class Game < ActiveRecord::Base
belongs_to :first_player, class_name: 'Player', foreign_key: 'first_player_id'
belongs_to :second_player, class_name: 'Player', foreign_key: 'second_player_id'
has_one :first_player_board, through: :first_player, source: :board
has_one :second_player_board, through: :second_player, source: :board
end
I ended up solving this by doing away with the player model all together and having Users have_many :games.
class Game < ActiveRecord::Base
belongs_to :first_player, class_name: 'User', foreign_key: 'first_player_id'
belongs_to :second_player, class_name: 'User', foreign_key: 'second_player_id'
has_one :first_player_board, through: :first_player, source: :board
has_one :second_player_board, through: :second_player, source: :board
end
I've built easy Twitter application in Rails.
Now I would like to choose three random users that are not followed by the current user.
Here is my model:
class User < ActiveRecord::Base
has_many :tweets, dependent: :destroy
has_many :followerships, class_name: 'Followership', foreign_key: 'followed_id'
has_many :followedships, class_name: 'Followership', foreign_key: 'follower_id'
has_many :followers, through: :followerships, source: :follower
has_many :followed, through: :followedships, source: :followed
end
class Followership < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
class Tweet < ActiveRecord::Base
belongs_to :user
end
I tried to use the following query:
User.where.not(followers: current_user).order("RANDOM()").limit(3)
But it obviously doesn't work as I get no such column: users.follower_id error.
Is it even possible to do without sql query?
Thank you!
Try this:
already_following = current_user.followed.map(&:id)
#users = User.where.not(id: already_following).order("RANDOM()").limit(3)
Basically what I did, was got the list of users already being followed. Then you check the User table for id's not matching users already being followed.
Suppose an app where users can give gifts to other users. There are two relations between the gift and the user (one as sender and the other one as receiver).
The receiver part seems to not be working according to the following code, where a gift is created but not retrieved when calling the association:
require 'rubygems'
require 'mongoid'
Mongoid.load!("./config/mongoid.yml")
class User
include Mongoid::Document
has_many :gifts
has_many :gifts_sent, class_name: "Gift", as: :sender
end
class Gift
include Mongoid::Document
belongs_to :user, inverse_of: :gifts
belongs_to :sender, inverse_of: :gifts_sent, class_name: "User"
end
alice = User.create!
bob = User.create!
gift = Gift.create! sender: alice, user: bob
puts Gift.where(sender_id: alice.id).count # => 1 (nice)
puts alice.gifts_sent.count # => 0 (not so nice)
How should the association be defined to make the last line output 1?
You have to give a inverse_of to has_many gifts relation.
And to work the second relation named gifts_sent, You have to mention the foreign_key while defining relation.
class User
include Mongoid::Document
has_many :gifts, inverse_of: :user
has_many :gifts_sent, :foreign_key => :assign_whatever_field, class_name: "Gift", inverse_of: :sender,
end
And the gift Model will be
class Gift
include Mongoid::Document
belongs_to :user, inverse_of: :gifts
belongs_to :sender,:foreign_key => :assign_whatever_field, inverse_of: :gifts_sent, class_name: "User"
end
It is necessary to include the inverse relation in the User model as well:
class User
include Mongoid::Document
has_many :gifts, inverse_of: :user
has_many :gifts_sent, inverse_of: :sender, class_name: "Gift"
end
class Gift
include Mongoid::Document
belongs_to :user, inverse_of: :gifts
belongs_to :sender, inverse_of: :gifts_sent, class_name: "User"
end