2

I have a variable value that is initialized when I create a new object. However, this variable is not a column of my table. I just want to use it inside my model and have it available for some methods inside my class:

class MyClass < ActiveRecord::Base
  def initialize (value)
    @value = value
  end
end

So, when a create a new object @value will keep the some text temporarily, for example:

test = MyClass.new("some text")

There is a way to validates the variable value to accept only text?

class MyClass < ActiveRecord::Base
  validates_format_of :value => /^\w+$/ # it doesn't work

  def initialize (value)
    @value = value
  end
end

EDIT

I have tried all the answer, but my Rspec is still passing:

My new class:

class MyClass < ActiveRecord::Base
  validate :check_value

  def check_value
    errors.add(:base,"value is wrong") and return false if !@value || !@value.match(/^\w+$/)
  end  

  def initialize (value)
    @value = value
  end

  def mymethod
    @value
  end
end

My Rspec (I was expecting it to fail, but it is still passing):

describe MyClass do
  it 'checking the value' do
    @test = MyClass.new('1111').mymethod
    @test.should == '1111'
  end
end

I would like to raise an validation error before assigning 1111 to the @value.

gabrielhilal
  • 10,660
  • 6
  • 54
  • 81
  • I think you got your answers, but here is a friendly warning: don't override `#initialize` in your models. Otherwise you'll miss the one defined in `AR::Base` (unless you override it carefully with `super` call). – KL-7 Jul 18 '12 at 13:56
  • 3
    [Avoid overriding ActiveRecord::Base#initialize](http://stackoverflow.com/questions/328525/what-is-the-best-way-to-set-default-values-in-activerecord), add a after_initialize filter instead if you need to do something upon object instantiation. – mogelbrod Jul 18 '12 at 13:57
  • Btw, if you define attribute accessor, e.g., `attr_accessor :foo`, in your model you can pass the value in the default constructor as any other attribute: `MyModel.new(:foo => 'bar')`. – KL-7 Jul 18 '12 at 13:59
  • Thank you guys for all the answers, but I couldn't achieve what I want.... I have edited my question. – gabrielhilal Jul 18 '12 at 14:22
  • \w includes 1. `\w` is equivalent to `[A-Za-z0-9\_]` – Chowlett Jul 18 '12 at 15:35
  • @Chowlett - really? how can I avoid all number? – gabrielhilal Jul 18 '12 at 15:50
  • 1
    Depends what you want to _accept_. `[A-Za-z]` might be a good start, or the POSIX class `[[:alpha:]]`. `\D` is explicitly "anything at all except a digit". – Chowlett Jul 18 '12 at 15:55

6 Answers6

4

It's because you're using the validates_format_of. It's a helper method that essentially calls validates_each. That method validates specific attribute. You can check its source here.

Since you're trying to validate on instance variable and not on an attribute, you have to write a custom validator.

Validate with this instead:

validate :check_value

def check_value
   unless @value && @value.match(/^\w+$/)
      errors.add(:base,"value is wrong") and return false
   end
end

I hope this is just a representative example of your code. Please tell me you don't use @value as variable name.

shime
  • 8,746
  • 1
  • 30
  • 51
  • yep, it is just an example... I have edited my question, as I am still able to assign '1111', for example, for `@value` – gabrielhilal Jul 18 '12 at 14:19
4

Rails does the validations only if you call valid? or save on the model. So if you want it to accept only values for your value when calling those methods, do a custom validation:

class MyClass < ActiveRecord::Base

  validate :value_string

  #further code

Setup your value validation like that under the protected scope

protected

  def value_string
    self.errors[:base] << 'Please assign a string to value' unless @value.match(/^\w+$/)
  end

These validations won't be called without calling valid?or save, like I said before. If you want value not to be assigned with another value than a word-like-value at any time, there's no other way than to prevent it on initialization:

def initialize(attributes = nil, options = {})
  attr_value = attributes.delete(:value) if attributes
  @value = attr_value if attr_value && attr_value.match(/^\w+$/)

  super
end

EDIT

I would not recommend raising an ActiveRecord validation error when assigning not accepted values on initialization. Try to raise your own custom error based on ArgumentError

Outside your class

YourError = Class.new(ArgumentError)

Inside your Class

def initialize(attributes = nil, options = {})
  attr_value = attributes.delete(:value) if attributes
  if attr_value && attr_value.match(/^\w+$/)
    @value = attr_value
  elsif attr_value
    raise YourError.new('Value only accepts words')
  end

  super
end

and then test it like this

describe Myclass do
  it 'should raise an error if value is assigned with something else than a word' do
    lambda{ MyClass.new(:value => 1111)}.should raise_error(YourError)
  end
  it 'should assign the value for words' do
    MyClass.new(:value => 'word').value.should == 'word'
  end
end
KL-7
  • 46,000
  • 9
  • 87
  • 74
Beat Richartz
  • 9,474
  • 1
  • 33
  • 50
  • 1
    Have you tried to define `#initialize` in your models like that? – KL-7 Jul 18 '12 at 13:58
  • 1
    Not enough. `AR::initialize` accepts a hash of attributes that will instantly fail with your `value.match`. The easiest solution is to define attribute accessor and do `Model.new(:value => value)`. Then you don't need to override `#initialize`. – KL-7 Jul 18 '12 at 14:05
  • Ok, defining attr_accessor and assigning via rails assignment is surely prettier, but I think my latest edit should work. Just curious. – Beat Richartz Jul 18 '12 at 14:22
  • Thanks @Beat Richartz but it is still not working... I have edited my question... – gabrielhilal Jul 18 '12 at 14:27
  • @gabrielhilal I think raising a Active Record Validation Error when assigning a volatile attribute is a bit misleading. In this case, I would recommend raising a custom Error inheriting from ArgumentError. I edited my question. – Beat Richartz Jul 18 '12 at 14:31
  • @BeatRichartz looks better now. You can remove arguments from `super` call. And feels like you should do something (raise an exception?) if `value` doesn't match the pattern. Plus, I'd extract matching in a separate method so you an use it both in `initialize` and validation method. – KL-7 Jul 18 '12 at 14:36
  • @KL-7 Thanks for the feedback! I made an edit because the question changed to include raise a custom error based on ArgumentError if the assignment fails. – Beat Richartz Jul 18 '12 at 14:41
  • BeatRichartz and @KL-7 - many thanks for your help and all clarification. Problem solved... – gabrielhilal Jul 18 '12 at 16:28
1

Watch out. the other methods will blow up when @value is nil.

validate :check_value

def check_value
  errors.add(:base,"value is wrong") and return false if !@value || !@value.match(/^\w+$/)
end
drhenner
  • 2,200
  • 18
  • 23
  • +1 for nil check. use `&&` instead of `||` however. this way your `@value.match` expression will not get reached. the correct syntax would be `unless @value && @value.match(/^\w+$/)` – shime Jul 18 '12 at 14:08
0

You should be able to do this by creating your own validator, like so:

class MyClass < ActiveRecord::Base

  validate :value_is_string

  protected

    def value_is_string
      unless @value.match(/^\w+$/)
        self.errors[:base] << 'Please put a string in value'
      end
    end
end
rjz
  • 16,182
  • 3
  • 36
  • 35
0

Validations run before save, so if you want to raise error on assigment you can do this

  def initialize (value)
    raise ActiveRecord::RecordInvalid if !@value || !@value.match(/^\w+$/)
    @value = value
  end

But i don't think that it is a good way to do that

Yuri Barbashov
  • 5,407
  • 1
  • 24
  • 20
0

You just need to call super when initializing. Try this:

    # Trigger validation on initialization.
    def initialize(attributes = {})
      super
      valid?
    end
alextanhongpin
  • 607
  • 1
  • 8
  • 11