views:

220

answers:

5
+3  Q: 

Ruby setter idiom

I'm working on a Chart class and it has a margin parameter, that holds :top, :bottom, :right and :left values. My first option was to make margin a setter and set values like this:

# Sets :left and :right margins and doesn't alter :top and :bottom 
chart.margins = {:left => 10, :right => 15}

It's nice, because it is clearly a setter, but, after some thought, I think it could be confusing too: the user might think that margins contains only :left and :right values, what is not right. Another option is eliminate = and make it an ordinary method:

chart.margins(:left => 10, :right => 15)

With this syntax, it's easy to figure out what is happening, but it is not a standard setter and conflicts with margins getter. And there's still another option:

chart.margins(:left, 10)
chart.margins(:right, 15)

I don't know what to think about this. For me, it is obvious the method is a setter, but this time I cannot set multiple values with just one call and there's the problem with getter again. I'm relatively new to Ruby and I haven't got used to all the idioms yet. So, what do you think guys? Which is the best option?

+4  A: 

You could also make a Margin class to enjoy the following clear syntax:

class Margin
    attr_accessor :left, :right, :top, :bottom
    ...
end

class Chart
    attr_accessor :margins
    ...
 end


chart.margins.left = 10
puts chart.margins.right
paradigmatic
I don't know. Maybe create a class for Margins is somewhat overkill. I'd prefer to have these values on a hash.
Lailson Bandeira
I do not think it is a good idea to use a hash here, because there are only 4 possible options. So if you write something like ':up => 10' instead of ':top => 10' you will experience bugs difficult to spot or add a switch in you function to validate all hash keys...
paradigmatic
+2  A: 

In addition to paradigmatic's answer, you could add a method to the Margins class to support:

chart.margins.set :left => 10, :right => 15

You could extend the margins= method to treat a numeric argument:

chart.margins = 20

as sugar for:

chart.margins = Margins.new(20, 20, 20, 20)
Douglas
Sure. I had thought about this: `charts.margins(:all, 20)`.
Lailson Bandeira
+3  A: 

Not so sure if this is the kind of syntax that you would like to make available (sorry if not : )

#!/usr/bin/ruby
class Margins < Struct.new(:top, :bottom, :left, :right) 
end

class Chart
  attr_reader :margins

  def initialize()
    @margins = Margins.new(0,0,0,0)
  end

  def margins=(hash)
    [:top, :bottom, :left, :right].each do |dir|
      if (hash[dir])
        @margins[dir] = hash[dir]
      end
    end
  end
end

c = Chart.new
c.margins.left = 10
c.margins={:top=>12,:bottom=>13}
puts c.margins.left
# 10
puts c.inspect;
# #<Chart:0xb7caaf8c @margins=#<struct Margins top=12, bottom=13, left=10, right=0>>

# However c.margins.foo = 12 would give you an error
phtrivier
Poderating a bit, I think something like `c.margins={:top=>12,:bottom=>13}` is ugly and can lead to confusion. And after all, I do not remeber to see anything like this on any Ruby code I've worked with.
Lailson Bandeira
There is also a syntax that would allow you to write c.set_margins(:top=>12, :bottom=>13) -- that is, you pass a hash to the function, but since it is the only argument, you don't have to write the curly braces. Would this look better to you ?
phtrivier
+2  A: 

I don't think creating a class for Margin is an overkill. You can always expose its values as a hash using to_hash or something similar.

Also, if you like, you can make it work in DSL-style:

chart.margins do |m|
  m.left 10
  m.right 20
  m.vertical 5 # sets both top and bottom margin
end

But I guess I would choose paradigmatic's approach anyway...

Mladen Jablanović
+1  A: 

You could also stick with what you had first and use the normal hash syntax.

margins["left"] = 10  #to set just one without changing the others
MattMcKnight