Skip to content
daniel-rikowski edited this page Jun 30, 2013 · 13 revisions

Idea

I've been thinking a lot about the issues that delocalize currently has and has had in the past.

Lots of the issues that we've had with delocalize over the years seem to have come from the fact that it is just too involved with Rails inner workings – ActiveRecord/ActiveModel in particular. The tight coupling to AR/AM is particularly worrying since i18n/l10n are frontend rather than backend concerns: It shouldn't be the model layer's responsibility to parse user input and since delocalize is currently implemented in the model layer, it is doing things on the wrong level.

I think the issue is similar to what we've seen with attr_accessible/attr_protected and the new strong_parameters gem as well as issues outlined by various people about putting business logic into ActiveModel callbacks (e.g. the famous example where creating a user always sends a confirmation email). Hence, I think the solution to the problem at hand is similar: We need to move delocalize from the model to the controller layer.

I think, delocalize could be used for parsing and converting the params hash directly. The syntax could actually be quite similar to the spike branch:

class Admin::ProductsController < Admin::BaseController
  delocalize :product => { :price => :number, :available_at => :datetime }
end

delocalize would then be executed as a before_filter, check if params[:product] is set and finally check for the presence of price and available_at and convert them as usual.

Regarding the localization of stored data (i.e. forms), delocalize could store the data based on conventions and use them in forms automatically. For cases where conventions can't be followed, the data could be exposed via a helper method. I'm thinking along the following lines:

  • A model Product has a numeric field price and an available_at datetime (just like in the example above).
  • If the controller uses the delocalize filter mentioned above, the localized values are automatically stored in a hash that is accessible using a helper method.
  • This helper method could provide an API like this: delocalize_localized_value(@product, :price). This would result in a lookup like delocalize_localized_values[:products]['1'][:price] (1 is the primary key).
  • If one needs to break the convention, the helper method can be used manually.

While I acknowledge that this means more setup compared to the original implementation (even more than the spike branch), it has the benefit of being much less involved with Rails' inner workings and thus being much less brittle. Also, delocalizing could be done on a case by case basis (e.g. not using it in API controllers where programmers can be trusted/forced to use certain formats) and even customized as necessary just like any other before_filter.

Also note that I haven't quite figured out the localization part – so consider this work in progress and feel free to share your thoughts on that part.

What are your opinions on this?

Please append your opinions to this wiki page and include your GitHub username followed by the date and your thoughts. Thanks!

Your thoughts

@noniq (22 April 2013)

I think that's exactly the right thing to do, so +1 from me. (Last time I struggled with some problem caused by delocalizing in the model layer, I tried to rethink the whole concept and came to conclusion very similar to yours … never got around to implement it, though :-| )

@daniel-rikowski (30 June 2013)

I also think this is the way to go. I hacked together a quick-and-dirty Rails controller concern which implements this functionality:

module Delocalizer
  extend ActiveSupport::Concern
  
  module ClassMethods
    def delocalize(attributes)
      before_filter -> { delocalize_params(params, attributes) }
    end
  end

  private
  def delocalize_params(params, attributes)
    attributes.each do |key, value|
      next unless params.key?(key)
      if [:date, :time, :numeric].include?(value)
        params[key] = value.to_s.classify.constantize.parse_localized(params[key])
      else
        delocalize_params(params[key], value)
      end
    end
  end
end

This also works with nested attributes:

  delocalize item: { amount: :numeric, effective_date: :time,
                     transaction_attributes: { amount: :numeric, effective_date: :time } }

I guess this still needs some work, but currently I'm using the above code in production (except for some syntactic sugar provided by Rails 4.0 and Ruby 2.0 )

One additional thought: I think it would be possible to derive the data types by looking at the models (Model.columns_hash['my_column_name'].type) That way the delocalize statement could be condensed and it would look a lot like strong_parameters code:

delocalize item: [:amount, :effective_date, transaction_attributes: [:amount, :effective_date]]

But that would definitely be a lot more code and would also introduce additional work for other ORM adapters like Mongoid etc, so I'm not sure if this is worth the effort.

Clone this wiki locally