Robot Has No Heart

Xavier Shay blogs here

A robot that does not have a heart

You don't need view logic in models

Jake Scruggs wrote about moving view logic into his models

It’s hard to tell without knowing the full dataset, but my approach to these sort of problems is to reduce the data down to the simplest possible form (usually a hash), and then use an algorithm to extract what I need.

One commenter tried this and I think it’s heading in the right direction. There is potentially quite a lot of duplication here – the repetition of the layouts and scripts. To ease this it can sometimes be easier to inverse the key/values, for a more concise representation. You could reduce this even further if there were sensible defaults (if 90% of cars used a two_column layout, for instance) – just replace the raise in the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# See original post for context
# Data
layouts = {
  'two_column'   => [Toyota, Saturn],
  'three_column' => [Hyundai],
  'ford'         => [Ford]
}

scripts => {
  'discount' => [Hyundai, Ford],
  'poll'     => [Saturn]
}

# Algorithm
find_key = lambda {|hash, car| 
  (
    hash.detect {|key, types| 
      types.any? {|type| car.is_a?(type)}
      # types.include?(car.class) if you're not using inheritance
    } || raise("No entry for car: #{car}")
  ).first
}

layout = find_key[layouts, @car]
script = find_key[scripts, @car]

@stylesheets += ['layout', 'theme'].collect {|suffix| "#{layout}_#{suffix}.css" }
@scripts     += ["#{script}.js"]

render :action => find_view, :layout => layout

This is preferable to putting this data in your object hierarchy for all the normal reasons, especially since it keeps view logic where you expect to find it and doesn’t muddy up your models.

Hash trumps case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Two equivalent functions
def rgb(color)
  case color
    when :red   then 'ff0000'
    when :green then '00ff00'
    when :blue  then '0000ff'
    else             '000000' # Default to black
  end
end

def rgb2(color)
  {
    :red   => 'ff0000',
    :green => '00ff00',
    :blue  => '0000ff'
  }[color] || '000000'
end

Even though these functions are equivalent, the second carries more semantic weight – it maps a symbol directly to a color. The case sample makes no such guarantees since you can execute any arbitrary code in the then block. In addition, a hash is easier to work with – you can easily iterate over the keys, extract to another method if you need reuse, or query it for other properties (for example, 3 colors are available). It is also easier to read – both aesthetically and because it contains fewer tokens. In almost all circumstances I will prefer a hash over a case statement.

Relationships in data are easier to comprehend and manipulate than relationships in code.

A pretty flower Another pretty flower