Kleber S.
Kleber S.

Reputation: 8240

Rails 3 - Custom validator

I'm trying to make a custom validator work on my app.

I have already configured my config.autoload.paths and it is loading fine.

The problem is with the validator itself.

Result of binding pry

instance variables: @attributes  @options  @with
locals: _  _dir_  _ex_  _file_  _in_  _out_  _pry_  attribute  record  value
[12] pry(#<FileCountValidator>)> value
=> [#<Attachment id: 60, description: nil, file: "cache_600_1__img_948867_5770137d84a6c79ac825886938e...", attachable_type: "Post", attachable_id: 15, created_at: "2012-03-10 14:50:54", updated_at: "2012-03-10 14:50:54">,
 #<Attachment id: 61, description: nil, file: "cache_600_1__img_948867_90f64e01b9c871ec656a884e015...", attachable_type: "Post", attachable_id: 15, created_at: "2012-03-10 14:50:54", updated_at: "2012-03-10 14:50:54">,
 #<Attachment id: 62, description: nil, file: "cache_600_1__img_948867_85eda3946c27fa90566403ac941...", attachable_type: "Post", attachable_id: 15, created_at: "2012-03-10 14:50:54", updated_at: "2012-03-10 14:50:54">,
 #<Attachment id: nil, description: nil, file: nil, attachable_type: "Post", attachable_id: 15, created_at: nil, updated_at: nil>]
[13] pry(#<FileCountValidator>)> value > @with
TypeError: compared with non class/module
from /home/kleber/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.1/lib/active_record/relation/delegation.rb:20:in `>'
[14] pry(#<FileCountValidator>)> value.size > @with
=> true
[15] pry(#<FileCountValidator>)> value.size
=> 4
[16] pry(#<FileCountValidator>)> @with
=> 3
[17] pry(#<FileCountValidator>)> 

So, I trying to make this comparisson exactly like I did on the pry debug console.

def validate_each(record, attribute, value)
  #binding.pry
   record.errors.add(attribute,"#{@with} attachments per post only. #{attribute['file'].size} detected.") if value.size > @with
  end

But doing this, return me the error:

NoMethodError (undefined method `size' for nil:NilClass):
  lib/validators/file_count_validator.rb:11:in `validate_each'
  app/controllers/posts_controller.rb:61:in `block in update'
  app/controllers/posts_controller.rb:60:in `update'

Are there any way to catch the value before it get enters the validate_each method?

Upvotes: 0

Views: 2713

Answers (1)

prasvin
prasvin

Reputation: 3009

Sorry, but the value being passed seems to be correct. value is meant to be the value of that attribute for that record, i.e. record.send(attribute) should be equal to the value.

Calling validates :attachments, :photo_count => 2 does not send 2 to the validate_each method as argument value. You could do :photo_count => true, which is what I generally do, or even :photo_count => "foo". The photo_count in your validates statement serves to provide that the validator is to be called by passing the value(i.e. 2 or true or "foo" for stated examples) embedded in the options hash.

Here's a way without passing a limit to the validator.

Create a Constants class and define MAX_ATTACHMENTS constant. I usually have it at models/constants.rb.

class Constants
  MAX_ATTACHMENTS = 1
end

Then, in the validator, you could do

class PhotoCountValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors.add(attribute,"#{Constants::MAX_ATTACHMENTS} attachments per post only.") if record.send(attribute).size > Constants::MAX_ATTACHMENTS
  end
end

Another way that passes a parameter to a validator:

validates :attachments, :photo_count => 3 #desired_limit

Override the initialize method for PhotoCountValidator class, and initialize a class variable :

def initialize(options)
  @@max_objects = options[:with] # the options hash will be as {:attributes=>[:attachments], :with=>3}, where :with contains the desired limit passed
  super
end

Then within the validate_each method :

record.errors.add(attribute,"#{@@max_objects} attachments per post only.") if record.send(attribute).size > @@max_objects

Upvotes: 2

Related Questions