Skip to content

Instantly share code, notes, and snippets.

@karmi
Created November 25, 2008 11:34
Show Gist options
  • Select an option

  • Save karmi/28889 to your computer and use it in GitHub Desktop.

Select an option

Save karmi/28889 to your computer and use it in GitHub Desktop.
I ♥ Ruby so much...
class CreateCategories < ActiveRecord::Migration
def self.up
create_table :categories do |t|
t.string :name_en,
:name_cz,
:name_es
t.timestamps
end
end
def self.down
drop_table :categories
end
end
class CreateProducts < ActiveRecord::Migration
def self.up
create_table :products do |t|
t.string :name
t.text :description
t.datetime :manufactured_on
t.timestamps
end
end
def self.down
drop_table :products
end
end
class CreateTranslations < ActiveRecord::Migration
def self.up
create_table :translations do |t|
t.string :name
t.text :description
t.string :locale
t.string :translatable_type
t.integer :translatable_id
t.timestamps
end
add_index :translations, [:locale, :translatable_id, :translatable_type], :name => "lang_index", :unique => true
end
def self.down
drop_table :translations
end
end
class Category < ActiveRecord::Base
I18n.backend.send(:init_translations) if I18n.available_locales.blank?
self.class_eval do
Category.columns_hash.collect { |c| c[0] if c[1].type == :string or c[1].type == :text }.compact.each do |attribute_name|
puts attribute_name
if includes_locale = I18n.available_locales.find { |locale| attribute_name =~ Regexp.new("_#{locale}$") }
method_name = attribute_name.sub( Regexp.new("(\w*)_#{includes_locale}$"), '\1' )
unless method_defined?(method_name.to_sym)
# Use like this:
# c = Category.first
# c.name_en
# => "Category one"
# c.name
# => "Category one"
# I18n.locale = 'es'
# c.name
# => "Categoria uno"
puts "Defining localized getter '#{method_name}'"
define_method(method_name) do |*args|
lookup_locale = args.shift || I18n.locale
puts "Arguments: #{args.inspect}"
puts "Lookup locale: #{lookup_locale}"
return @attributes["#{method_name}_#{lookup_locale}"]
end
# And now try this
# c = Category.first
# I18n.locale = 'es'
# c.name
# => "Categoria uno"
# c.name = 'Pues, vamos a modificar este registro!'
# c.save
# >> UPDATE `categories` SET `name_es` = 'Pues, vamos a modificar este registro!' ... WHERE ...
puts "Defining localized setter '#{method_name}='"
define_method("#{method_name}=") do |args|
raise ArgumentError,
"argument must be a String, like 'Salut' or a hash of values, like :es => '¡Hola!'" unless args.is_a?(String) || args.is_a?(Hash)
locale = args.is_a?(Hash) ? args.keys.first : I18n.locale
value = args.is_a?(Hash) ? args.values.first : args
puts "Arguments: #{args.inspect}"
puts "Lookup locale: #{locale}"
self.send(:"#{method_name}_#{locale}=", value) if self.respond_to?(:"#{method_name}_#{locale}")
end
end
end
end
end #:nodoc: class_eval
end #:nodoc: class
class Product < ActiveRecord::Base
# This is really it
has_many :translations, :as => :translatable
self.class_eval do
Product.columns_hash.collect { |c| c[0] if c[1].type == :string or c[1].type == :text }.compact.each do |attribute_name|
# Use like this:
# p = Product.first
# p.name
# => "Product two"
# I18n.locale = :cz
# p.name
# => "Produkt 2"
# I18n.locale = :en
# p.name(:cz)
# => "Produkt 2"
puts "Re-defining method '#{attribute_name}' for #{self.class.name}"
define_method(attribute_name) do |*args|
lookup_locale = args.shift || I18n.locale
puts "Arguments: #{args.inspect}"
puts "Lookup locale: #{lookup_locale}"
return @attributes[attribute_name] if lookup_locale.to_s == I18n.default_locale.to_s # Do not lookup anything if passed locale is default
if translation = self.translations.find_by_locale(lookup_locale.to_s)
translation.attributes[attribute_name]
end
end
# This is needed so Rails form helpers like f.text_field :name actually work. They call _before_type_cast.
# See http://github.com/rails/rails/tree/2-2-stable/actionpack%2Flib%2Faction_view%2Fhelpers%2Fform_helper.rb#L649-655
alias :"#{attribute_name}_before_type_cast" :"#{attribute_name}"
# And now try this:
# p = Product.first
# p.name
# => "Product two"
# p.name = 'This is product two'
# p.save
# p.reload.name
# => "This is product two"
# I18n.locale = 'cz'
# p.name
# => "Produkt 2"
# p.name = 'Produkt dvě'
# p.save
# p.reload.name
# => "Produkt dvě"
# p.name={:es => 'Esto es un nombre de producto dos'}
# p.save
# >> INSERT INTO `translations` (`name` ... `locale` ...) VALUES('Esto es un nombre de producto dos' ... 'es' ...)
# p.reload.name
# => "Produkt dvě"
# p.name(:es)
# => "Esto es un nombre de producto dos"
puts "Re-defining method '#{attribute_name}=(value)' #{self.class.name}"
define_method("#{attribute_name}=") do |args|
puts "Arguments: #{args.inspect}"
raise ArgumentError,
"argument must be a String, like 'Salut' or a hash of values, like :es => '¡Hola!'" unless args.is_a?(String) || args.is_a?(Hash)
# TODO : Allow passing more locales and values at once, like this <tt>p.name={ :cz => 'Ahoj', :es => 'Hola' }</tt>
locale = args.is_a?(Hash) ? args.keys.first : I18n.locale
value = args.is_a?(Hash) ? args.values.first : args
puts "Write to locale: #{locale}"
puts "Value: #{value}"
super(value.to_s) and return if locale.to_s == I18n.default_locale.to_s
raise ActiveRecord::RecordNotSaved, "you cannot add translations to unsaved record" if new_record?
if translation = self.translations.find_by_locale(locale.to_s)
translation.update_attribute(attribute_name.to_sym, value.to_s)
else
self.translations.create!(:locale => locale.to_s, attribute_name.to_s => value.to_s)
end
end
end
end #:nodoc: class_eval
# Returns true if object has translation for passed locale
def has_translation?(locale)
!translations.find_by_locale(locale.to_s).nil?
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment