views:

792

answers:

7

Lets say you have a model like the following:

class Stock < ActiveRecord::Base
  # Positions
  BUY = 1
  SELL = 2
end

And in that class as an attribute of type integer called 'position' that can hold any of the above values. What is the Rails best practice for converting those integer values into human readable strings?

a) Use a helper method, but then you're force to make sure that you keep the helper method and model in sync

def stock_position_to_s(position)
  case position
  when Stock::BUY
    'buy'
  when Stock::SELL
    'sell'
  end
  ''
end

b) Create a method in the model, which sort of breaks a clean MVC approach.

class Stock < ActiveRecord::Base
  def position_as_string
    ...snip
  end
end

c) A newer way using the new I18N stuff in Rails 2.2?

Just curious what other people are doing when they have an integer column in the database that needs to be output as a user friendly string.

Thanks, Kenny

+3  A: 

Sounds to me like something that belongs in the views as it is a presentation issue.

If it is used widely, then in a helper method for DRY purposes, and use I18N if you need it.

frankodwyer
A: 

Is there a good reason for the app be converting the integer to the human readable string programmatically?

I would make the positions objects which have a position integer attribute and a name attribute.

Then you can just do

stock.position.name
DanSingerman
A: 

@HermanD: I think it's a lot better to store the values in an integer column rather than a string column for numerous reasons.

  1. It saves database space.
  2. Easier/faster to index on an integer than a string.
  3. Your not hard coding a human readable string as values in a database. (What happens if the client says that "Buy" should become "Purchase"? Now the UI shows "Purchase" everywhere but you need to keep setting "Buy" in the database.)

So, if you store certain values in the database as integers, then at some point, you're going to need to show them to the user as strings, and I think the only way you can do that is programatically.

You could move this info into another object but, IMHO, I'd say this is overkill. You'd then have to add another database table. Add another 'admin' section for adding, removing and renaming these values and so on. Not to mention that if you had several columns, in different models that needed this behavior, you'd either have to create lots of these objects (ex: stock_positions, stock_actions, transaction_kinds, etc...) or you'd have to design it generically enough to use polymorphic associations. Finally, if the position name is hard coded, then you lose the ability to easy localize it at a later date.

@frankodwyer: I'd have to agree that using a helper method is probably the best way to go. I was hoping their might be a "slicker" way to do this, but it doesn't look like it. For now, I think the best method is to create a new helper module, maybe something like StringsHelper, and stuff a bunch of methods in their for converting model constants to strings. That way I can use all the I18N stuff in the helper to pull out the localized string if I need to in the future. The annoying part is that if someone needs to add a new value to the models column, then they will also have to add a check for that in the helper. Not 100% DRY, but I guess "close enough"...

Thanks to both of you for the input.

Kenny

Kenny C
Sorry - you misunderstood me - Stocks would still be a table with an integer PK which you would use in your app, It would just also have a description attribute that was text.For my money I would rather store the 1 as 'Buy' in the database, not code; then you could write an admin tool for it...
DanSingerman
..if you wanted to, but it would be by no means necessary. If in code you will never be able to change it without a code release.
DanSingerman
A: 

Why not use the properties of a native data structure? example:

class Stock < ActiveRecord::Base ACTIONS = [nil,'buy','sell'] end

Then you could grab them using Stock::ACTIONS[1] #=> 'buy' or Stock::ACTIONS[2] #=> 'sell'

or, you could use a hash {:buy => 1, :sell => 2} and access it as Stock::ACTIONS[:buy] #=> 1

you get the idea.

Derek P.
A: 

@Derek P. That's the implementation I first went with and while it definitely works, it sort of breaks the MVC metaphor because the model, now has view related info defined in its class. Strings in controllers are one thing, but strings in models (in my opinion) are definitely against the spirit of clean MVC.

It also doesn't really work if you want to start localizing, so while it was the method I originally used, I don't think it's the method for future development (and definitely not in an I18N world.)

Thanks for the input though.

Sincerely, Kenny

Kenny C
+1  A: 

Try out something like this

class Stock < ActiveRecord::Base
  @@positions => {"Buy" => 1, "Sell" => 2}
  cattr_reader :positions

  validates_inclusion_of :position,  :in => positions.values
end

It lets you to save position as an integer, as well as use select helpers easily.

Of course, views are still a problem. You might want to either use helpers or create position_name for this purpose method

class Stock < ActiveRecord::Base
  @@positions => {"Buy" => 1, "Sell" => 2}
  cattr_reader :positions

  validates_inclusion_of :position,  :in => positions.values

  def position_name
    positions.index(position)
  end
end
maurycy
I like this approach, but would like to have position_name return the string index that l18n could use, then store the actual "Buy" and "Sell" in the en.yml file.
csexton
Its up to you.You might want to use I18n in the @@positions. From what I understand (have not read too much about new Rails i18n, yet), it doesn't violate any design pattern, as model's validation is translated on the ActiveRecord layer as well.
maurycy
A: 

I wrote a plugin that may help a while ago. See this. It lets you define lists and gives you nice methods ending in _str for display purposes.

Ghoti