4 class MigrationError < StandardError; end
5 class UntranslatedMigrationField < MigrationError; end
6 class MigrationMissingTranslatedField < MigrationError; end
7 class BadMigrationFieldType < MigrationError; end
11 def self.included(base)
12 base.extend ActMethods
16 def translates(*attr_names)
17 options = attr_names.extract_options!
18 options[:translated_attributes] = attr_names
20 # Only set up once per class
21 unless included_modules.include? InstanceMethods
22 class_inheritable_accessor :globalize_options, :globalize_proxy
24 include InstanceMethods
27 self.globalize_proxy = Globalize::Model::ActiveRecord.create_proxy_class(self)
29 :globalize_translations,
30 :class_name => globalize_proxy.name,
31 :extend => Extensions,
32 :dependent => :delete_all,
33 :foreign_key => class_name.foreign_key
36 after_save :update_globalize_record
39 self.globalize_options = options
40 Globalize::Model::ActiveRecord.define_accessors(self, attr_names)
42 # Import any callbacks that have been defined by extensions to Globalize2
45 Callbacks.instance_methods.each {|cb| send cb }
53 (defined?(@@locale) && @@locale) || I18n.locale
57 # Dummy Callbacks module. Extensions to Globalize2 can insert methods into here
58 # and they'll be called at the end of the translates class method.
62 # Extension to the has_many :globalize_translations association
64 def by_locales(locales)
65 find :all, :conditions => { :locale => locales.map(&:to_s) }
70 def method_missing(method, *args)
71 if method.to_s =~ /^find_by_(\w+)$/ && globalize_options[:translated_attributes].include?($1.to_sym)
72 find(:first, :joins => :globalize_translations,
73 :conditions => [ "#{i18n_attr($1)} = ? AND #{i18n_attr('locale')} IN (?)",
74 args.first,I18n.fallbacks[I18n.locale].map{|tag| tag.to_s}])
80 def create_translation_table!(fields)
81 translated_fields = self.globalize_options[:translated_attributes]
82 translated_fields.each do |f|
83 raise MigrationMissingTranslatedField, "Missing translated field #{f}" unless fields[f]
85 fields.each do |name, type|
86 unless translated_fields.member? name
87 raise UntranslatedMigrationField, "Can't migrate untranslated field: #{name}"
89 unless [ :string, :text ].member? type
90 raise BadMigrationFieldType, "Bad field type for #{name}, should be :string or :text"
93 translation_table_name = self.name.underscore + '_translations'
94 self.connection.create_table(translation_table_name) do |t|
95 t.references self.table_name.singularize
97 fields.each do |name, type|
104 def drop_translation_table!
105 translation_table_name = self.name.underscore + '_translations'
106 self.connection.drop_table translation_table_name
111 def i18n_attr(attribute_name)
112 self.base_class.name.underscore + "_translations.#{attribute_name}"
116 module InstanceMethods
117 def reload(options = nil)
120 # clear all globalized attributes
121 # TODO what's the best way to handle this?
122 self.class.globalize_options[:translated_attributes].each do |attr|
123 @attributes.delete attr.to_s
130 @globalize ||= Adapter.new self
133 def update_globalize_record
134 globalize.update_translations!
137 def translated_locales
138 globalize_translations.scoped(:select => 'DISTINCT locale').map {|gt| gt.locale.to_sym }
141 def set_translations options
142 options.keys.each do |key|
144 translation = globalize_translations.find_by_locale(key.to_s) ||
145 globalize_translations.build(:locale => key.to_s)
146 translation.update_attributes!(options[key])