How can I validate relations in models in RoR? For example I have 3 models:
class Post < ActiveRecord::Base
belongs_to :blog
has_one :user, :through => :blog
validates :blog_id, :presence => true
end
class Blog < ActiveRecord::Base
belongs_to :user
has_many :posts, :dependent => :destroy
end
class User < ActiveRecord::Base
has_many :blogs
has_many :posts, :through => :blogs
end
And in my controller:
#post = current_user.blogs.find(params[:post].delete(:blog_id)).posts.build(params[:post])
But when I want to create post I get:
Can't mass-assign protected attributes: blog_id
I shouldn't get this error, because I am delete blog_id from params hash, or don't? Any way, what the better way of validating blog_id accessory to User.blogs in my Post model?
If you want to set the on which blog the post should be published after writing, you have to put the blog_id into the whitelist by setting attr_accessible
So in your example your Post model should look like
class Post < ActiveRecord::Base
belongs_to :blog
attr_accessible :blog_id, :title, :content
validates :blog_id, :presence => true
end​
Besides this. Be careful how you set up your relation. The difference between has_one and belongs_to is where the foreign key goes. It goes to where you define the belongs_to. has_one says that one of something is yours, so something points back to you. It doesn't make much sense to say that a Post has_one user...
It is enough to have a Post only belong to a Blog. You still can do something like current_user.posts by how you setup the relationship in the user model like you already did...
I'd recommend reading the following links http://guides.rubyonrails.org/association_basics.html
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html
Validations for :blog_id in Post model is perfect.
#post = current_user.blogs.find(params[:post].delete(:blog_id)).posts.build(params[:post])
:blog_id is deleted from params[:post], but let's look at the thing from a different view.
#blog = current_user.blogs.find(params[:post].delete(:blog_id))
#post = #blog.build(params[:post])
params[:post] does not have :blog_id, but build method automatically assigns blog_id to #blog.id.
That's why error for :blog_id is not coming up.
If you wanna avoid the mass assignment warning, you can make the :blog_id attribute accessible.
Related
I have these models: Post, TextPost and PhotoPost and I use polymorphic association.
You can find these three models here or look below.
post.rb
class Post < ActiveRecord::Base
belongs_to :user
default_scope { order ("created_at DESC")}
belongs_to :content, polymorphic: true
has_reputation :votes, source: :user, aggregated_by: :sum
end
photo_post.rb
class PhotoPost < ActiveRecord::Base
has_attached_file :image, styles: {
post: "200x200>"
}
end
text_post.rb
class TextPost < ActiveRecord::Base
attr_accessible :body
end
What I want, is to validate the presence of :body and :image when a users submits a text_post or photo_post respectively. So far, I found out that I have to use validates_associated.
The full project can be found on Github.
I have experiment a lot to find out how validates_associated works and searched for examples online but I don't have a clue what's going on.
(If you need any more info please let me know)
Any help/guidance is greatly appreciated.
I think for starters there needs to be some form of association between these 3 models here. You have Post, PhotoPost and TextPost. As a post can has a type of post. Also remember that type in rails is a reserved word. But anyways your models should be set out as followed:
class Post< ActiveRecord::Base
belongs_to :postable, polymorphic: true
end
class PhotoPost < ActiveRecord::Base
has_many :posts, as: :postable
end
class TextPost < ActiveRecord::Base
has_many :posts, as: :postable
end
As from looking at the snippet you provided it doesn't show any set up of polymorphic association. With regards to validating the association of the polymorphic association you might want to take a read of this answer: Validate presence of polymorphic parent. Furthermore also might want to read this article: Validating a polymorphic association for a new record . The purpose of the validate_associated validation helper simply ensures that the association between the two records are valid or as they put it works. In your case if you were to use that validation helper. This would not be what you'd want because all that would be doing is validating the association between the two models. See the second link I provided this is what I believe you are after.
Also in your controller you need to build the relationship between the models. So in your controller I think you could have something like this inside your new action:
def new
#Post = Postfind(params[:id])
#PhotoPost = #post.photoposts.build
#TextPost = #post.textposts.build
end
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.
My scenario is that there are several different models which can have comments. Trying to figure out the relationships:
Post
has_many :comments
Update
has_many :comments
Comment
belongs_to EITHER :post OR :update (but not both)????
Whats the proper way to set up the comment relationships? I want to be able to call Post.comments and Update.comments
Smells like a polymorphic association:
With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model.
So you'd want something like this:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Post < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Update < ActiveRecord::Base
has_many :comments, :as => :commentable
end
You'd have to set up a few things in the database for this to work as well. See the Polymorphic Associations section of the Active Record Associations Guide for details on the columns you'll need.
I have a many-to-many model, following the example in this great railscast
My model links authors to each other. I'd like to validate that an author cannot friend himself. I know I can handle this at the UI level, but I'd love to have a validation in place to prevent a bug in the UI from allowing it. I've tried validates_exclusion_of, but it doesn't work. Here's my model for the relationship:
class Friendship < ActiveRecord::Base
# prevent duplicates
validates_uniqueness_of :friend_id, :scope => :author_id
# prevent someone from following themselves (doesn't work)
validates_exclusion_of :friend_id, :in => [:author_id]
attr_accessible :author_id, :friend_id
belongs_to :author
belongs_to :friend, :class_name => "Author"
end
You'll have to use a custom validation:
class Friendship < ActiveRecord::Base
# ...
validate :disallow_self_referential_friendship
def disallow_self_referential_friendship
if friend_id == author_id
errors.add(:friend_id, 'cannot refer back to the author')
end
end
end
Is there any way to validate that a polymorphic association is only related to one item? for example, if I have a comments that are polymorphic and can be on photos, posts, etc. I want to ensure that if I am adding a comment to a posts' list of comments, that if the comment is already associated with the post, the add will fail. (validation uniqueness error). Any ideas?
So I'm guessing you have something like this:
class Comment < ActiveRecord::Base
belongs to commentable, :polymorphic => true
end
class Post < ActiveRecord::Base
has_many :comments, :as => commentable, :dependent => :destroy
end
class Photo < ActiveRecord::Base
has_many :comments, :as => commentable, :dependent => :destroy
end
Assuming your Comment model has a couple of attributes like author and body (that you want to be unique) then you could create a custom validation in that model like this:
validate do |comment|
if comment.commentable_type.constantize.comments.find_by_author_and_body(comment.author, comment.body)
comment.errors.add_to_base "Duplicate comment added for ..."
end
end
I've also assumed that comments are created something like this:
#post.comments.create(:author => name, :body => comment_text)