Conor Owen
Conor Owen

Reputation: 107

How could metaprogramming be used to reduce redundancy in this Ruby code?

class Device
  def initialize(device_id, data_resource)
    @id = device_id
    @data_resource = data_resource
  end

  def display_device
    mode = @data_resource.get_display_device_mode(@id)
    presets = @data_resource.get_display_device_presets(@id)
    summary = "display_device: #{mode} ($#{presets})"
    return "* #{summary}" if presets == "XTC909"
    summary
  end

  def chip
    mode = @data_resource.get_chip_mode(@id)
    presets = @data_resource.get_chip_presets(@id)
    summary = "chip: #{mode} ($#{presets})"
    return "* #{summary}" if presets == "XTC909"
    summary
  end

  def input_device
    mode = @data_resource.get_input_device_mode(@id)
    presets = @data_resource.get_input_device_presets(@id)
    summary = "input_device: #{mode} ($#{presets})"
    return "* #{summary}" if presets == "XTC909"
    summary
  end

end

As you can see from the code above, there is quite a bit of redundancy within the methods. Regardless of whether metaprogramming is the best way to reduce this redundancy, I am hoping to learn how to use metaprogramming in Ruby to reduce some of the repetitiveness here if someone could provide some suggestions.

Upvotes: 4

Views: 410

Answers (6)

Pablo Herrero
Pablo Herrero

Reputation:

I guess u probably solve this alreaday, anyway this is my alternative:

class Device
  def initialize(device_id, data_resource)
    @id,@data_resource = device_id, data_resource
  end

  %w{display_device chip input_device}.each do |met|
    define_method met do  
      mode = @data_resource.send("get_#{met}_mode", @id)
      presets = @data_resource.send("get_#{met}_presets",@id)
      summary = "#{met}: #{mode} ($#{presets})"
      return "* #{summary}" if presets == "XTC909"
      summary
    end
  end
end

Upvotes: 0

madlep
madlep

Reputation: 49796

Something like this could work so you can define 'components' (or whatever they are) declaratively. This is overkill for this sort of example, but you can use it when you need to define dozens/hundreds of these things, or you're putting it as part of some framework (like rails does).

The component class level method would usually live in some other module that gets included into the class rather than declaring it inline where it's used like this.

class Device

  class << self 
    def component(component_name)
      define_method(component_name) do
        mode = @data_resource.send("get_#{component_name}_mode", @id)
        presets = @data_resource.send("get_#{component_name}_presets", @id)
        summary = "#{component_name} : #{mode} ($#{presets})"
        presets == "XTC909" ? "* #{summary}" : summary
      end
    end
  end

  component :display_device
  component :chip
  component :input_device

  def initialize(device_id, data_resource)
    @id = device_id
    @data_resource = data_resource
  end
end

You can drive it with something like:

class DataResource
  def method_missing(method, *args)
    # puts "called #{method} with:#{args.inspect}"
    "#{method}-#{args.join(':')}"
  end
end

device = Device.new("ID123", DataResource.new)
puts device.display_device
puts device.chip
puts device.input_device

Upvotes: 3

Chuck
Chuck

Reputation: 237110

Here's a version that uses metaprogramming, though I'd also remove the duplication by putting it in a method where it belongs.

class Device
  def initialize(device_id, data_resource)
    @id = device_id
    @data_resource = data_resource
  end

  def resource_summary(resource_name)
    mode = @data_resource.send("get_#{resource_name}_mode", @id)
    presets = @data_resource.send("get_#{resource_name}_presets", @id)
    summary = "#{resource_name}: #{mode} ($#{presets})"
    return "* #{summary}" if presets == "XTC909"
    summary
  end

  def self.resource_accessor(*names)
    names.each {|resource| define_method(resource) {resource_summary resource}}
  end

  resource_accessor :display_device, :chip, :input_device
end

If you really didn't want to make a method for that functionality, you could just replace the resource_summary method call with the body of the resource_summary method.

Upvotes: 6

Matt Grande
Matt Grande

Reputation: 12157

Obviously, some names should change...

def display_device
  i_heart_meta_programming("display_device")
end

def chip
  i_heart_meta_programming("chip")
end

def input_device
  i_heart_meta_programming("input_device")
end

def i_heart_meta_programming(what_to_get)
  mode = @data_resource.send("get_#{what_to_get}_mode", @id)
  mode = @data_resource.send("get_#{what_to_get}_presets", @id)
  summary = "#{what_to_get}: #{mode} ($#{presets})"
  return "* #{summary}" if presets == "XTC909"
  summary
end

Upvotes: 2

thedz
thedz

Reputation: 5572

Can you come up with a better example?

As I said your previous version of this, metaprogramming is hardly needed here. Basic encapsulation of functionality in methods would work.

Any examples that people give would be contrived and not really representative of real world usage of metaprogramming.

Upvotes: -1

Colen
Colen

Reputation: 13918

Are you sure that you need to reduce redundancy here at all? It's certainly possible, but anything you do will just make the code harder to understand and will not necessarily be a net win.

Upvotes: 0

Related Questions