2

I'm trying to Implementation of custom attribute accessors with validation.

Let say attr_validated. Now this attr_validated

1: Should have same setter and getter methods as with attr_accessor. ## this part is done.

2: It Should validate the given block.

attr_validated :num_legs do |v|
v <= 4
end

This question might be look like any other question but its not. While googled i got

1: Ist Part

class Class
     def attr_validated(*args)
    args.each do |arg|
      # getter
      self.class_eval("def #{arg};@#{arg};end")
      # setter
      self.class_eval("def #{arg}=(val);@#{arg}=val;end")
    end
  end
end

class Dog
  attr_validated :num_legs ## Instead of this i need to validate a block also attr_validated :num_legs do |v|
v <= 4
end

dog = Dog.new
p dog.num_legs
p dog.num_legs = 'Stack'

2: How might we can Implement second part.

Any help would be greatly appreciated !!!

Community
  • 1
  • 1
Tyshawn
  • 39
  • 4

2 Answers2

4

How about something like this:

class Class
  def attr_validated(*args, &validator)
    args.each do |name|
      define_method("#{name}=") do |value|
        if block_given?
          raise ArgumentError, "Value '#{value}' is invalid" unless validator.call(value)
        end

        instance_variable_set("@#{name}", value)
      end

      define_method(name) do
        instance_variable_get("@#{name}")
      end
    end
  end
end

class Person
  attr_validated(:name, :occupation) { |name| name.length > 3 }
end

p1 = Person.new
p1.name = "John The Tester"
p1.occupation = "Software developer"
p "#{p1.name} - #{p1.occupation}"

p2 = Person.new
p2.name = "test"
p2.occupation = "Tester"
p "#{p2.name} - #{p2.occupation}"

Which would generate output like:

"John The Tester - Software developer"
app.rb:6:in `block (2 levels) in attr_validated': Value 'test' is invalid (ArgumentError)
        from app.rb:28:in `<main>'

Hope that helps!

Good luck!

UPDATE

You can add another method, that will apply validation for first argument like this:

class Class
  def attr_validated_first(*args, &validator)
    args.each_with_index do |name, index|
      define_method("#{name}=") do |value|
        if block_given? && index == 0
          raise ArgumentError, "Value '#{value}' is invalid" unless validator.call(value)
        end

        instance_variable_set("@#{name}", value)
      end

      define_method(name) do
        instance_variable_get("@#{name}")
      end
    end
  end
end

However I wouldn't recommend this approach, which would be confusing! if you want to register couple attributes with different validation rules... You can use attr_validated from first example multiple times, like this:

class Person
  attr_validated(:name)       { |name| name.length > 3 }
  attr_validated(:occupation) { |occupation| occupation == "Ruby Developer" }
end
Paweł Dawczak
  • 9,519
  • 2
  • 24
  • 37
  • Many many Thanks @Paweł Dawczak ..It solve my problem . Still i have one thing, the block condition will apply each and every attribute ..so can we do for only first parametre .. – Gupta May 08 '15 at 07:48
  • @Vinay Normally methods like this apply the block condition to all items, not just the first, at least dictated by the conventions of Rails. If you need to apply them more selectively, make more than one `attr_validated` call, one with a block, one without. You might also want to adopt a pattern like: `attr_validated :example, with: -> { |v| v.length > 6 }` where it's passed in as a lambda. – tadman May 08 '15 at 08:16
3

That's easy. Just raise an ArgumentError in the setter if the block says the argument is invalid:

class Person
  attr_reader :name 

  def name=(name)
    raise ArgumentError, "'#{name}' is not a valid name!" if rejector.(name)
    @name = name
  end

  private

  attr_accessor :rejector

  def initialize(&rejector)
    self.rejector = rejector
  end
end

artist = Person.new do |person|
  person.length > 6
end

artist.name = 'The Artist Formerly Known As Prince'
# ArgumentError: 'The Artist Formerly Known As Prince' is not a valid name!
artist.name = 'ruby'
# => 'ruby'
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Could you please explain ..what self.rejector = rejector doing here ..As i know rejector is a proc object and self is person object..then what is going under the hood .. – Tyshawn May 04 '15 at 11:53
  • As I changed the question .And Thanks for your previous answer . Could you please help to sort out this problem . Thanks in advance – Tyshawn May 06 '15 at 18:33