Reputation: 39
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 !!!
Upvotes: 2
Views: 1551
Reputation: 9639
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
Upvotes: 4
Reputation: 369458
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'
Upvotes: 3