Currently, I'm making a simple blog-like app, where a user can make a post, and several other users can comment on it.
Is there a way to have a polymorphic attribute belong to more than one Model?
For example,
a Comment will always have an author (User model)
However, a Comment can belong to many other models (Posts, Journals, Articles, etc etc)
so, for (Posts, Journals, Articles) models, polymorphic association is best.
However, for the author (or User relationship), polymorphic would not work, since polymorphic can only belong to one at a time.
Is there a better way around this?
EDIT:
What are the pros/cons of doing this:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
end
EDIT2:
with the solution above, is there a more elegant way of doing this
def create
#comment = #commentable.comments.new(params[:comment])
#comment.user_id = current_user.id
if #comment.save
flash[:success] = 'Comment created'
redirect_to #commentable
else
flash[:error] = 'Comment not created - is body empty?'
redirect_to #commentable
end
end
without having to save the user_id manually in the controller?
#comment.user_id = current_user.id
You can have both a User relationship as well as a polymorphic relationship representing the model it is associated with. For example:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :document, polymorphic: true
end
class Post < ActiveRecord::Base
has_many :comments, as: :document
end
class Journal < ActiveRecord::Base
has_many :comments, as: :document
end
class Article < ActiveRecord::Base
has_many :comments, as: :document
end
class User < ActiveRecord::Base
has_many :comments
end
Now, you can call comment.user to get the User model for the person who created the comment and comment.document to get the Post, Journal, or Article that the comment is associated with.
Related
I didn't find any related threads on this, so I made a new question myself.
I have following models. I added foreign_key: :sender in the class User because the field name :sender of the model class Comment differs from the model class User.
I added dependent: :destroy to the class User to make sure when an user is deleted all of its comments are deleted as well.
However when I destroy an user, their comments do not get destroyed. The comments remain in the database. I don't see why.
class Comment < ApplicationRecord
belongs_to :sender, :class_name => "User"
belongs_to :event
end
class User < ApplicationRecord
has_many :comments, dependent: :destroy, foreign_key: :sender
end
class CommentsController < ApplicationController
<snip>
def destroy
#event = Event.find(params[:event_id]) # dont mind this line
#comment = Comment.find(params[:id])
#comment.destroy
redirect_to event_path(#event) # dont mind this either
end
<snip>
end
class UsersController < ApplicationController
<snip>
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to users_path
end
<snip>
end
I managed to fix it by changing foreign_key: :sender to foreign_key: :sender_id. In other words, the real db column :sender_id has to be given instead of the field name :sender.
However, when I was using foreign_key: :sender before I did not get any errors from rails. It just did not the delete the comments when deleting an user.
If anyone can explain why I did not see any errors, I would like to hear from you.
I'm running into an issue with an associated model. I have a nested attributes for my user, to a reviewer. A user can essentially review another person, thus be a reviewer and be the person reviewed.
It's set up like this:
# User
has_many :reviewers
accepts_nested_attributes_for :reviewers
has_many :active_managements, class_name: 'Reviewer',
foreign_key: 'reviewer_id',
dependent: :destroy
class Reviewer < ActiveRecord::Base
belongs_to :user
belongs_to :reviewer_id, class_name: 'User'
end
now in my users controller I have:
class UsersController < ApplicationController
def edit
#user = User.find(params[:id])
#user.reviewers.build
redirect_to root_url && return unless #user.activated?
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user)
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:invitation_token, :first_name, :admin,
:last_name, :title, :email, :password,
reviewers_attributes: [:reviewer_id])
end
the error that I get is:
User(#70197180889680) expected, got String(#70197172430700)
happening on "user_params", so I assume it has to do with my attributes. Anybody know what's up?
The line belongs_to :reviewer_id, class_name: 'User' is incorrect. Try changing it to something like belongs_to :reviewing_user, class_name: 'User' (replacing reviewing_user with whatever name you want to use for this association :), not field name.
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to
The immediate fix is here:
#app/models/review.rb
class Reviewer < ActiveRecord::Base
belongs_to :reviewer
end
--
This is how I'd fix the systemic issue:
#config/routes.rb
resources :users do
resources :reviewers #-> url.com/users/:user_id/reviews/new
end
#app/controllers/reviewers_controller.rb
class ReviewersController < ApplicationController
def new
#user = User.find params[:user_id]
#review = user.reviewers.new
end
def create
#user = User.find params[:user_id]
#review = user.reviewers.new reviewer_params
end
private
def review_params
params.require(:reviewer).permit(:user_id, :reviewer_id)
end
end
Models
Apart from this, I think your main issue is the way your models are set up.
Ideally, you want to have reviewer and user as the same data-set (I presume they're both users), which makes your current setup really inefficient...
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :reviewers,
class_name: "User",
join_table: :reviewers_users
foreign_key: :user_id,
association_foreign_key: :reviewer_id
end
What you're looking for is something called a self referrential association, which basically allows you to associate the same model in a many-to-many relationship.
In most cases, this will be used with a has_and_belongs_to_many relationship; you could also use a has_many :through although it's not as common.
The above will allow you to use the following:
#user = User.find params[:id]
#user.reviewers #-> collection of users who are reviewers of original user
I have a Post model that looks like this:
# id :integer
# author_id :integer
# owner_id :integer
# owner_type :string(255)
# content :text
class Post < ActiveRecord::Base
belongs_to :author, class_name: 'User'
belongs_to :owner, polymorphic: true
end
The owner can be a User, Group, or a Place. I'm wondering what is the best approach to model a Comment. Considering that it shares most of its attributes with Post, I thought that the same Post model could serve as Comment using a relation like:
has_many :comments, class_name: 'Post', :as => :owner
But indeed I'm not happy at all with this solution since Post uses the same relation for storing its owner.
It's better to create a different model for comment? What about STI?
To make an abstraction of the real world and keep the things simple (clear, succinct), my suggestion is to use a Comment model:
class Comment < ActiveRecord::Base
belongs_to :post
end
class Post < ActiveRecord::Base
belongs_to :author, class_name: 'User'
belongs_to :owner, polymorphic: true
has_many :comments
end
If you are planning to add comments to another entity, for example, photos, use a Polymorphic Association:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Post < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Photo < ActiveRecord::Base
has_many :comments, :as => :commentable
#...
end
The rails tutorial does exactly this: guides.rubyonrails.org/getting_started.html. It makes a blog with a posts model and a comments controller attached to it.
resources :posts do
resources :comments
end
run this
$ rails generate controller Comments
And add this to the generated controller:
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:commenter, :body))
redirect_to post_path(#post)
end
end
You should look at this railscast:
http://railscasts.com/episodes/154-polymorphic-association?view=asciicast
It's a bit old, but it is one of the free ones. You do want to create a different model for your comments, and you want to create a polymorphic association.
I'm building an application where a user can build a lesson from an assortment of standards and questions to teach the standards, but I'm not exactly sure if I have set up everything correctly or not.
The 'new' page allows the user to use drop down menus to sort through to select the standards through the Lesson Controller
def new
#search = Standard.search(params[:q])
#standards = #search.result
#lesson = Lesson.new
end
def create
#lesson = current_user.selects.build(params[:lesson])
if #lesson.save
redirect_to edit_lesson_path(#lesson)
else
render :action => 'new'
end
end
def edit
#lesson = Lesson.find(params[:id])
#standards = #lesson.standards
end
Once the standards are selected, the user is redirected to the 'edit' page which shows each of the selected standards, but this is the part where I'm having trouble with and I'm not sure my models are set up correctly. There is a has_many through relationship between lessons and standards to select standards, and also a has_many through relationship between lessons and questions as well to select the questions associated with each standard.
I'm trying to list each of the questions associated with the standards underneath the parent standard, I have tried #questions = #standards.questions in the 'edit' method, but an ActiveRecord Relation NoMethod error is called. I have also tried #questions = Question.where(:standard_id => #standards) in the controller, but the page lists all of the questions for all of the selected standards underneath each standard.
My lesson model:
class Lesson < ActiveRecord::Base
attr_accessible :user_id, :name, :grade_id, :text_id, :date, :subject_id, :question_ids
has_many :select_standards
has_many :standards, through: :select_standards
has_many :select_questions
has_many :questions, through: :select_questions
end
Standard model:
class Standard < ActiveRecord::Base
attr_accessible :content, :grade_id, :subject_id
belongs_to :subject
belongs_to :grade
has_many :questions
end
Question model:
class Question < ActiveRecord::Base
attr_accessible :content, :standard_id
belongs_to :standard
has_many :select_questions
has_many :lessons, through: :select_questions
end
Select_standards:
class Selection < ActiveRecord::Base
attr_accessible :lesson_id, :standard_id
belongs_to :lesson
belongs_to :standard
end
The problem seems to be related to Rails not being able to figure out proper class names for your association names. For example, you've specified has_many :select_standards for your class Selection, by default Rails would search for SelectStandard class for association name :select_standards. The fix is easy in this case, which is to either change the association declaration to:
has_many :selections
or add class_name to association as:
has_many :select_standards, class_name: 'Selection'
You need to make sure that this is done for any and all the custom association names that do not match the actual ActiveRecord inherited class names.
Hello and thanks for any help in advance.
I want to create a relationship between a user and a product(pdf) upon an order being saved.
class PDF
has_many :line_items
end
class LineItem
belongs_to :pdf
belongs_to :cart
end
class Cart
has_many :line_items
has_one :order
end
class Order
belongs_to :cart
end
Upon the user purchasing a line_item, I would like to create relationships through a join model between user and pdf (pdf_relationships).
I'm trying to find each PDF(found by foreign_key line_item.pdf_id) in a given cart and create pdf_relationships between a user and each pdf in the cart. I'll be making the user's id the owner id and making the pdf's id the owned_id.
My order controller looks like this:
def create
#order = current_cart.build_order(params[:order])
#order.ip_address = request.remote_ip
if #order.save
if #order.purchase
render :action => "success"
else
render :action => "failure"
end
else
render :action => 'new'
end
end
and what I'm having trouble with is this:
class Order
belongs_to :cart
before_save :create_owner
***def create_owner
self.cart.line_items.each do |item|
pdf.find_by_item_pdf_id(:pdf_id)
current_user.pdf_relationships.build(:owned_id => pdf.id)
end
end***
end
here is my user model:
class User
has_many :line_items
has_many :pdf_relationships, foreign_key: :owner_id, :dependent => :destroy
has_many :pdfs, foreign_key: :user_id, dependent: :destroy
has_many :pdf_ownings, :through => :pdf_relationships, :source => :owned
def owning?(owned)
pdf_relationships.find_by_pdf_owned_id(owned)
end
def own!(owned)
pdf_relationships.create!(:owned_id => owned.id)
end
def unown!(owned)
pdf_relationships.find_by_pdf_owned_id(owned).destroy
end
I hope this is clear enough. I've been trying to figure this out for sometime now and definitely trying to get past being just a novice. Suggestive readings are definitely welcome too!
It looks like you have the right idea.
Since LineItem has many PDFs, you can just use the pdf_id from the LineItem without bothering to fetch the record from the database. You can also append those IDs to the existing set of user-PDF associations as if you were just pushing items onto an array.
However, your model won't have access to the current session (since that's handled by the controller), so you'll have to pass the current user to the Order some other way, perhaps as part of the purchase method.
class Order
belongs_to :cart
def purchase(user)
# ... existing logic ...
user.pdf_relationship_ids << cart.line_items.map(&:pdf_id)
end
end
You will also have to declare the association between User and PDF and create the migrations, but it sounds like you're already planning to do that.
Update: I think you can greatly simplify your User model by taking advantage of has_many :through. Here's what I envision:
class User < ActiveRecord::Base
has_many :orders
has_many :line_items, :through => :orders
has_many :pdfs, :through => :line_items
end
class Order < ActiveRecord::Base
belongs_to :user
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
has_one :pdf
end
class Pdf < ActiveRecord::Base
belongs_to :line_item
end
Then you don't have to explicitly specify that a user owns a PDF. It's implicit in the user -> order -> line item -> PDF relationship.
This may not solve your problem as-is. You wouldn't be able to "disown" a PDF without deleting the original line item, which is probably not what you want. But I think you should try to aim for something like this. Take advantage of Rails's built-in associations as much as you can.