9

I'm following Codecademy's Ruby course, about 85% done.

Over and over it asks you to create a class and pass in some parameters and make them instance variables, like this for example:

class Computer
    def initialize(username, password)
        @username = username
        @password = password
    end
end

Every time, it asks you to make the exact same instance variables as the parameters you passed in.

It made me wonder if there is a Ruby way to handle this automatically, removing the need to type it all out yourself every time.

I am aware you can do

class Computer
    def initialize(username, password)
        @username, @password = username, password
    end
end

but that's hardly less typing.

I did some searching and found that you can create a set of 'getters' using attr_reader like

class Song
    attr_reader :name, :artist, :duration
end

aSong = Song.new("Bicylops", "Fleck", 260)
aSong.artist # "Fleck"
aSong.name # "Bicylops"
aSong.duration # 260

But as far as I can tell that's not really what I'm looking for. I'm not trying to auto create getters and/or setters. What I'm looking for would be something like this

class Person
    def initialize(name, age, address, dob) #etc
        # assign all passed in parameters to equally named instance variables
        # for example
        assign_all_parameters_to_instance
        # name, age, address and dob would now be accessible via
        # @name, @age, @address and @dob, respectively
    end
end

I did some searching for ruby shortcut for assigning instance variables and alike but couldn't find an answer.

Is this possible? If so, how?

Tim
  • 41,901
  • 18
  • 127
  • 145
  • 2018 Update: creating `attr_reader` doesn't automatically creates initialiser. – vaibhavatul47 Sep 13 '18 at 18:00
  • Does this answer your question? [How to cleanly initialize attributes in Ruby with new?](https://stackoverflow.com/questions/12763016/how-to-cleanly-initialize-attributes-in-ruby-with-new) – Jon Schneider Feb 07 '23 at 13:38
  • @JonSchneider I might have been able to answer that 8 years ago – Tim Feb 08 '23 at 07:26

4 Answers4

8
Person = Struct.new(:name, :artist, :duration) do
  # more code to the Person class
end

Your other option is to pass a Hash/keyword of variables instead and use something like ActiveModel::Model https://github.com/rails/rails/blob/master/activemodel/lib/active_model/model.rb#L78-L81

def initialize(params={})
  params.each do |attr, value|
    self.instance_variable_set("@#{attr}", value)
  end if params
end
avl
  • 663
  • 3
  • 6
  • Are there any restrictions in using a struct instead of a class? It looks useful, but i expect it to have a downside :-P – Tim Nov 30 '14 at 16:48
  • The draw is that a Struct is more tolerant to argument size `Person.new("John")` -> no Error, artist and duration set to nil. More about structs you can find here: http://www.ruby-doc.org/core-2.1.5/Struct.html – avl Nov 30 '14 at 16:51
  • Much like Proc and lambda then. Thanks, this is helpful – Tim Nov 30 '14 at 16:54
  • How do you handle other initialization code in a struct? `def initialize` with some other code seems to mess it up – Tim Nov 30 '14 at 16:57
  • 2
    @TimCastelijns: `Struct.new` returns a `Class`, so there are exactly zero restrictions, because it *is* a class. – Jörg W Mittag Nov 30 '14 at 16:59
  • 1
    Thanks both. I found some useful information in [Ruby: Struct vs initialize](http://stackoverflow.com/questions/17492357/ruby-struct-vs-initialize) – Tim Nov 30 '14 at 17:07
  • there are caviats however, see http://thepugautomatic.com/2013/08/struct-inheritance-is-overused/ – peter Nov 30 '14 at 21:31
1

First of all, your second block with set of attr_reader will not work. Because you are providing 3 arguments to default initialize method, which accepts 0 arguments.

Answer to your question is no, there is no such method, unless you are going to define it yourself using metaprogramming.

Rustam Gasanov
  • 15,290
  • 8
  • 59
  • 72
  • I found attr_reader [here](http://phrogz.net/programmingruby/tut_classes.html), it's not my code. But thanks for pointing that out – Tim Nov 30 '14 at 16:46
  • @TimCastelijns I suppose they just skip `initialize` method defined at the beginning of the article in subsequent examples. – Rustam Gasanov Nov 30 '14 at 16:49
  • Ah I see. That makes the entire concept unusable. – Tim Nov 30 '14 at 16:51
1

assign_all_parameters_to_instance can't exist the way you would want it to. It would need to have access to its caller's local variables or parameters, which is a very awkward thing to do, and a violation of method encapsulation.

However, you could just generate a suitable initialize method:

class Module
  private def trivial_initializer(*args)
    module_eval(<<-"HERE")
      def initialize(#{args.join(', ')})
        #{args.map {|arg| "@#{arg} = #{arg}" }.join("\n")}
      end
    HERE
  end
end

class Computer
  trivial_initializer :username, :password
end

Computer.new('jwm', '$ecret')
# => #<Computer:0x007fb3130a6f18 @username="jwm", @password="$ecret">
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • *It would need to have access to its caller's local variables or parameters, which is a very awkward thing to do, and a violation of method encapsulation.* Knowing that I'm not supposed to do what I'm trying to do (and why) is valuable information, thanks – Tim Dec 01 '14 at 09:21
0

I used this https://www.safaribooksonline.com/library/view/ruby-cookbook/0596523696/ch10s09.html and it worked fine for me.

I just had to replace eval(var, binding) by eval(var.to_s, binding).

So finally :

class Object
  private
  def set_instance_variables(binding, *variables)
    variables.each do |var|
      instance_variable_set("@#{var}", eval(var.to_s, binding))
    end
  end
end

class RGBColor
  def initialize(red=0, green=0, blue=0)
    set_instance_variables(binding, *local_variables)
  end
end

RGBColor.new(10, 200, 300)
# => #<RGBColor:0xb7c22fc8 @red=10, @blue=300, @green=200>
Cédric ZUGER
  • 422
  • 4
  • 12