I have a Post has_many Comments association. Post has boolean attribute published.
When post.published is false, the new comment shouldn't be valid.
What is the best practise to accomplish this kind of validation?
I've tried to do it by this way, but sadly, it doesn't work correctly. It is still possible to create new comment for unpublished post.
class Comment < ActiveRecord::Base
validates :post_id, presence: true, if: :post_is_published
...
def post_is_publised
post && post.published
end
end
Hmm.. I think you have syntax errors in your code... Try this:
class Comment < ActiveRecord::Base
validates :post_id, :presence => true, :if => :post_is_published
def post_is_publised
post.try(:published)
end
end
After reading your console output and checking your question one more time:
class Comment < ActiveRecord::Base
validate :post_has_to_be_published
def post_has_to_be_published
unless post.try(:published)
self.errors.add(:base, "you can add comments only to published posts")
end
end
end
I understand that you don't want to allow adding comments to unpublished posts. Above code should accomplish that.
Related
I have a Post model and Comment model (Post has_many comments, Comment belongs_to Post)
I want to validate a field in my Comment model where they can only create/update if a comment field is less than the difference between 2 Post date_fields. How do I go about achieving this?
Right now, in my Comment model, I have:
validates :comment_check, presence: true, :numericality => { :greater_than_or_equal_to => 0 }
I just need to add the validation to make it :less_than_or_equal_to the Post date diff. I also have this model method in my Post model:
def self.days_diff
(end_date.to_date - start_date.to_date).to_i
end
Any help would be greatly appreciated!
In your Comment Model you can validate with:
validate :comment_date
def comment_date
if self.post.end_date < Time.now or self.post.start_date.to_date > Time.now
self.errors.add(:created_at, "failed to save at the moment")
end
end
I have two models Post model and Comment model..First if a create a post it will have a post id of 1 then while creating a comment i can give association to the post using post_id equal to 1 but if i create a comment with post id of 2 which does not exist it would still go ahead and create a comment but with id of 'nil'..I want to ensure that the comment will be created only if the respective post_id is present.
class Post < ActiveRecord::Base
has_many :comments, dependent: destroy
end
class Comment < ActiveRecord::Base
belongs_to :post
validates_associated: post
end
As per my understanding validates_associated checks whether the validations in post model passes before creating a comment. Clarify me if i am wrong and what would be a appropriate solution for the above scenario?
First, the preferred way of setting the association b/w Post-Comment here is by :
def new
#product = Product.first
#comment = #product.comments.build
end
def create
#product = Product.find(params[:comment][:post_id])
#comment = #product.comments.create(comment_params)
end
For your particular scenario, I'm assuming that post_id is coming in params via some form or something, and then you wish to create a comment only if the post with that particular post_id exists. This can be done by adding following in Comment model:
validates :post, presence: true, allow_blank: false
OR
validate :post_presence, on: :create
def post_presence
errors.add(:post_id, "Post doesn't exist") unless Post.find(post_id).present?
end
You can even do the same thing at controller-side with before_action/before_filter hooks.
You can do this to validate the presence of post_id
class Comment < ActiveRecord::Base
belongs_to :post
validates :post_id, :presence => true
end
or to validate association, you can use
class Comment < ActiveRecord::Base
belongs_to :post
validates_presence_of :post
end
I want to order posts based on the total votes it has. This is what I have in the Post Model:
class Post < ActiveRecord::Base
attr_accessible :title, :url
validates :title, presence: true
validates :url, presence: true
has_many :votes
def vote_number
votes.where(direction: "up").count - votes.where(direction: "down").count
end
end
And this is what I attempted to do in the Post Controller:
def index
#posts = Post.last(10).order('vote_number')
end
Nevertheless I get this error from the index:
undefined method `order' for #<Array:0x3787158>
The other questions in Stack Overflow resolved this problem by making the calculation in the Post Controller but I can not do it because votes are arrays and not integers.
Found a way to solve it. Instead of using order I used sort_by.
Instead of having this in the Post Controller:
def index
#posts = Post.last(10).order('vote_number')
end
I used sort_by:
def index
#posts = Post.all.sort_by{|post|-post.vote_number}
end
You should try counter cache.
You can read more about it from the following links -
How to sort authors by their book count with ActiveRecord?
http://hiteshrawal.blogspot.com/2011/12/rails-counter-cache.html
http://railscasts.com/episodes/23-counter-cache-column
Counter cache only works inside rails. If you updated from outside application you might have to do some work around.
first, last and all execute query. Insert order always before those three.
class Post < ActiveRecord::Base
attr_accessible :title, :url
attr_reader :vote_difference # getter
attr_writer :vote_difference # setter
validates :title, presence: true
validates :url, presence: true
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :post, :counter_cache => true
#more class methods here
def after_save
self.update_counter_cache
end
def after_destroy
self.update_counter_cache
end
def update_counter_cache
post.vote_difference = post.comments.where(direction: 'up').count - post.comments.where(direction: 'down').count
post.save
end
end
Now you can sort by vote_difference when you query up.
for example -
posts = Post.order(:vote_difference, :desc)
I haven't check the correctness of my code yes. If you find any issues please let me know. I am sure it can be adapted to make it works.
If you follow this pattern to use counter_cache you might will to run a migration to add a vote_difference column, and another migration to update the vote_difference column for previous created post.
I have a couple of classes with a usual one-to-many relationship, like:
class Blog < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :blog
validates :blog_id, presence: true
end
when I build a blog and add to it a bunch of posts:
blog = Blog.new
blog.posts = [Post.new, Post.new]
I cannot just save the blog, like this:
blog.save
because blog_id in the posts is blank, so validation for the posts are failing. I thought Rails was smart enough to either check the id or the presence of the object, but it isn't.
What's the proper Rails way to solve this? Something better than:
saved = true
Blog.transaction do
saved &&= #blog.save
if saved && params[:blog][:posts].responds_to?(:each)
params[:blog][:posts].each do |post|
p = Post.new({blog: #blog}, without_protection: true)
saved &&= p.save
end
end
end
My solution, so far, is to change the validation line from:
validates :blog_id, presence: true
to
validate { errors.add(:blog, "can't be blank") if blog_id.blank? && blog.blank? }
which makes me wonder, why isn't Rails doing that for associations? Is there any danger in doing that?
You can do it two ways
blog.posts.create(attributes_of_new_post)
or
blog.posts << Post.new(attributes_of_new_post)
And change your has_many to be
has_many :posts, :inverse_of => :blog
Is there any way I can validate a single attribute in ActiveRecord?
Something like:
ac_object.valid?(attribute_name)
You can implement your own method in your model. Something like this
def valid_attribute?(attribute_name)
self.valid?
self.errors[attribute_name].blank?
end
Or add it to ActiveRecord::Base
Sometimes there are validations that are quite expensive (e.g. validations that need to perform database queries). In that case you need to avoid using valid? because it simply does a lot more than you need.
There is an alternative solution. You can use the validators_on method of ActiveModel::Validations.
validators_on(*attributes) public
List all validators that are being used to validate a specific
attribute.
according to which you can manually validate for the attributes you want
e.g. we only want to validate the title of Post:
class Post < ActiveRecord::Base
validates :body, caps_off: true
validates :body, no_swearing: true
validates :body, spell_check_ok: true
validates presence_of: :title
validates length_of: :title, minimum: 30
end
Where no_swearing and spell_check_ok are complex methods that are extremely expensive.
We can do the following:
def validate_title(a_title)
Post.validators_on(:title).each do |validator|
validator.validate_each(self, :title, a_title)
end
end
which will validate only the title attribute without invoking any other validations.
p = Post.new
p.validate_title("")
p.errors.messages
#=> {:title => ["title can not be empty"]
note
I am not completely confident that we are supposed to use validators_on safely so I would consider handling an exception in a sane way in validates_title.
I wound up building on #xlembouras's answer and added this method to my ApplicationRecord:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
end
Then I can do stuff like this in a controller:
if #post.valid_attributes?(:title, :date)
render :post_preview
else
render :new
end
Building on #coreyward's answer, I also added a validate_attributes! method:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
def validate_attributes!(*attributes)
valid_attributes?(*attributes) || raise(ActiveModel::ValidationError.new(self))
end
end