Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails app logic internationalization

I'm develping a rails app that will have separate instances run in different countries. Internationalization takes care of the language issues, but when there are some minor logical changes I'm not sure how to approach. One example would be the payment methods - in each country different options will be shown to the users with different integrations. Another examples would be salaries - taxes and social securities are completely different from country to country so we'll need to take this into account.

The differences are not that many so in general I don't want to create a different git branch for each country, but I might be wrong about that.

The solution I'm currently thinking of is to have a different environment for each country - "production_germany", "production_france" etc. and depending on that to load a different yml file with all the necessary variables that I'll be needing for this country. Then create some "Manager" class that will make decisions what special classes to load and what things to show on the views(wherever there are differences). For example I can have the salary calculation classes for germany in "salary_calculation/de" and for france in "salary_calculation/fr" and load only the ones I need.

What would be your solution?

like image 273
Kazmin Avatar asked Jul 29 '15 09:07

Kazmin


2 Answers

I would handle as much as possible using Rails' I18n. In case you haven't yet, also take a look at rails-i18n.

For your special cases that aren't handled by Rails, I would go about it like Rails does itself. For example, in your config/locales, you might have some configuration for taxes in a country:

en:
  taxes:
    name: Tax
    rate: 0.065
de:
  taxes:
    name: USt
    rate: 0.19

Then you write a TaxesHelper module that will use the information from the locale file to display the tax rate. Take a look at the Rails NumberHelper package.

If you actually do calculations with something like tax rates, I would persist the factor in the model where you're doing the calculation. That way your calculations won't be messed up when the tax rate changes. Say you have the following model:

class Invoice < ActiveRecord::Base
  # Injected by AR
  # attr_accessor :net_price
  # attr_accessor :tax_rate
  def gross_price
    net_price * tax_rate
  end

  before_create do
    tax_rate = I18n.t(:rate, scope: [ :taxes ]).to_f
  end
end

That way your gross price won't change for persisted invoices after the tax rate changes in a specific country.

like image 120
amiuhle Avatar answered Sep 18 '22 19:09

amiuhle


Amiuhle gave a good tip on how to handle static parameterization - languages, tax rates etc,

when you need more business logic then things get more complicated, and there's no 1-size-fits-all solution, just be sure to know tools at your disposal and make sure you fit the right one, and here's one more that seems to be a bit underused in the ruby community:

Dependency injection

For example: http://solnic.eu/2013/12/17/the-world-needs-another-post-about-dependency-injection-in-ruby.html

This pattern is typically good for managing payment provider gateways for example

class Transaction < ActiveRecord::Base
  def gateway_class
    I18n.t(:class_name, scope: [ :payment_provider ]).constantize
  end
  def gateway
    @gateway ||= gateway_class.new(self)
  end
end

the I18n parametrizes the payment provider class depending on the country:

en:
  payment_provider:
    class_name: PaymentProvider
de:
  payment_provider:
    class_name: AnotherPaymentProvider

and you make sure that the payment provider class answers to the same public API so for the whole app it's not necessary to know which payment provider is used, just that a payment provider class is interfaced.

Another good thing is that you can create a FakePaymentProvider class for testing and development which will speed up your tests a lot - even if you would normally use a web mocking tool such as vcr, and you can still use something like vcr to test integrations, and just integrations, decoupled from the rest of the app architecture.

Actually, take a look here: https://github.com/activemerchant/active_merchant somebody already did a bunch of work on that plan :)

like image 20
bbozo Avatar answered Sep 21 '22 19:09

bbozo