[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Reading from file, create a class with variables

Pelle Strul

5/6/2008 9:33:00 AM

Hi, I'm trying to load a file with specifications like:

title :Person
attribute :name, String
attribute :age, Fixnum
constraint :name, 'name != nil'
constraint :name, 'name.size > 0'
constraint :name, 'name =~ /^[A-Z]/'
constraint :age, 'age >= 0'

After which I want to create a class Person, with variables name being a
string, variable age being a fixed number and also the constraints for
them. So far I've been using this guide:
http://www.artima.com/rubycs/articles/ruby_a...

The problem is by using this I only get the variable @title = :Person
@attribute = name, String and @constraint = age, 'age >= 0'. I know why
I'm only getting these variables, but I cant find a way to somehow read
the specification and creating the variables @name being a String, @age
being a Fixnum and these different constraints to be used later on in
for example if-conditions.

Any ideas how these specifications can be read and declared?

FYI: The formatting cant be changed and the names (name, age, Person)
can be different.
--
Posted via http://www.ruby-....

9 Answers

Claes Rembrandt

5/7/2008 8:36:00 AM

0

Pelle Strul wrote:
> Hi, I'm trying to load a file with specifications like:
>
> title :Person
> attribute :name, String
> attribute :age, Fixnum
> constraint :name, 'name != nil'
> constraint :name, 'name.size > 0'
> constraint :name, 'name =~ /^[A-Z]/'
> constraint :age, 'age >= 0'


Im also curious about how this could be solved...
--
Posted via http://www.ruby-....

Daniel Finnie

5/7/2008 4:03:00 PM

0

[Note: parts of this message were removed to make it a legal post.]

Hi,

I think that the Doodle Rubygem might be a good fit for this purpose, unless
this is just an exercise to further your Ruby-fu.

To make sure something is a string, you can use the kind_of? or is_a?
methods. I would use kind_of? in this case (for the differences, check
http://ruby-doc.org/core/classes/Object.ht... ).

Then, you just have to make sure things set with object.name = "ssdfd" are
Strings using define_method:

attr_name = :age
attr_type = Fixnum

klass.class_eval do
define_method "#{attr_name}=" do |arg|
self.instance_variable_set("@#{attr_name}", arg) if arg.type_of?
attr_type
end
end

And something similar for the initialize method.

Dan

On 5/7/08, Claes Rembrandt <kackihamas@hotmail.com> wrote:
>
> Pelle Strul wrote:
> > Hi, I'm trying to load a file with specifications like:
> >
> > title :Person
> > attribute :name, String
> > attribute :age, Fixnum
> > constraint :name, 'name != nil'
> > constraint :name, 'name.size > 0'
> > constraint :name, 'name =~ /^[A-Z]/'
> > constraint :age, 'age >= 0'
>
>
> Im also curious about how this could be solved...
> --
> Posted via http://www.ruby-....
>
>

Jesús Gabriel y Galán

5/7/2008 4:16:00 PM

0

On Tue, May 6, 2008 at 11:33 AM, Pelle Strul <aardtwig@msn.com> wrote:
> Hi, I'm trying to load a file with specifications like:
>
> title :Person
> attribute :name, String
> attribute :age, Fixnum
> constraint :name, 'name != nil'
> constraint :name, 'name.size > 0'
> constraint :name, 'name =~ /^[A-Z]/'
> constraint :age, 'age >= 0'
>
> After which I want to create a class Person, with variables name being a
> string, variable age being a fixed number and also the constraints for
> them.

> Any ideas how these specifications can be read and declared?

This is my first try at solving this problem. It might not be very good,
and I would also like people's comments on my solution, since I would
like to learn how to do these things better:

class ClassGenerator
def initialize
@constraints = {}
@attr_names = []
end

def title the_title
@class_name = the_title
end

def attribute attr, klazz
@attr_names << attr
# First constraint is to check the class
@constraints[attr] = ["#{attr}.is_a? #{klazz}"]
end

def constraint attr, constr
@constraints[attr] << constr
end

def generate data
instance_eval data
klazz = Class.new
klazz.class_eval "attr_reader #{@attr_names.map{|k| ":#{k}"}.join(",")}"
init_params = @attr_names.join(",")
initialize_body = ""
@attr_names.each do |attr|
@constraints[attr].each do |constraint|
initialize_body << "raise ArgumentError.new('#{constraint}')
unless #{constraint};"
end
initialize_body << "@#{attr}=#{attr};"
end
klazz.class_eval "def initialize(#{init_params}); #{initialize_body}; end"
Object.const_set @class_name, klazz
end
end

# The data could be read from a file, obviously
data =<<EOD
title :Person
attribute :name, String
attribute :age, Fixnum
constraint :name, 'name != nil'
constraint :name, 'name.size > 0'
constraint :name, 'name =~ /^[A-Z]/'
constraint :age, 'age >= 0'
EOD

# This should create a Person class, with
# an initialize method with a param for each attribute
# which checks the constraints raising ArgumentError
# if not passed, and assigning to an instance variable
# It also creates attr_readers for the attributes.

ClassGenerator.new.generate data

# So now we can do:
a = Person.new "A", 3
puts a.name
puts a.age

# These should fail with ArgumentError
# The msg of the error contains the constraint
Person.new "A", -3
Person.new "a", 3
Person.new '', 3

Hope this helps and I would appreciate any comment on my code.

Jesus.

Sean O'Halpin

5/8/2008 2:19:00 AM

0

On Wed, May 7, 2008 at 5:03 PM, Daniel Finnie <dan@danfinnie.com> wrote:
> Hi,
>
> I think that the Doodle Rubygem might be a good fit for this purpose

Indeed it is - see the code below. This requires the latest version
0.1.6 (which among other things renames 'attributes' to avoid name
clashes). Classes that define classes are the happiest classes :)

# see ruby-talk:300767
require 'rubygems'
# note: this requires doodle 0.1.6+
require 'doodle'

# set up classes to manage class definitions of form:
# title :Person
# attribute :name, String
# attribute :age, Fixnum
# constraint :name, 'name != nil'
# constraint :name, 'name.size > 1'
module ClassDef
class Attribute < Doodle
has :name, :kind => Symbol
has :kind, :kind => Class
end

class Constraint < Doodle
has :key, :kind => Symbol
has :condition, :kind => String
end

class Definition < Doodle
has :title, :kind => Symbol
has :attributes, :collect => Attribute
has :constraints, :collect => Constraint
# the names don't have to match - you could have this, for example:
# has :validations, :collect => { :constraint => Validation }

# create a new object from a string containing Ruby source for
# an initialization block - this method works with any Doodle
# class
def self.load(str, context = self.to_s + '.load')
begin
new(&eval("proc { #{str} }", binding, context))
rescue SyntaxError, Exception => e
raise e, e.to_s, [caller[-1]]
end
end
end
# this is the core method that defines a class
def self.define(source, namespace = Object, superclass = Doodle)
# read class definition
cd = Definition.load(source, 'example')
# create anonymous class
klass = Class.new(superclass) do
include Doodle::Core if !(superclass <= Doodle)
cd.attributes.each do |attribute|
has attribute.name, :kind => attribute.kind
end
# the constraints as given work as class level constraints in
# Doodle so that's what we're setting up here
cd.constraints.each do |constraint|
must "have " + constraint.condition do
instance_eval(constraint.condition)
end
end
end
# associate anonymous class with constant name
namespace.const_set(cd.title, klass)
# and add factory method/shorthand constructor (if wanted) - has
to happen ~after~ class has a name
klass.class_eval { include Doodle::Factory }
klass
end
end

# demo

source = %[
title :Person
attribute :name, String
attribute :age, Fixnum
constraint :name, 'name != nil'
constraint :name, 'name.size > 1'
constraint :name, 'name =~ /^[A-Z]/'
constraint :age, 'age >= 0'
]

# install class definitions in their own namespace - you don't have to
# do this - this is just to show how
module Outer
module Inner
end
end
ClassDef.define(source, Outer::Inner)

# return value or exception from block
def try(&block)
begin
block.call
rescue Exception => e
e
end
end

module Outer::Inner
# example use of newly defined class (run this file with xmpfilter
to display output)
try { person = Person.new :name => "Arthur", :age => 42 } # =>
#<Outer::Inner::Person:0xb7d3b410 @age=42, @name="Arthur">
try { person = Person.new :name => "arthur", :age => 42 } # =>
#<Doodle::ValidationError: Outer::Inner::Person must have name =~
/^[A-Z]/>
try { person = Person.new :name => "Arthur", :age => -1 } # =>
#<Doodle::ValidationError: Outer::Inner::Person must have age >= 0>
try { person = Person.new :name => "", :age => -1 } # =>
#<Doodle::ValidationError: Outer::Inner::Person must have name.size >
1>
try { person = Person.new :name => nil, :age => -1 } # =>
#<Doodle::ValidationError: Outer::Inner::Person.name must be String -
got NilClass(nil)>
try { person = Person.new :name => "", :age => 42 } # =>
#<Doodle::ValidationError: Outer::Inner::Person must have name.size >
1>
try { person = Person.new :name => nil, :age => 42 } # =>
#<Doodle::ValidationError: Outer::Inner::Person.name must be String -
got NilClass(nil)>
try { person = Person.new :name => "Arthur", :age => "1" } # =>
#<Doodle::ValidationError: Outer::Inner::Person.age must be Fixnum -
got String("1")>
# or using Doodle postitional args
try { person = Person.new("Arthur", 42) } # =>
#<Outer::Inner::Person:0xb7d2ae6c @age=42, @name="Arthur">
# and shorthand constructor
try { person = Person("Arthur", 42) } # =>
#<Outer::Inner::Person:0xb7d2893c @age=42, @name="Arthur">
try { person = Person(:name => "Arthur", :age => 42) } # =>
#<Outer::Inner::Person:0xb7d263f8 @age=42, @name="Arthur">
end
require 'yaml'
try { Outer::Inner::Person.new(:name => "Arthur", :age => 42).to_yaml
} # => "--- !ruby/object:Outer::Inner::Person \nage: 42\nname:
Arthur\n"


Apologies if this looks a mess - you may have to edit line breaks to
get the code to work.

Regards,
Sean

Sean O'Halpin

5/8/2008 2:27:00 AM

0

It's not evident in the example I gave in my last post that Doodle
validations work on the attributes themselves, not just in
initialization, e.g.

person = Outer::Inner::Person.new(:name => "Arthur", :age => 42)
try { person.age = "42" } # => #<Doodle::ValidationError:
Outer::Inner::Person.age must be Fixnum - got String("42")>

Regards,
Sean

ara.t.howard

5/8/2008 5:37:00 AM

0


On May 6, 2008, at 3:33 AM, Pelle Strul wrote:
> Hi, I'm trying to load a file with specifications like:
>
> title :Person
> attribute :name, String
> attribute :age, Fixnum
> constraint :name, 'name != nil'
> constraint :name, 'name.size > 0'
> constraint :name, 'name =~ /^[A-Z]/'
> constraint :age, 'age >= 0'

i'd do something like



cfp:~ > cat a.rb
class Specd
alias_method '__eval__', 'instance_eval'

instance_methods.each{|m| undef_method m unless m[%r/__/]}

def Specd.build config
c = Class.new
new(c).__eval__(config)
Object.send :const_set, c.title, c
Object.send :const_get, c.title
end

def initialize c
@class = c
@singleton_class =
class << @class
self
end
end

def title name
@class.module_eval{ @title = name }
@singleton_class.module_eval{ attr_accessor :title }
end

def attribute name, type
constraint name, type
end

def constraint key, constraint
@class.module_eval do
key = key.to_s

( ( @@constraints ||= Hash.new )[key] ||= [] ).push( constraint )

reader, writer, ivar = "#{ key }", "#{ key }=", "@#{ key }"

unless instance_methods(false).include?(key)
attr reader

define_method(writer) do |value|
previous = instance_variable_get ivar
begin
instance_variable_set ivar, value
@@constraints[key].each do |constr|
ok =
case constr
when Module
constr === value
when String
instance_eval(constr)
else
true
end
raise ArgumentError, "#{ value.inspect } [#{ constr }]"
unless ok
end
rescue Object => e
instance_variable_set ivar, previous
raise
end
end
end
end
end
end


Specd.build(
<<-text
title :Person
constraint :name, 'name =~ /^Z/'
attribute :name, String
attribute :age, Fixnum
constraint :name, 'name != nil'
constraint :name, 'name.size > 0'
constraint :age, 'age >= 0'
text
)

person = Person.new
person.name = 'Zaphod'
person.age = 42

p person

begin
person.name = 'lowercase'
rescue
puts $!.message
end

begin
person.age = -42
rescue
puts $!.message
end


cfp:~ > ruby a.rb
#<Person:0x24db0 @age=42, @name="Zaphod">
"lowercase" [name =~ /^Z/]
-42 [age >= 0]





a @ http://codeforp...
--
we can deny everything, except that we have the possibility of being
better. simply reflect on that.
h.h. the 14th dalai lama




Sean O'Halpin

5/10/2008 4:22:00 PM

0

Always a pleasure reading your code. One question: why are you
aliasing instance_eval?

Regards,
Sean

ara.t.howard

5/10/2008 4:45:00 PM

0


On May 10, 2008, at 10:21 AM, Sean O'Halpin wrote:
> Always a pleasure reading your code. One question: why are you
> aliasing instance_eval?
>
> Regards,
> Sean
>



hmmm. well this:


class Specd
alias_method '__eval__', 'instance_eval'

instance_methods.each{|m| undef_method m unless m[%r/__/]}

def Specd.build config
c = Class.new
new(c).__eval__(config)
Object.send :const_set, c.title, c
Object.send :const_get, c.title
end

...


basically says

- keep a handle on instance_eval

- blow away all public methods

- build Specd objects by creating a class (which has only a few
instance methods like 'attribute' and 'constraint') and evaluating the
config definition in there.

so i needed the alias to be able to do the instance eval.


the point of blowing away all the methods in spec'd is so i can
intercept any DSL-ish methods and apply them to the class i'm
building. using this sort of DSL wrapper allows me to build up a
class with a dsl without littering the class itself with useless DSL
crap. for instance

class Model

has_many :foos

end

relies on Model having a has_many method. this is sometimes not
desirable as it may require, for instance, inheriting from some
abstract base type. with the 'dsl as wrapper approach' one can do

Model = DSL.build do

has_many :foos

end

and Model can be a totally 'normal' class - all the special DSL-y
goodness on how to build up stuff is contained in the DSL object,
which has a @model instance and all methods stripped except the dsl
ones.


this code works very similarly

http://codeforp...lib/ruby/configuration/configuration-0.0.5/lib/config...

it's more complex for sure but the usage should make it clear enough

http://codeforp...lib/ruby/configuration/configuration-0....


cheers.



a @ http://codeforp...
--
we can deny everything, except that we have the possibility of being
better. simply reflect on that.
h.h. the 14th dalai lama




ara.t.howard

5/10/2008 9:02:00 PM

0


On May 10, 2008, at 12:29 PM, Sean O'Halpin wrote:
> DSL(Foo) do
> has :name
> end
>
> foo = Foo.new
> foo.name = 'Trillian'
^^^^^^
heh

>
> foo.name # => "Trillian"
> p Foo.methods - Object.methods
> # >> []
>
> which is basically the same approach as yours I believe.

yup, exactly. it's so liberating to have clean slate for the dsl -
this is my current preferred approach.

cheers.

a @ http://codeforp...
--
we can deny everything, except that we have the possibility of being
better. simply reflect on that.
h.h. the 14th dalai lama