Copying parameters to singleton class

L

Lars Olsson

Hi list!

I have a metaprogramming question that is driving me mad. I though I
understood how to do it, but obviously I didn't. This is what I want
to do:

I want to create a class method that takes a bunch of options and
returns a singleton class with those options set,

class Opportunities
def self.using(options)
# Store options in singleton class variable @options and then
return singleton class
end
def self.options
return @options
end
end

So that I can use:

foo = Opportunities.using({:eek:ne => 1, :two: => 2, :three => 3})
bar = Opportunities.using({:four => 4, :five: => 5, :six => 6})

and then

foo.options => {:eek:ne => 1, :two: => 2, :three => 3}
bar.options => {:four => 4, :five: => 5, :six => 6}

Please note that I don't want instances of the Opportunities class, I
want two separate classes that shares the same behavior except for
that they return different values for the Opportunities.options call.

This really should be possible with ruby, right?

/lasso
 
J

Jesús Gabriel y Galán

Hi list!

I have a metaprogramming question that is driving me mad. I though I
understood how to do it, but obviously I didn't. This is what I want
to do:

I want to create a class method that takes a bunch of options and
returns a singleton class with those options set,

class Opportunities
=A0def self.using(options)
=A0 =A0# Store options in singleton class variable @options and then
return singleton class
=A0end
=A0def self.options
=A0 =A0return @options
=A0end
end

So that I can use:

foo =3D Opportunities.using({:eek:ne =3D> 1, :two: =3D> 2, :three =3D> 3})
bar =3D Opportunities.using({:four =3D> 4, :five: =3D> 5, :six =3D> 6})

and then

foo.options =3D> {:eek:ne =3D> 1, :two: =3D> 2, :three =3D> 3}
bar.options =3D> {:four =3D> 4, :five: =3D> 5, :six =3D> 6}

Please note that I don't want instances of the Opportunities class, I
want two separate classes that shares the same behavior except for
that they return different values for the Opportunities.options call.

If you don't want instances of Opportunities, of which class you want insta=
nces?
Where is the common behaviour of those objects defined?
This really should be possible with ruby, right?

I'm not sure if I understand what you want, can you tell us why this
doesn't fit your requirements:

ruby-1.8.7-p334 :005 > class Opportunities
ruby-1.8.7-p334 :006?> attr_reader :eek:ptions
ruby-1.8.7-p334 :007?> def initialize options
ruby-1.8.7-p334 :008?> @options =3D options
ruby-1.8.7-p334 :009?> end
ruby-1.8.7-p334 :010?> def self.using options
ruby-1.8.7-p334 :011?> new(options)
ruby-1.8.7-p334 :012?> end
ruby-1.8.7-p334 :013?> end
=3D> nil
ruby-1.8.7-p334 :014 > foo =3D Opportunities.using({:a =3D> 3, :b =3D> 4})
=3D> #<Opportunities:0xb7496418 @options=3D{:b=3D>4, :a=3D>3}>
ruby-1.8.7-p334 :015 > foo.options
=3D> {:b=3D>4, :a=3D>3}
ruby-1.8.7-p334 :016 > bar =3D Opportunities.using({:a =3D> 5, :b =3D> 10})
=3D> #<Opportunities:0xb748e22c @options=3D{:b=3D>10, :a=3D>5}>
ruby-1.8.7-p334 :017 > bar.options
=3D> {:b=3D>10, :a=3D>5}

Jesus.
 
L

Lars Olsson

If you don't want instances of Opportunities, of which class you want instances?
Where is the common behaviour of those objects defined?


I'm not sure if I understand what you want, can you tell us why this
doesn't fit your requirements:

ruby-1.8.7-p334 :005 > class Opportunities
ruby-1.8.7-p334 :006?>   attr_reader :eek:ptions
ruby-1.8.7-p334 :007?>   def initialize options
ruby-1.8.7-p334 :008?>     @options = options
ruby-1.8.7-p334 :009?>     end
ruby-1.8.7-p334 :010?>   def self.using options
ruby-1.8.7-p334 :011?>     new(options)
ruby-1.8.7-p334 :012?>     end
ruby-1.8.7-p334 :013?>   end
 => nil
ruby-1.8.7-p334 :014 > foo = Opportunities.using({:a => 3, :b => 4})
 => #<Opportunities:0xb7496418 @options={:b=>4, :a=>3}>
ruby-1.8.7-p334 :015 > foo.options
 => {:b=>4, :a=>3}
ruby-1.8.7-p334 :016 > bar = Opportunities.using({:a => 5, :b => 10})
 => #<Opportunities:0xb748e22c @options={:b=>10, :a=>5}>
ruby-1.8.7-p334 :017 > bar.options
 => {:b=>10, :a=>5}

Jesus.

Actually, I don't want any instances at all. I would have preferred to
use instances myself, but since my task involves calling a framework
method that takes a class and not an "ordinary" instance I needed a
hack to be override the options method. Robert's solution does exactly
what I need.

Thank you for your speedy answers!

/lasso
 
7

7stud --

...but then you could do this:

module ClassFactory
def ClassFactory.included(includer)
includer.extend(ClassMethods)
end

module ClassMethods

def using(hash)
Class.new do
@options = hash
class << self
attr_reader :eek:ptions
end
end
end

end
end

class Opportunities
include ClassFactory
end

Foo = Opportunities.using({one: 1, two: 2, three: 3})
Bar = Opportunities.using({four: 4, five: 5, six: 6})

p Foo.options
p Bar.options

--output:--
{:eek:ne=>1, :two=>2, :three=>3}
{:four=>4, :five=>5, :six=>6}


I really wanted to write that like below, but it doesn't work??

module ClassFactory
def ClassFactory.included(includer)
includer.extend(ClassMethods)
end

module ClassMethods

def using(hash)
Class.new do
@options = hash
end
end

def options
@options
end

end
end

class Opportunities
include ClassFactory
end

Foo = Opportunities.using({one: 1, two: 2, three: 3})
Bar = Opportunities.using({four: 4, five: 5, six: 6})

p Foo.options
p Bar.options

--output:--
prog.rb:28:in `<main>': undefined method `options' for Foo:Class
(NoMethodError)
 
R

Robert Klemme

...but then you could do this:

module ClassFactory
=A0def ClassFactory.included(includer)
=A0 =A0includer.extend(ClassMethods)
=A0end

=A0module ClassMethods

=A0 =A0def using(hash)
=A0 =A0 =A0Class.new do
=A0 =A0 =A0 =A0@options =3D hash
=A0 =A0 =A0 =A0class << self
=A0 =A0 =A0 =A0 =A0attr_reader :eek:ptions
=A0 =A0 =A0 =A0end
=A0 =A0 =A0end
=A0 =A0end

=A0end
end

class Opportunities
=A0include ClassFactory
end

I think we can do this simpler if you want to reuse that options
creation functionality:

module ClassFactory
attr_reader :eek:ptions

def using(opts)
Class.new(self).tap do |cl|
cl.instance_variable_set '@options', opts.freeze
end
end
end

class Opportunities
extend ClassFactory
end

irb(main):020:0> foo =3D Opportunities.using(foo: 1, bar: 2)
=3D> #<Class:0x10952bc0>
irb(main):021:0> foo.options
=3D> {:foo=3D>1, :bar=3D>2}
irb(main):022:0> foo.ancestors
I really wanted to write that like below, but it doesn't work??

module ClassFactory
=A0def ClassFactory.included(includer)
=A0 =A0includer.extend(ClassMethods)
=A0end

=A0module ClassMethods

=A0 =A0def using(hash)
=A0 =A0 =A0Class.new do
=A0 =A0 =A0 =A0@options =3D hash
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0def options
=A0 =A0 =A0@options
=A0 =A0end

=A0end
end

class Opportunities
=A0include ClassFactory
end

Foo =3D Opportunities.using({one: 1, two: 2, three: 3})
Bar =3D Opportunities.using({four: 4, five: 5, six: 6})

p Foo.options
p Bar.options

--output:--
prog.rb:28:in `<main>': undefined method `options' for Foo:Class
(NoMethodError)

Your new class does not inherit module ClassMethods, that's why.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
L

Lars Olsson

...but then you could do this:
module ClassFactory
 def ClassFactory.included(includer)
   includer.extend(ClassMethods)
 end
 module ClassMethods
   def using(hash)
     Class.new do
       @options = hash
       class << self
         attr_reader :eek:ptions
       end
     end
   end

class Opportunities
 include ClassFactory
end

I think we can do this simpler if you want to reuse that options
creation functionality:

module ClassFactory
  attr_reader :eek:ptions

  def using(opts)
    Class.new(self).tap do |cl|
      cl.instance_variable_set '@options', opts.freeze
    end
  end
end

class Opportunities
  extend ClassFactory
end

irb(main):020:0> foo = Opportunities.using(foo: 1, bar: 2)
=> #<Class:0x10952bc0>
irb(main):021:0> foo.options
=> {:foo=>1, :bar=>2}
irb(main):022:0> foo.ancestors
Opportunities said:
I really wanted to write that like below, but it doesn't work??
module ClassFactory
 def ClassFactory.included(includer)
   includer.extend(ClassMethods)
 end
 module ClassMethods
   def using(hash)
     Class.new do
       @options = hash
     end
   end
   def options
     @options
   end

class Opportunities
 include ClassFactory
end
Foo = Opportunities.using({one: 1, two: 2, three: 3})
Bar = Opportunities.using({four: 4, five: 5, six: 6})
p Foo.options
p Bar.options
--output:--
prog.rb:28:in `<main>': undefined method `options' for Foo:Class
(NoMethodError)

Your new class does not inherit module ClassMethods, that's why.

Kind regards

robert

Hello again!

I've now incorporated your solution into my code and it works very
well. I still have a small question though. What's the reason for
freezing the options? I can see no obvious explanation for it except
that one might not want to change them by accident. Or is there
another reason for this?

/lasso
 
R

Robert Klemme

I've now incorporated your solution into my code and it works very
well.

Fine! (Btw, which one did you pick?)
I still have a small question though. What's the reason for
freezing the options? I can see no obvious explanation for it except
that one might not want to change them by accident. Or is there
another reason for this?

No, that's the only reason. Note also that this is not 100% safe
because elements in the Hash are not frozen so for a more robust
solution you would need to recursively freeze everything contained in
the Hash. I still like it to freeze the Hash to avoid simple errors
and document in code that this is really meant to be constant. (At
least that's what I guessed you wanted them to be.)

Kind regards

robert
 
7

7stud --

Lars Olsson wrote in post #992249:
Hello again!

I was fooling around with your original requirement:
I want to create a class method that takes a bunch
of options and returns a singleton class with those
options set,

...and I think I was probably running into the same problems you were:

class Object
def singleton_class
class << self
self
end
end

end

class Opportunities
def self.using(options)
# Store options in singleton class variable
# @options and then return singleton class

obj = self.new
singleton = obj.singleton_class
singleton.instance_variable_set:)@options, options)

singleton.class_eval do #<----PROBLEM HERE
def options
@options
end
end

singleton

end

end


Foo = Opportunities.using(one: 1, two: 2, three: 3)
Bar = Opportunities.using(four: 4, five: 5, six: 6)

p Foo.options
p Bar.options

--output:--
prog.rb:37:in `<main>': undefined method `options' for
#<Class:#<Opportunities:0x88cfef8>> (NoMethodError)

The problem is that options() is an instance method of the singleton
class, but in this line:

Foo.options

Foo is the singelton class, and therefore options() is being called as a
class method of the singleton class. A class method of a singleton
class??!! You can actually create such a thing in ruby:

class Object
def singleton_class
class << self
self
end
end
end

class Opportunities
def self.using(options)
# Store options in singleton class variable
# @options and then return singleton class

obj = self.new
singleton = obj.singleton_class
singleton.send:)instance_variable_set, :mad:options, options)

singleton.singleton_class.class_eval do #<---SOLUTION***
def options
@options
end
end

singleton

end


end


Foo = Opportunities.using(one: 1, two: 2, three: 3)
Bar = Opportunities.using(four: 4, five: 5, six: 6)

p Foo.options
p Bar.options

--output:--
{:eek:ne=>1, :two=>2, :three=>3}
{:four=>4, :five=>5, :six=>6}
 
7

7stud --

7stud -- wrote in post #992354:
The syntax is a little simpler doing this:

class << singleton
attr_reader :eek:ptions #(self.)attr_reader, where self=singleton
end

...actually, class << singleton puts us in the singleton class of
singleton, so that should say "self = singleton class of singleton".
 
7

7stud --

Robert K. wrote in post #992235:
Your new class does not inherit module ClassMethods, that's why.

I figured out the problem after I posted--including the module
ClassFactory creates class methods in Opportunities--not the anonymous
class that using() returns.
 
L

Lars Olsson

and:

    class <<singleton
      attr_reader :eek:ptions
    end

I'm happy to see that is was possible to do this in so many ways :)
For what it is worth, here's what I finally settled for:

class Opportunities

@options = {:eek:pt1 => :default, :eek:pt2 => :default, :eek:pt3
=> :default}.freeze

def self.options
@options
end

def self.using(options)
Class.new(self) do
@options = superclass.options.merge(options).select do |key|
superclass.options.has_key?(key)
end.freeze
end
end

end

Thank you all for showing how crazy simple this stuff is to do in
ruby!

/lasso
 
7

7stud --

Try something like this:

def using(new_options)
Class.new(self) do
old_options = superclass.options
@options = old_options.merge(new_options).select do |key|
old_options.has_key?(key)
end.freeze
end
end
 
7

7stud --

7stud -- wrote in post #992632:
Try something like this:

def using(new_options)
Class.new(self) do
old_options = superclass.options
common_keys = old_options.keys & new_options.keys

common_keys.each do |key|
old_options[key] = new_options[key]
end

@options = old_options
@options.freeze
end
end

Whoops. That doesn't work:

prog.rb:18:in `[]=': can't modify frozen hash (RuntimeError)

Hmmm...why can you merge() but not directly assign to individual keys
when a hash is frozen?

Ok, merge() with some better variable names:

def using(new_options)
Class.new(self) do
old_options = superclass.options

@options = old_options.merge(new_options).select do |key|
old_options.has_key?(key)
end.freeze
end
end
 
L

Lars Olsson

7stud -- wrote in post #992632:
Try something like this:
   def using(new_options)
      Class.new(self) do
        old_options = superclass.options
        common_keys = old_options.keys & new_options.keys
        common_keys.each do |key|
          old_options[key] = new_options[key]
        end
        @options = old_options
        @options.freeze
      end
    end

Whoops.  That doesn't work:

prog.rb:18:in `[]=': can't modify frozen hash (RuntimeError)

Hmmm...why can you merge() but not directly assign to individual keys
when a hash is frozen?

Ok, merge() with some better variable names:

   def using(new_options)
      Class.new(self) do
        old_options = superclass.options

        @options = old_options.merge(new_options).select do |key|
           old_options.has_key?(key)
        end.freeze
      end
    end

Hi!

Even though I agress that using the same name for both the local
parameter and a method with the same name might be a bad idea, my code
still works without any problem. If I redefine self using to:

(minor change to last version. I split up the calls to merge, select,
and freeze to its own rows.)

def self.using(options)
Class.new(self) do
puts "Superclass options: #{superclass.options.inspect}"
puts "Options from parameter: #{options}"
@options = superclass.options.merge(options)
@options.select! { |key, value| superclass.options.has_key?
(key) }
@options.freeze
end
end

it clearly shows that options (by itself) refers to the local paramter
and not the class name.

/lasso
 
7

7stud --

Lars Olsson wrote in post #992715:
old_options[key] = new_options[key]

old_options.has_key?(key)
end.freeze
end
end

Hi!

Even though I agress that using the same name for both the local
parameter and a method with the same name might be a bad idea, my code
still works without any problem. If I redefine self using to:

There's working code, and there's clear code. The goal: working code
that is clear!
Hmmm...why can you merge() but not directly assign to
individual keys when a hash is frozen?

Duh. Because merge() produces a new hash--it doesn't change the frozen
hash.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top