"Reflecting" on my self.n00b

I'm admittedly quite a novice with programming. I'm sort of playing
around with some ideas, and I'm wondering if what I'm doing is
completely stupid, and if not, is there a better way to do it? Also,
is there any way that I could do some type of type-ish validation on
the data?

Basically, I want to provide what I suppose would be abstract class
(?), which someone else could extend and add attributes if they'd
like. I'd like to be able to look into the class and pick out the
attributes to populate (which is the reason for all of the sp_set &
sp_get stuff). Basically, my thought is to be able to provide a web
interface at some point to populate the needed data of a class, and
then maybe Marshal.dump the object to a database and call it later
using that data as config data essentially. I'm sure I could learn a
ton about this from reading Rails code, but there's so much of it that
I don't know where to look. Output and code follows. Please feel
free to tell me if this is dumb :O)

Output:
Enter Username: name
Enter Url: url
Enter Extra: more
Enter Password: pass
Enter Owner: own
Enter Certificate: cert
name
url
more
pass
own
cert

Code:
<code>
class ServiceProfile
    def initialize()
    end

    def sp_set_url(url)
        @url = url
    end

    def sp_get_url()
        @url
    end

    def sp_set_username(username)
        @username = username
    end

    def sp_get_username()
        @username
    end

    def sp_set_password(password)
        @password = password
    end

    def sp_get_password()
        @password
    end

    def sp_set_certificate(certificate)
        @certificate = certificate
    end

    def sp_get_certificate()
        @certificate
    end

    def sp_set_owner(owner)
        @owner = owner
    end

    def sp_get_owner()
        @owner
    end

    def load_certificate()
        @certificate.read
    end

    def call_service(&block)
    end
end

class MyService < ServiceProfile
    def sp_set_extra(extra)
        @extra = extra
    end

    def sp_get_extra()
        @extra
    end
end

svc_profile = MyService.new

set_re = Regexp.new('^sp_set_(\w+)')
get_re = Regexp.new('^sp_get_(\w+)')

svc_profile.methods.sort.reverse.each { |meth|
    if meth.match(set_re)
        print "Enter #{Regexp.last_match(1).capitalize}: "
        value = $stdin.gets.chomp
        svc_profile.send(meth, value)
    end
}

svc_profile.methods.sort.reverse.each { |meth|
    if meth.match(get_re)
        puts svc_profile.send(meth)
    end
}
</code>

here are a few suggestions:

instead of:

    def sp_set_url(url)
        @url = url
    end

    def sp_get_url()
        @url
    end

    def sp_set_username(username)
        @username = username
    end

    def sp_get_username()
        @username
    end

    def sp_set_password(password)
        @password = password
    end

    def sp_get_password()
        @password
    end

    def sp_set_certificate(certificate)
        @certificate = certificate
    end

    def sp_get_certificate()
        @certificate
    end

    def sp_set_owner(owner)
        @owner = owner
    end

    def sp_get_owner()
        @owner
    end

···

--
try:
--
   def initialize()
      @url, @username, @password, @certificate, @owner = nil
   end
   attr_accessor :url, :username, :password, :certificate, :owner

You might also store these values in a Hash or use OpenStruct

For the validation part, you might check out HighLine:

http://highline.rubyforge.org

You can save yourself some RSI by using attr_accessor.

class ServiceProfile
  attr_accessor :username, :url, :password, :owner, :certificate
  def load_certificate()
    @certificate.read
  end
  def call
    yield self
  end
  def each_attribute(&block)
    (methods - Object.instance_methods).each &block
  end
end
class MyService < ServiceProfile
  attr_accessor :extra
end

svc_profile = MyService.new
svc_profile.each_attribute do |meth|
  print "Enter #{meth}: "
  svc_profile.send(meth, gets.chomp)
end
svc_profile.each_attribute {|meth| puts svc_profile.send(meth) }

If you want to use validation, too, you can easily write a method like
attr_accessor. See Dwemthy's Array for how.
http://poignantguide.net/dwemthy/

A type-checking attr_accessor-ish method might look something like
this:

def ServiceProfile.typed_attr(hash)
  attr_reader *hash.keys
  hash.each_pair do |attr, klass|
    define_method("#{attr}=") do |value|
      raise TypeError, "not a #{klass}: #{value.inspect}" unless
value.kind_of?(klass)
      instance_variable_set "@#{attr}", value
    end
  end
end

Cheers,
Dave

i think what you want is traits:

   harp:~ > cat a.rb
   require 'traits'
   require 'readline'

   class ServiceProfile
     trait 'url', 'type' => String, 'case' => %r|^http://|
     trait 'username', 'type' => String
     trait 'password', 'type' => String
     trait 'certificate', 'munge' => 'to_i', 'case' => Fixnum
     trait 'owner', 'type' => String

     def inspect
       "#{ self.class }( " << klass.reader_traits.map{|t| "#{ t }:#{ send(t).inspect}" }.join(', ') << " )"
     end
     def input io
       klass::writer_traits.each{|t| send t, Readline::readline("#{ t } ")}
     end
     def klass
       self.class
     end
   end

   class MyService < ServiceProfile
     trait 'extra', 'munge' => 'to_f', 'ducktypes' => %w( floor ceil )
   end

   my_service = MyService::new
   p my_service

   my_service.input STDIN
   p my_service

   harp:~ > ruby a.rb
   MyService( extra:nil, certificate:nil, owner:nil, password:nil, url:nil, username:nil )
   extra= 42.0
   certificate= 42abcd
   owner= me
   password= tru5t
   url= Index of /lib/ruby/traits
   username= ahoward
   MyService( extra:42.0, certificate:42, owner:"me", password:"tru5t", url:"http://codeforpeople.com/lib/ruby/traits/&quot;, username:"ahoward" )

regards.

-a

···

On Fri, 28 Oct 2005, swille wrote:

I'm admittedly quite a novice with programming. I'm sort of playing around
with some ideas, and I'm wondering if what I'm doing is completely stupid,
and if not, is there a better way to do it? Also, is there any way that I
could do some type of type-ish validation on the data?

Basically, I want to provide what I suppose would be abstract class (?),
which someone else could extend and add attributes if they'd like. I'd like
to be able to look into the class and pick out the attributes to populate
(which is the reason for all of the sp_set & sp_get stuff). Basically, my
thought is to be able to provide a web interface at some point to populate
the needed data of a class, and then maybe Marshal.dump the object to a
database and call it later using that data as config data essentially. I'm
sure I could learn a ton about this from reading Rails code, but there's so
much of it that I don't know where to look. Output and code follows.
Please feel free to tell me if this is dumb :O)

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
anything that contradicts experience and logic should be abandoned.
-- h.h. the 14th dalai lama

===============================================================================

For a little more clarification on usage:

irb(main):027:0> class Foo
irb(main):028:1> def initialize()
irb(main):029:2> @url, @username, @password, @certificate, @owner = nil
irb(main):030:2> end
irb(main):031:1> attr_accessor :url, :username, :password,
:certificate, :owner
irb(main):032:1> end
=> nil
irb(main):033:0> a = Foo.new
=> #<Foo:0xb7d3715c @certificate=nil, @password=nil, @username=nil,
@owner=nil, @url=nil>
irb(main):034:0> a.url = "http://newhavenrubyists.org"
=> "http://newhavenrubyists.org"

and if you need to extend something

irb(main):036:0> class Bar < Foo
irb(main):037:1> end
=> nil
irb(main):038:0> b = Bar.new
=> #<Bar:0xb7d82634 @certificate=nil, @password=nil, @username=nil,
@owner=nil, @url=nil>
irb(main):039:0> b.url = "http://stonecode.org/blog"
=> "http://stonecode.org/blog"

It seems that if I use attr_accessor, I can't use
instance_variables(). Is that the case? If so, how could I peruse
the instance variables to find the ones I need?

Sorry for replying to my own thread so much :O)

···

On 10/27/05, dave.burt@gmail.com <dave.burt@gmail.com> wrote:

You can save yourself some RSI by using attr_accessor.

class ServiceProfile
  attr_accessor :username, :url, :password, :owner, :certificate
  def load_certificate()
    @certificate.read
  end
  def call
    yield self
  end
  def each_attribute(&block)
    (methods - Object.instance_methods).each &block
  end
end

Wow, that's really nice. All of these suggestions are great. I
definitely needed the guidance. I'm glad David posted. He really
made me realize how much I was overthinking this. For some reason, I
got stuck on some type of reflection for this. I really like this
traits thing though, and I'd like to explore that a bit. Where can I
get that from? I might have to study the source on that one.

···

On 10/28/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

On Fri, 28 Oct 2005, swille wrote:

> I'm admittedly quite a novice with programming. I'm sort of playing around
> with some ideas, and I'm wondering if what I'm doing is completely stupid,
> and if not, is there a better way to do it? Also, is there any way that I
> could do some type of type-ish validation on the data?
>
> Basically, I want to provide what I suppose would be abstract class (?),
> which someone else could extend and add attributes if they'd like. I'd like
> to be able to look into the class and pick out the attributes to populate
> (which is the reason for all of the sp_set & sp_get stuff). Basically, my
> thought is to be able to provide a web interface at some point to populate
> the needed data of a class, and then maybe Marshal.dump the object to a
> database and call it later using that data as config data essentially. I'm
> sure I could learn a ton about this from reading Rails code, but there's so
> much of it that I don't know where to look. Output and code follows.
> Please feel free to tell me if this is dumb :O)

i think what you want is traits:

   harp:~ > cat a.rb
   require 'traits'
   require 'readline'

   class ServiceProfile
     trait 'url', 'type' => String, 'case' => %r|^http://|
     trait 'username', 'type' => String
     trait 'password', 'type' => String
     trait 'certificate', 'munge' => 'to_i', 'case' => Fixnum
     trait 'owner', 'type' => String

     def inspect
       "#{ self.class }( " << klass.reader_traits.map{|t| "#{ t }:#{ send(t).inspect}" }.join(', ') << " )"
     end
     def input io
       klass::writer_traits.each{|t| send t, Readline::readline("#{ t } ")}
     end
     def klass
       self.class
     end
   end

   class MyService < ServiceProfile
     trait 'extra', 'munge' => 'to_f', 'ducktypes' => %w( floor ceil )
   end

   my_service = MyService::new
   p my_service

   my_service.input STDIN
   p my_service

   harp:~ > ruby a.rb
   MyService( extra:nil, certificate:nil, owner:nil, password:nil, url:nil, username:nil )
   extra= 42.0
   certificate= 42abcd
   owner= me
   password= tru5t
   url= http://codeforpeople.com/lib/ruby/traits/
   username= ahoward
   MyService( extra:42.0, certificate:42, owner:"me", password:"tru5t", url:"http://codeforpeople.com/lib/ruby/traits/&quot;, username:"ahoward" )

regards.

-a
--

> email :: ara [dot] t [dot] howard [at] noaa [dot] gov
> phone :: 303.497.6469
> anything that contradicts experience and logic should be abandoned.
> -- h.h. the 14th dalai lama

How would I programatically find which instance variables I'm
concerned with? The naming convention and the methods() call was my
(probably weak) way of being able to find what needed to be populated.
I'm not aware of how to search for instance variables. Can I?

···

On 10/27/05, Gregory Brown <gregory.t.brown@gmail.com> wrote:

For a little more clarification on usage:

irb(main):027:0> class Foo
irb(main):028:1> def initialize()
irb(main):029:2> @url, @username, @password, @certificate, @owner = nil
irb(main):030:2> end
irb(main):031:1> attr_accessor :url, :username, :password,
:certificate, :owner
irb(main):032:1> end
=> nil
irb(main):033:0> a = Foo.new
=> #<Foo:0xb7d3715c @certificate=nil, @password=nil, @username=nil,
@owner=nil, @url=nil>
irb(main):034:0> a.url = "http://newhavenrubyists.org"
=> "http://newhavenrubyists.org"

and if you need to extend something

irb(main):036:0> class Bar < Foo
irb(main):037:1> end
=> nil
irb(main):038:0> b = Bar.new
=> #<Bar:0xb7d82634 @certificate=nil, @password=nil, @username=nil,
@owner=nil, @url=nil>
irb(main):039:0> b.url = "http://stonecode.org/blog&quot;
=> "http://stonecode.org/blog&quot;

This seems to work great, thanks! I swear, this is the first way that
I thought of, but somehow I missed all of the instance_variable calls.
Mental note, look harder. :O)

Still though, I'm unsure of whether this is a smart thing to do. Any
thoughts on that? I'd definitely like to learn the right way to do
stuff.

class ServiceProfile
    def initialize
        @sp_url = nil
        @sp_username = nil
        @sp_password = nil
        @sp_certificate = nil
        @sp_owner = nil
    end

    def load_certificate
        @sp_certificate.read
    end

    def call_service(&block)
    end
end

class MyService < ServiceProfile
    def initialize
        @sp_extra = nil
        super
    end
end

svc_profile = MyService.new #ServiceProfile.new
sp_re = Regexp.new('^@sp_(\w+)')

svc_profile.instance_variables.sort.reverse.each { |var|
    if var.match(sp_re)
        print "Enter #{Regexp.last_match(1).capitalize}: "
        value = $stdin.gets.chomp
        svc_profile.instance_variable_set(var, value)
    end
}

svc_profile.instance_variables.sort.reverse.each { |var|
    if var.match(sp_re)
        puts svc_profile.instance_variable_get(var)
    end
}

···

On 10/27/05, Gregory Brown <gregory.t.brown@gmail.com> wrote:

For a little more clarification on usage:

irb(main):027:0> class Foo
irb(main):028:1> def initialize()
irb(main):029:2> @url, @username, @password, @certificate, @owner = nil
irb(main):030:2> end
irb(main):031:1> attr_accessor :url, :username, :password,
:certificate, :owner
irb(main):032:1> end
=> nil
irb(main):033:0> a = Foo.new
=> #<Foo:0xb7d3715c @certificate=nil, @password=nil, @username=nil,
@owner=nil, @url=nil>
irb(main):034:0> a.url = "http://newhavenrubyists.org"
=> "http://newhavenrubyists.org"

and if you need to extend something

irb(main):036:0> class Bar < Foo
irb(main):037:1> end
=> nil
irb(main):038:0> b = Bar.new
=> #<Bar:0xb7d82634 @certificate=nil, @password=nil, @username=nil,
@owner=nil, @url=nil>
irb(main):039:0> b.url = "http://stonecode.org/blog&quot;
=> "http://stonecode.org/blog&quot;

It seems that if I use attr_accessor, I can't use
instance_variables(). Is that the case?

No, it's not - attr_accessor defines methods.

... how could I peruse
the instance variables to find the ones I need?

I added the method "each_attribute" in my previous post - look at that,
and how it's used.

You can also get a list by using something like:
MyService.instance_methods - Object.instance_methods

Sorry for replying to my own thread so much :O)

You should keep replying until your question is answered.

Cheers,
Dave

How would I programatically find which instance variables I'm
concerned with? The naming convention and the methods() call was my
(probably weak) way of being able to find what needed to be populated.
I'm not aware of how to search for instance variables. Can I?

Doh! Nevermind, I just saw instance_variables() in ri. I didn't see
that one before. Thanks!

Hi --

Still though, I'm unsure of whether this is a smart thing to do. Any
thoughts on that? I'd definitely like to learn the right way to do
stuff.

class ServiceProfile
   def initialize
       @sp_url = nil
       @sp_username = nil
       @sp_password = nil
       @sp_certificate = nil
       @sp_owner = nil
   end

That looks kind of odd. Instance variables default to nil anyway. If
you're just trying to trigger them into existence, then I think the
program design is probably suspect.

   def load_certificate
       @sp_certificate.read
   end

   def call_service(&block)
   end

What's that method for? (It's not actually doing anything.)

end

class MyService < ServiceProfile
   def initialize
       @sp_extra = nil
       super
   end
end

svc_profile = MyService.new #ServiceProfile.new
sp_re = Regexp.new('^@sp_(\w+)')

svc_profile.instance_variables.sort.reverse.each { |var|
   if var.match(sp_re)
       print "Enter #{Regexp.last_match(1).capitalize}: "
       value = $stdin.gets.chomp
       svc_profile.instance_variable_set(var, value)
   end
}

svc_profile.instance_variables.sort.reverse.each { |var|
   if var.match(sp_re)
       puts svc_profile.instance_variable_get(var)
   end
}

The thing with the instance variable names matching a regex looks very
fragile. I would recommend using real data structures. You could do
something like this:

   class ServiceProfile
     SP_ITEMS = %w{url username password certificate owner }

     def sp_items
       self.class::SP_ITEMS
     end

     def initialize
       @sp_hash = {}
     end

     def save_service(service,value)
       @sp_hash[service] = value
     end
   end

   class MyService < ServiceProfile
     SP_ITEMS = SP_ITEMS.dup
     SP_ITEMS << "extra"
   end

And then you could just use the data structures to get and save your
data:

   svc_profile = MyService.new
   svc_profile.sp_items.each do |item|
     print "Enter #{item.capitalize}: "
     value = gets.chomp
     svc_profile.save_service(item,value)
   end

David

···

On Fri, 28 Oct 2005, swille wrote:

--
David A. Black
dblack@wobblini.net