1

I need to load a YAML file (I'm experimenting with SettingsLogic) and I'd like the instance to load the YAML with the same name as it. Briefly:

class MySettings < SettingsLogic
  source "whatever_the_instance_is_called.yml"

# Do some other stuff here
end

basic_config = MySettings.new    # loads & parses basic_config.yml
advanced_cfg = MySettings.new    # loads & parses advanced_cfg.yml
...and so on...

The reason for this I don't yet know what configuration files I'll have to load, and typing:

my_config = MySettings.new("my_config.yml") 

or

my_config = MySettings.new(:MyConfig) 

just seems to be repeating myself.

I took a look around both Google and Stackoverflow, and the closest I came to an answer is either "Get Instance Name" or a discussion about how meaningless an instance name is! (I'm probably getting the query wrong, however.)

I have tried instance#class, and instance#name; I also tried instance#_id2ref(self).

What am I missing?!

Thanks in advance!

Community
  • 1
  • 1
Carolyn Ann
  • 93
  • 1
  • 2
  • 8
  • Why the edits, the Tin Man? I can understand the technical corrections, but the grammatical ones? I do know how to write! I'm kind of put out by your "corrections"; even if you don't like my "voice" or style, I'm not so sure I appreciate being "corrected" in this manner. – Carolyn Ann Nov 09 '12 at 00:08
  • And yes, I didn't want to tell you this, but your first sentence was nearly not readable. – Boris Stitnicky Nov 09 '12 at 00:14
  • I missed a word, sorry! I've corrected it. (I still don't appreciate *all* of the Tin Man's edits, however.) – Carolyn Ann Nov 09 '12 at 00:33
  • Tin Man is doing this all the time, to all new posts, his edits are generally very useful, so put up with him :-) for now. – Boris Stitnicky Nov 09 '12 at 00:49
  • Someone should tell @the-tin-man to be very careful with that sort of thing. It's not as if *he* noticed the missing word, either. And imposing his own "voice" isn't an improvement - it seems to be more that he's taken CS Lewis ("use only sufficient words") to heart, without understanding what Lewis was saying. As you might guess - I'm seriously offended by the unnecessary editing; the technical edits, sure. The rest of them? Yeah, they're offensive. I do know how to write. – Carolyn Ann Nov 09 '12 at 01:24
  • http://stackoverflow.com/faq#editing If you don't like the editing, you're free to revert it at any time. And, I noticed the missing word, but wasn't able to tell from the sentence what the word should have been. I figured that the OP would step up and correct it after-the-fact, and was right. If not, someone else would -- that's how SO works -- it's collaboratively edited, as per the FAQ. If you'd like to discuss the collaborative editing process, you're welcome to submit a question on http://meta.stackoverflow.com/. – the Tin Man Nov 09 '12 at 04:58
  • I get collaborative editing - but I didn't think it extended to *you* deciding *how* I was going to ask my question. I do not appreciate my voice being altered so I am in agreement with some "officialese" you might have going. If the standard is corporate, then fine - I can adhere to that. I won't like it, but I can do it. I do not appreciate having to take the time to check if you has altered the way I use words. You might deem some sentences superfluous - but I put them for a reason. You might want to consider, or ask, why someone wrote those words afore you start editing. – Carolyn Ann Nov 09 '12 at 06:19
  • (cont.) *Asking* me about the missing word would have been appreciated - we all make mistakes. (On the other hand, I think the intent was clear from the example.) – Carolyn Ann Nov 09 '12 at 06:20
  • 1
    Okay - I read that FAQ. I have very limited experience with editing content on Wikipedia. And I'm not about to get into a lengthy, and ultimately fruitless, discussion about the editing process. As I said, we all make mistakes; I've got a couple in single prior comment; you made one in your editing. (BTW, I am not "OP", I am me - a person.) I will, in the future, consider whether this is the appropriate venue for my questions before I ask; I will also try to remember to restrict myself to a featureless, corporate, writing style so that you won't be tempted to edit my words. – Carolyn Ann Nov 09 '12 at 06:48

4 Answers4

2

O.K., so with local variable assignment, there are snags, such as that assignment might occur slightly later than local variable symbol addition to the local variable list. But here is my module ConstMagicErsatz that I used to implement something similar to out-of-the box Ruby constant magic:

a = Class.new
a.name #=> nil - anonymous
ABC = a # constant magic at work
a.name #=> "ABC"

The advantage here is that you don't have to write ABC = Class.new( name: "ABC" ), name gets assigned 'magically'. This also works with Struct class:

Koko = Struct.new
Koko.name #=> "Koko"

but with no other classes. So here goes my ConstMagicErsatz that allows you to do

class MySettings < SettingsLogic
  include ConstMagicErsatz
end

ABC = MySettings.new
ABC.name #=> "ABC"

As well as

a = MySettings.new name: "ABC"
a.name #=> "ABC"

Here it goes:

module ConstMagicErsatz
  def self.included receiver
    receiver.class_variable_set :@@instances, Hash.new
    receiver.class_variable_set :@@nameless_instances, Array.new
    receiver.extend ConstMagicClassMethods
  end

  # The receiver class will obtain #name pseudo getter method.
  def name
    self.class.const_magic
    name_string = self.class.instances[ self ].to_s
    name_string.nil? ? nil : name_string.demodulize
  end

  # The receiver class will obtain #name setter method
  def name= ɴ
    self.class.const_magic
    self.class.instances[ self ] = ɴ.to_s
  end

  module ConstMagicClassMethods
    # #new method will consume either:
    # 1. any parameter named :name or :ɴ from among the named parameters,
    # or,
    # 2. the first parameter from among the ordered parameters,
    # and invoke #new of the receiver class with the remaining arguments.
    def new( *args, &block )
      oo = args.extract_options!
      # consume :name named argument if it was supplied
      ɴς = if oo[:name] then oo.delete( :name ).to_s
           elsif oo[:ɴ] then oo.delete( :ɴ ).to_s
           else nil end
      # but do not consume the first ordered argument
      # and call #new method of the receiver class with the remaining args:
      instance = super *args, oo, &block
      # having obtained the instance, attach the name to it
      instances.merge!( instance => ɴς )
      return instance
    end

    # The method will search the namespace for constants to which the objects
    # of the receiver class, that are so far nameless, are assigned, and name
    # them by the first such constant found. The method returns the number of
    # remaining nameless instances.
    def const_magic
      self.nameless_instances = 
        class_variable_get( :@@instances ).select{ |key, val| val.null? }.keys
      return 0 if nameless_instances.size == 0
      catch :no_nameless_instances do search_namespace_and_subspaces Object end
      return nameless_instances.size
    end # def const_magic

    # @@instances getter and setter for the target class
    def instances; const_magic; class_variable_get :@@instances end
    def instances= val; class_variable_set :@@instances, val end

    # @@nameless_instances getter for the target class
    def nameless_instances; class_variable_get :@@nameless_instances end
    def nameless_instances= val; class_variable_set :@@nameless_instances, val end

    private

    # Checks all the constants in some module's namespace, recursivy
    def search_namespace_and_subspaces( ɱodule, occupied = [] )
      occupied << ɱodule.object_id           # mark the module "occupied"

      # Get all the constants of ɱodule namespace (in reverse - more effic.)
      const_symbols = ɱodule.constants( false ).reverse

      # check contents of these constant for wanted objects
      const_symbols.each do |sym|
        # puts "#{ɱodule}::#{sym}" # DEBUG
        # get the constant contents
        obj = ɱodule.const_get( sym ) rescue nil
        # is it a wanted object?
        if nameless_instances.map( &:object_id ).include? obj.object_id then
          class_variable_get( :@@instances )[ obj ] = ɱodule.name + "::#{sym}"
          nameless_instances.delete obj
          # and stop working in case there are no more unnamed instances
          throw :no_nameless_instances if nameless_instances.empty?
        end
      end

      # and recursively descend into the subspaces
      const_symbols.each do |sym|
        obj = ɱodule.const_get sym rescue nil # get the const value
        search_namespace_and_subspaces( obj, occupied ) unless
          occupied.include? obj.object_id if obj.kind_of? Module
      end
    end
  end # module ConstMagicClassMethods
end # module ConstMagicErsatz

The above code implements automatic searching of whole Ruby namespace with the aim of finding which constant refers to the given instance, whenever #name method is called.

The only constraint using constants gives you, is that you have to capitalize it. Of course, what you want would be modifying the metaclass of the object after it is already born and assigned to a constant. Since, again, there is no hook, you have to finde the occasion to do this, such as when the new object is first used for its purpose. So, having

ABC = MySettings.new

and then, when the first use of your MySettings instance occurs, before doing anything else, to patch its metaclass:

class MySettings
  def do_something_useful
    # before doing it
    instance_name = self.name
    singleton_class.class_exec { source "#{instance_name}.yml" }
  end

  # do other useful things
end
Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
  • Now I've noticed I use `ActiveSupport` `#demodulize` method in `ConstMagicErsatz` module, sorry about that. – Boris Stitnicky Nov 09 '12 at 01:09
  • Holy cannoli! Wow! :-) THANK YOU!!! No worries on he ActiveSupport - I've got a few places where I've had to include it. Again, thanks! :-) – Carolyn Ann Nov 09 '12 at 01:13
  • @Carolyn Ann: Good luck getting it work. It won't be easy, but it will work in the end. – Boris Stitnicky Nov 09 '12 at 01:22
  • Hi, @Boris Stitnicky - I've been trying to get the code to work, but it won't! If I pass in MyClass.new :name => instance_name, it works. If I don't - it doesn't. Any ideas what I'm doing wrong? (I haven't altered any other code.) BTW: I had to change "instance = super *args, oo, &block" to instance = super *args, &block". Thanks! – Carolyn Ann Nov 11 '12 at 05:32
0

Shouldn't you be able to do either

File.open(File.join(File.expand_path(File.dir_name(__FILE__)), foo.class), "r")

or

require foo.class

The first one need not be that complicated necessarily. But if I'm understanding you correctly, you can just use foo.class directly in a require or file load statement.

Adjust as necessary for YAML loading, but #class returns a plain old string.

Alex Nye
  • 147
  • 8
  • Unfortunately, Alex, this is all happening in an "application.rb" file of a Sinatra app, so grabbing the file name won't give me the YAML file to load. I'm trying to load a bunch of configuration files - I'm staying away from the 'one big config array/hash/object/etc' because it seems silly to pass that around when I can pass a smaller, more pertinent, object to whatever needs it. The only thing I know at this point is that there could be a lot of configuration files! (One for each type of server, plus one for multithreading or not, and the combinations thereof.) Thanks, though! – Carolyn Ann Nov 09 '12 at 00:21
0

Well if you have tons of variables to instantiate, I'd personally just create a Hash to hold them, it's cleaner this way. Now to instantiate all of this, you could do a loop other all your yaml files :

my_settings = {}
[:basic_config, :advanced_cfg, :some_yaml, :some_yaml2].each do |yaml_to_parse|
  my_settings[yaml_to_parse] = MySettings.new(yaml_to_parse)
end

Make sure your initialize method in MySettings deals with the symbol you give it!

Then get your variables like this :

my_settings[:advanced_cfg]
Anthony Alberto
  • 10,325
  • 3
  • 34
  • 38
0

Unfortunately, Ruby has no hooks for variable assignment, but this can be worked around. The strategy outline is as follows: First, you will need to get your MySettings.new method to eval code in the caller's binding. Then, you will find the list of local variable symbols in the caller's binding by calling local_variables method there. Afterwards, you will iterate over them to find which one refers to the instance returned by super call in your custom MySettings.new method. And you will pass its symbol to source method call.

Community
  • 1
  • 1
Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
  • Ah... I didn't know about that, Boris! Thanks - I'm going to give that a try. :-) – Carolyn Ann Nov 09 '12 at 00:22
  • No, don't do that. That's too complicated. I only wrote it because it is interesting and it actually answers your question. But maybe we can try to _address your problem_ instead of _answering your question_ :-) – Boris Stitnicky Nov 09 '12 at 00:28