Copying parameters to singleton class

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({:one => 1, :two: => 2, :three => 3})
bar = Opportunities.using({:four => 4, :five: => 5, :six => 6})

and then

foo.options => {:one => 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

On Mon, Apr 11, 2011 at 11:20 AM, Lars O. [email protected] wrote:

def self.using(options)
foo = Opportunities.using({:one => 1, :two: => 2, :three => 3})

This really should be possible with ruby, right?

Right. You can use a closure like this:

class Opportunities
def self.using(opts)
opts.freeze
cl = Class.new self # so we can define instance methods
class<<cl;self;end.send(:define_method, :options) {opts}
cl
end

inherited by all

def xyz
end
end

irb(main):019:0> foo = Opportunities.using(:one => 1, :two => 2, :three
=> 3)
=> #Class:0x10958c78
irb(main):020:0> bar = Opportunities.using(:four => 4, :five => 5, :six
=> 6)
=> #Class:0x1095f7f8
irb(main):021:0> foo.options
=> {:one=>1, :two=>2, :three=>3}
irb(main):022:0> bar.options
=> {:four=>4, :five=>5, :six=>6}
irb(main):023:0> f = foo.new
=> #<#Class:0x10958c78:0x109672dc>
irb(main):024:0> f.class.options
=> {:one=>1, :two=>2, :three=>3}
irb(main):025:0> f.class.ancestors
=> [#Class:0x10958c78, Opportunities, Object, Kernel, BasicObject]

Or you can use a member

class Opportunities
def self.using(opts)
cl = Class.new self

class<<cl
  attr_reader :options
end

cl.instance_variable_set '@options', opts.freeze

cl

end
end

or this way

class Opportunities
class <<self
attr_reader :options
end

def self.using(opts)
cl = Class.new self
cl.instance_variable_set ‘@options’, opts.freeze
cl
end
end

or this way

class Opportunities
class <<self
attr_reader :options
end

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

Choose to your liking. :slight_smile:

Cheers

robert

On Mon, Apr 11, 2011 at 11:20 AM, Lars O. [email protected] wrote:

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

and then

foo.options => {:one => 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.

If you don’t want instances of Opportunities, of which class you want
instances?
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 :options
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.

On 11 Apr, 11:36, Jess Gabriel y Galn [email protected]
wrote:

class Opportunities

that they return different values for the Opportunities.options call.
ruby-1.8.7-p334 :006?> attr_reader :options
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

Robert K. wrote in post #992088:

Choose to your liking. :slight_smile:

Hey Robert K,

I read your factory pattern thread the other day, and I wonder if that
pattern would apply here? After all, producing several anonymous
instances of Class is akin to producing instances of various classes.
Something like this:

class ClassFactory
def self.using(hash)

 Class.new do
   @options = hash

   class << self
     attr_reader :options
   end
 end

end
end

class Opportunities < 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:–
{:one=>1, :two=>2, :three=>3}
{:four=>4, :five=>5, :six=>6}

In any case, it seems to simplify the syntax a bit.

Hey Lars O.,

Check out the shorter syntax for hashes when your keys are symbols.

7stud – wrote in post #992201:

Robert K. wrote in post #992088:

Choose to your liking. :slight_smile:

Hey Robert K,

I read your factory pattern thread the other day, and I wonder if that
pattern would apply here? After all, producing several anonymous
instances of Class is akin to producing instances of various classes.

In hindsight, I guess that wouldn’t work if Opportunities is an existing
class that needs to be reopened to provide the needed functionality.

On Tue, Apr 12, 2011 at 4:05 AM, 7stud – [email protected]
wrote:

Something like this:
end
end
end

Well, basically you just exchanged Opportunities with ClassFactory
compared to one of my examples. Actually method #using is a factory
method, no matter what class or instance it sits in.

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

I don’t think this can work - at least there’s something missing. Did
you mean to make Opportunities inherit ClassFactory? But IMHO there
is no point in splitting this up. Please note also that I made new
classes inherit Opportunities in order for inheritance to work.

Check out the shorter syntax for hashes when your keys are symbols.

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

If we start shortening it, we should do

foo = Opportunities.using(one: 1, two: 2, three: 3)

i.e. get rid of the superfluous pair of curly braces. :slight_smile:

Kind regards

robert

…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 :options
    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:–
{:one=>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 methodoptions’ for Foo:Class
(NoMethodError)

On 12 Apr, 09:00, Robert K. [email protected] wrote:

def using(hash)

def using(opts)
irb(main):020:0> foo = Opportunities.using(foo: 1, bar: 2)

module ClassFactory
end
end

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

Kind regards

robert


remember.guy do |as, often| as.you_can - without
endhttp://blog.rubybestpractices.com/

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

On Tue, Apr 12, 2011 at 10:30 AM, Lars O. [email protected] wrote:

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

On Tue, Apr 12, 2011 at 4:48 AM, 7stud – [email protected]
wrote:

Class.new do
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 :options

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
=> [#Class:0x10952bc0, Opportunities, Object, Kernel, BasicObject]

Class.new do

–output:–
prog.rb:28:in <main>': undefined methodoptions’ for Foo:Class
(NoMethodError)

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

Kind regards

robert

Lars O. 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 (see output)
  def options
    @options
  end
end

return 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 methodoptions’ 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 Foo. A class method of a singleton
class??!! Whaaaat! But 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, :@options, options)

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

return 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:–
{:one=>1, :two=>2, :three=>3}
{:four=>4, :five=>5, :six=>6}

The syntax is a little simpler doing this:

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

7stud – wrote in post #992354:

The syntax is a little simpler doing this:

class << singleton
  attr_reader :options  #(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”.

Robert K. wrote in post #992232:

On Tue, Apr 12, 2011 at 4:05 AM, 7stud – [email protected]
wrote:

Something like this:
end
end
end

Well, basically you just exchanged Opportunities with ClassFactory
compared to one of my examples.

Yes, that’s true.

Did
you mean to make Opportunities inherit ClassFactory?

Yes. I think you must have posted in the middle of one of my edits
because Opportunities does inherit from ClassFactory.

But IMHO there
is no point in splitting this up. Please note also that I made new
classes inherit Opportunities in order for inheritance to work.

Yes, I noticed that, but I don’t think that is a requirement.

If we start shortening it, we should do

foo = Opportunities.using(one: 1, two: 2, three: 3)

i.e. get rid of the superfluous pair of curly braces. :slight_smile:

Good catch.

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

Yes, there was no need to use included() since the module did not
contain methods where some of the methods were destined to be instance
methods and others were destined to be class methods.

Robert K. wrote in post #992235:

–output:–
prog.rb:28:in <main>': undefined methodoptions’ for Foo:Class
(NoMethodError)

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

Yes,including the module
ClassFactory in Opportunities creates class methods in
Opportunities–not methods in the anonymous class that
Opportunities.using() returns.

Robert K. wrote in post #992088:

or this way

class Opportunities
class <<self
attr_reader :options
end

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

The using() method in that solution can be simplified:

def self.using(opts)
Class.new(self) do
@options = opts.freeze
end
end

Instance variables bind themselves to whatever self is at the time they
are created. Inside the block self is equal to the new
anonymous class created by Class.new.

And this bit in my last post:

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

is equivalent to:

singleton.instance_eval do
  def options
    @options
  end
end

and:

class <<singleton
  def options
    @options
  end
end

and:

class <<singleton
  attr_reader :options
end

Too many variables named ‘options’!

On 13 Apr, 02:29, 7stud – [email protected] wrote:

and:

class <<singleton
attr_reader :options
end


Posted viahttp://www.ruby-forum.com/.

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

class Opportunities

@options = {:opt1 => :default, :opt2 => :default, :opt3
=> :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

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

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs