SitePoint Sponsor

User Tag List

Results 1 to 7 of 7

Hybrid View

  1. #1
    SitePoint Member
    Join Date
    Jan 2003
    Posts
    15
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Dynamically Naming a Class Variable with a Param

    I swear this is like throwing a football or baseball left handed! I'm stumped on something that I think should be easy - but since I'm still new to Ruby, I'm not sure how to pull it off.

    Basically, I want to be able to create a class var based on the value I pass in the constructor. Something like this:

    # BEGIN CODE
    class Foo
    def initialize(var)
    @#{var} = []
    end
    end

    b = new.Foo("baz")
    p b
    # END CODE

    I was hoping that I would get a new 'Foo' that has a class array called 'baz' - instead I get a syntax error. I just need a short way of constructing empty arrays when I instantiate an object. Otherwise, I guess I could statically name all of the arrays I want, but that doesn't seem really elegant.

    And apologies about the code block - I couldn't get the Ruby to format correctly.

  2. #2
    SitePoint Zealot bronze trophy
    Join Date
    Jun 2004
    Location
    Stockholm, Sweden
    Posts
    148
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    What are you even trying to do?

    Correct Ruby syntax for instantiating an object is: bar = Foo.new.

    Why do you want to name the variable yourself from the outside of the object, an object's internal data is just that: An object's internal data.

    Could you please elaborate on what you are trying to achieve, and maybe we could tell you how you're supposed to be doing it.
    If there is a way to overcome the suffering, there is no need to worry; if there is no way to overcome the suffering, there is no point to worry.
    - Shantideva

  3. #3
    SitePoint Member
    Join Date
    Jan 2003
    Posts
    15
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Sorry about the syntax gaffe.

    I'm trying to create a class called 'Address.' And this class should have two public attributes - 'location' and 'type.' It will have one public method called 'getAddress.' Based on the 'provider' and 'type' attributes, 'getAddress' will return a 'street' and a 'zip code.' The mechanics of the 'getAddress' method are to:

    1. Load a CSV file that has the following elements ('location', 'street', 'zip')
    2. Parse the file and load 'street' and 'zip' into a 'array' that should be named 'location.'

    So let's say the CSV file has the following data:
    Code:
      Home,123 Foo,90210
        Home,345 Wombat,00921
        Work,456 Baz,11210
        Work,991 Bazbat,92128
    Now I have my class:
    Code:
       class Address
     	def initialize(location, type="default")
     		@location = location
     		@type = type
     		# i want to initialize an empty array here that's named
     		# whatever the value that is given for the 'location' parameter
     		# lets just call it @array but ideally these would be class
     		# vars that could be called '@home' or '@work' based on 
     		# 'location' parameter when the object is instantiated
     	end
     
     	def getAddress()
     		CSV.open('csv_filename_here.csv', 'r') do | row |
     			# push the contents of the row into the initialized array
     		end
     
     		case @type
     			when "random"
     			    # return a random element from the array
     			when "default"
     			    # return the first element from the array
     		end
     	end
        end
    I'm sure I'm doing this a backasswards - but when I pseudocoded it out, it seemed like a good way of handling the problem.

    Maybe not.

  4. #4
    SitePoint Guru silver trophy Luke Redpath's Avatar
    Join Date
    Mar 2003
    Location
    London
    Posts
    794
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It sounds to me like you've got candidates for two child classes, HomeAddress and WorkAddress. Perhaps some sort of factory class, AddressFactory that parses a CSV file and returns an array of Address objects. Your Address classes shouldn't know anything about a CSV file (what if you wanted to use a different source?).

    Of course, you only need the sub-classes if there is a need for different behaviour depending on whether its a home or work address, otherwise, a single Address class will do. It still shouldn't know anything about where the data comes from though.

    What I am confused about is why the array needs to be named according to the location. What difference does it make?

    So, as an example:

    Code:
    class Address
      attr_reader :city, :zip, :type
    
      def initialize(type, city, zip)
        @city = city
        @zip = zip
        @type = type
      end
    end
    
    class AddressCsvReader
      attr_reader :addresses
    
      def initialize(path)
        @addresses = Array.new
        CSV.open(path, 'r') do |row|
          address = Address.new( row[0], row[1], row[2] )
          @addresses << address
        end
      end
    end
    
    reader = AddressCsvReader.new("my_csv_file.csv")
    reader.addresses.each do |addr|
      puts "This is a #{addr.type} address in the city #{addr.city}"
    end
    Of course, because in the above example "reader.addresses" returns a normal array of Address objects, you can use any of the built-in Ruby address functions, such as "first". You might have to write your own "random" method on the AddressCsvReader class though.

  5. #5
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by bivouac
    I swear this is like throwing a football or baseball left handed!
    Probably more like trying to throw one with your appendix, but just so you know how to do it, here's an example from the documentation:

    Code:
       class Fred
         def initialize(p1, p2)
           @a, @b = p1, p2
         end
       end
       fred = Fred.new('cat', 99)
       fred.instance_variable_set(:@a, 'dog')   #=> "dog"
       fred.instance_variable_set(:@c, 'cat')   #=> "cat"
       fred.inspect                             #=> "#<Fred:0x401b3da8 @a=\"dog\", @b=99, @c=\"cat\">"
    You should be able to call instance_variable_set from inside Fred, like this:

    Code:
    class Foo
      def initialize(var)
         instance_variable_set(var, [])
      end
    end
    You say you think it should be easy - what does the code look like in a language you are more familiar with?

    What you are doing is setting the [b]name[b] of a variable dynamically. That's quite unusual. Normally you change the values of variables, not what the variables are themselves. If you want to set a key (a name) and a value at the same time, a hash is much more common. For example:

    Code:
    def add_a_key_and_a_value(key, value)
      @params[key] = value
    end
    That way you still know what the name of the variable is: @params. If you set the name of the variable dynamically, it is hard to find the variable again, because you don't know what it is called!

    As an aside, getAddress is a bit foreign in Ruby. CamelCase is only really used in class and module names, where the case of the first letter is important. So if you wanted to use two words, it would be get_address.

    Using "get" at the start of a method name is a little unusual too. Ruby supports this syntax for getters and setters, so an explicit "get" or "set" isn't needed:

    Code:
    class Fred
    
      def foo=(val)
        @foo = val
      end
    
      def foo
        @foo
      end
    
    end
    So normally you would use something more descriptive than "get" if you needed something special.

    Douglas
    Hello World

  6. #6
    SitePoint Wizard stereofrog's Avatar
    Join Date
    Apr 2004
    Location
    germany
    Posts
    4,324
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by DougBTX

    Code:
    def add_a_key_and_a_value(key, value)
      @params[key] = value
    end
    Perhaps more common is to override [] and []=

    Code:
    class A
    	def initialize(params)
    		@params = params
    	end
    	def [](key)
    		@params[key]
    	end
    	def []=(key, value)
    		@params[key] = value
    	end
    end
    
    a = A.new('name' => 'Fred', 'age' => 20)
    
    puts a['name']
    a['age'] += 10
    puts a['age']
    Delegation looks even more "rubysh":
    Code:
    require "delegate"
    
    class B < SimpleDelegator
    	def initialize(params)
    		super(params)
    	end
    end
    
    b = B.new('name' => 'Sally', 'age' => 17)
    
    puts b['name']
    puts b['age']

  7. #7
    SitePoint Member
    Join Date
    Jan 2003
    Posts
    15
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    You know the more I think about it - the more I think you guys are right. And I apologize for the real lack of clarity with my request, this is a work problem and of course I'm translating the problem space from work to example poorly.

    I think I'm going about this the totally wrong way - never have I ever wanted to dynamically set the name of a var inside of function, let along a class method. The factory class that Luke mentions is definitely worth investigating and Douglas' mention of the hash also has some merit (too bad data structures scare me).

    In any case, everything that you guys have posted has been immeasurably helpful. I really appreciate you guys putting the time and effort forward to helping me out. Going to try and re-attack this problem tonight with some of the tips given here and some of the tips I got @ work and see if I can't come up with something. I'll repost if anyone's interesting.

    Thanks again.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •