Changeset 129

Show
Ignore:
Timestamp:
05/03/07 20:54:36 (5 years ago)
Author:
saimon
Message:

--Adding Via Association storage method based on implementation by Chris hapgoods. Modified to correct :order/:group conditions, provide support for dynamic finders, bidi, fallbacks, write to same table if Locale.base?, destroy translations if translated attribute set to nil.

All tests are currently passing. (However, only been tested on MySQL atm.)

I see this as valid replacement for the current single_table storage method as the code is simpler to maintain. If this code replaces the current external table storage method then it makes implementing Josh's caching idea a lot simpler, however the existing translates_external code has had a lot more time in the field. Solution add a lot more tests.

Note: Currently, the code added uses the existing schema to provide compatibility with all 3 storage engines while we decide what to do.
The db_translate_new.rb file contains the same functionality as db_translate.rb but depends on a modified schema for globalize_translations where by table_name => item_type. i.e It's converted to a ploymorphic class indicator. This makes the implementation of Via Association storage method slightly simpler.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/saimon/trunk_with_rfc4646_support/lib/globalize/localization/db_translate.rb

    r122 r129  
    331331          when :single_table, :external 
    332332            translate_external(facets, options) 
     333          when :via_association 
     334            translate_via_association(facets, options) 
    333335          else 
    334336            raise "[Globalize::DbTranslate]" + 
     
    373375 
    374376      protected 
     377 
     378        # Alternative access methodology using associations.  Preserves standard find behavior. 
     379        # <i>i.e. Globalize::DbTranslate.translation_method = :via_association</i> 
     380        def translate_via_association(facets, options) 
     381          class_eval <<-HERE 
     382            @@facet_options = {} 
     383            @@translation_reflections = [] 
     384            @@translation_dyn_reflections = [] 
     385            @@aliases = {} 
     386            @@dyn_table_aliases = {} #Used for dynamic finders 
     387            @@facets = facets 
     388            has_many :translations, :class_name => 'Globalize::ModelTranslation', 
     389                  :conditions => ['globalize_translations.table_name = ?', table_name], 
     390                  :foreign_key => 'item_id', 
     391                  :dependent => :delete_all 
     392 
     393            class << self 
     394              alias_method :untranslated_find, :find 
     395              def find(*args) 
     396                options = args.last.is_a?(Hash) ? args.pop : {} 
     397                if (options.has_key?(:untranslated) && options[:untranslated] == true) 
     398                  args << options 
     399                  untranslated_find(*args) 
     400                else 
     401                  unless Locale.base? 
     402                    @@facets.each do |f| 
     403                      # Replaces table.untranslated_field or just untranslated_field with globalize_translations.text 
     404                      r = Regexp.new("(\\\\A|\\\\s)(#\{table_name\}.#\{f\}|#\{f\})(\\\\z|\\\\s)") 
     405                      options[:order].sub!(r, "\\\\1" + @@aliases[f] + "\\\\3") if options[:order] 
     406                      options[:group].sub!(r, "\\\\1" + @@aliases[f] + "\\\\3") if options[:group] 
     407                      options[:conditions].sub!(r, "\\\\1" + @@aliases[f] + "\\\\3") if options[:conditions].is_a?(String) 
     408                    end 
     409 
     410                    #If order is specified, only return results for current locale 
     411                    if options[:order] 
     412                      options[:conditions] ||= '' 
     413                      case options[:conditions] 
     414                        when String 
     415                          options[:conditions] << " AND" unless options[:conditions].empty? 
     416                          options[:conditions] << " globalize_translations.language_id = #\{Locale.language.id}" 
     417                        when Array 
     418                          options[:conditions].first << " AND" unless options[:conditions].first.empty? 
     419                          options[:conditions].first << " globalize_translations.language_id = #\{Locale.language.id}" 
     420                        else 
     421                          #Unsupported 
     422                          nil 
     423                      end 
     424                    end 
     425                  end 
     426 
     427                  options[:include] = ([:translations] << options[:include]).compact.flatten 
     428                  args << options 
     429                  untranslated_find(*args) 
     430                end 
     431              end 
     432            end 
     433 
     434            def add_bidi(value, facet) 
     435              return value unless Locale.active? 
     436              value.direction = self.send("\#{facet}_is_base?") ? 
     437                (Locale.base_language ? Locale.base_language.direction : nil) : 
     438                (Locale.active ? Locale.language.direction : nil) 
     439 
     440                # insert bidi embedding characters, if necessary 
     441                if @@facet_options[facet][:bidi] && 
     442                    Locale.language && Locale.language.direction && value.direction 
     443                  if Locale.language.direction == 'ltr' && value.direction == 'rtl' 
     444                    bidi_str = "\xe2\x80\xab" + value + "\xe2\x80\xac" 
     445                    bidi_str.direction = value.direction 
     446                    return bidi_str 
     447                  elsif Locale.language.direction == 'rtl' && value.direction == 'ltr' 
     448                    bidi_str = "\xe2\x80\xaa" + value + "\xe2\x80\xac" 
     449                    bidi_str.direction = value.direction 
     450                    return bidi_str 
     451                  end 
     452                end 
     453                return value 
     454            end 
     455 
     456            extend  Globalize::DbTranslate::ViaAssociationStorageClassMethods 
     457 
     458          HERE 
     459 
     460          facets.each do |facet| 
     461            bidi = (!(options[facet] && !options[facet][:bidi_embed])).to_s 
     462            fallback = (options[facet] && options[facet].key?(:fallback)) ? 
     463                         options[facet][:fallback] : 
     464                         options[:fallback] 
     465            base_as_default = (options[facet] && options[facet].key?(:base_as_default)) ? 
     466                         options[facet][:base_as_default] : 
     467                         options[:base_as_default] 
     468 
     469            class_eval <<-HERE 
     470              @@facet_options[:#{facet}] ||= {} 
     471              @@facet_options[:#{facet}][:bidi] = #{bidi} 
     472              @@facet_options[:#{facet}][:fallback] = #{fallback} 
     473              @@facet_options[:#{facet}][:base_as_default] = #{base_as_default} 
     474 
     475              @@translation_reflections << :#{facet}_translations 
     476              @@translation_dyn_reflections << :#{facet}_dyn_translations 
     477 
     478              case @@dyn_table_aliases.size 
     479                when 0 
     480                  @@dyn_table_aliases[:#{facet}] = "globalize_translations" 
     481                else 
     482                  @@dyn_table_aliases[:#{facet}] = "#{facet}_dyn_translations_" + self.table_name 
     483              end 
     484 
     485              case @@aliases.size 
     486                when 0 
     487                  @@aliases[:#{facet}] = "globalize_translations.text" 
     488                when 1 
     489                  @@aliases[:#{facet}] = self.to_s.tableize + "_globalize_translations.text" 
     490                else 
     491                  @@aliases[:#{facet}] = self.to_s.tableize + "_globalize_translations" + "2" + ".text" # this is a guess 
     492              end 
     493 
     494              has_many :#{facet}_translations, :class_name => '::Globalize::ModelTranslation', 
     495                    :conditions => ['globalize_translations.table_name = ? AND globalize_translations.facet = ?', table_name, "#{facet}"], 
     496                    :foreign_key => 'item_id', 
     497                    :dependent => :delete_all 
     498 
     499              #Used exclusively for globalizing dynamic finders (see method_missing) 
     500              has_many :#{facet}_dyn_translations, :class_name => '::Globalize::ModelTranslation', 
     501                    :conditions => [@@dyn_table_aliases[:#{facet}] + '.table_name = ? AND ' + @@dyn_table_aliases[:#{facet}] + '.facet = ?', table_name, "#{facet}"], 
     502                    :foreign_key => 'item_id', 
     503                    :dependent => :delete_all 
     504 
     505 
     506              def #{facet} 
     507                unless Locale.base? 
     508                  translation = self.#{facet}_translations.detect{|tr| tr.language_id == Locale.language.id } 
     509 
     510                  unless translation 
     511                    if @@facet_options[:#{facet}][:fallback] 
     512                      Locale.active.possible_languages.each do |fallback| 
     513                        unless Locale.base_language.code == fallback.code 
     514                          translation = self.#{facet}_translations.detect{|tr| tr.language_id == fallback.id } 
     515                          break if translation 
     516                        else 
     517                          translation = read_attribute(:#{facet}) 
     518                          break if translation 
     519                        end 
     520                      end 
     521                    end 
     522                  end 
     523 
     524                  unless translation 
     525                    if @@facet_options[:#{facet}][:base_as_default] 
     526                      translation = read_attribute(:#{facet}) 
     527                    end 
     528                  end 
     529                  result = translation.nil? ? nil : (translation.kind_of?(::Globalize::ModelTranslation) ? translation.text : translation) 
     530                else 
     531                  result = read_attribute(:#{facet}) 
     532                  #If the base locale value is nil and fallback is active... 
     533                  unless result 
     534                    if @@facet_options[:#{facet}][:fallback] 
     535                      Locale.active.possible_languages.each do |fallback| 
     536                        unless Locale.base_language.code == fallback.code 
     537                          translation = self.#{facet}_translations.detect{|tr| tr.language_id == fallback.id } 
     538                          break if translation 
     539                        end 
     540                      end 
     541                      result = translation.nil? ? nil : (translation.kind_of?(::Globalize::ModelTranslation) ? translation.text : translation) 
     542                    end 
     543                  end 
     544                end 
     545 
     546                result.nil? ? nil : add_bidi(result, :#{facet}) 
     547              end 
     548              alias :#{facet}_before_type_cast :#{facet} 
     549 
     550              def #{facet}=(value) 
     551                raise 'Globalize::Locale not active.' unless Locale.active? 
     552                unless Locale.base? 
     553                  language = Locale.language 
     554                  translation = self.#{facet}_translations.detect{|tr| tr.language_id == language.id } 
     555                  if value 
     556                    if translation.nil? 
     557                      translation = self.#{facet}_translations.build(:table_name => self.class.table_name, 
     558                          :item_id => self.id, :facet => "#{facet}", :language_id => language.id) 
     559                    end 
     560                    translation.text = value 
     561                    translation.save! unless translation.new_record? 
     562                  else 
     563                    translation.destroy && self.#{facet}_translations(true) if translation 
     564                  end 
     565                else 
     566                  write_attribute(:#{facet}, value) 
     567                end 
     568              end 
     569 
     570              def #{facet}_is_base? 
     571                unless Locale.base? 
     572                  translation = self.#{facet}_translations.detect{|tr| tr.language_id == Locale.language.id } 
     573                  result = translation.nil? 
     574                else 
     575                  true 
     576                end 
     577              end 
     578 
     579            HERE 
     580          end 
     581        end 
    375582 
    376583        #Alternative storage mechanism storing the translations in the models 
     
    11011308      end 
    11021309    end 
     1310 
     1311    module ViaAssociationStorageClassMethods 
     1312 
     1313      private 
     1314 
     1315      # Overridden to ensure that dynamic finders using localized attributes 
     1316      # like find_by_user_name(user_name) or find_by_user_name_and_password(user_name, password) 
     1317      # use the appropriately localized column. 
     1318      # 
     1319      # Note: <i>Used when Globalize::DbTranslate.storage_method = :same_table</i> 
     1320      def method_missing(method_id, *arguments) 
     1321        if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s) 
     1322          finder, deprecated_finder = determine_finder(match), determine_deprecated_finder(match) 
     1323 
     1324          facets = extract_attribute_names_from_match(match) 
     1325          super unless all_attributes_exists?(facets) 
     1326 
     1327          attributes = construct_attributes_from_arguments(facets, arguments) 
     1328 
     1329          unless Locale.base? 
     1330            conditions = [] 
     1331            keys = attributes.keys.collect do |k| 
     1332               replaced = self.send(:class_variable_get, :@@dyn_table_aliases)[k.intern] 
     1333               replaced ? "#{replaced}.text" : k 
     1334            end 
     1335            conditions << keys.collect {|k| "#{k} = ?"}.join(' AND ') 
     1336            conditions += attributes.values 
     1337 
     1338            attributes = conditions 
     1339          end 
     1340 
     1341          extra_options = arguments[facets.size] 
     1342          extra_options ||= {} 
     1343          includes = self.send(:class_variable_get, :@@translation_dyn_reflections).dup 
     1344          extra_options[:include] = (includes << extra_options[:include]).compact.flatten 
     1345 
     1346          case extra_options 
     1347            when nil 
     1348              options = { :conditions => attributes } 
     1349              set_readonly_option!(options) 
     1350              ActiveSupport::Deprecation.silence { send(finder, options) } 
     1351 
     1352            when Hash 
     1353              finder_options = extra_options.merge(:conditions => attributes) 
     1354              validate_find_options(finder_options) 
     1355              set_readonly_option!(finder_options) 
     1356 
     1357              if extra_options[:conditions] 
     1358                with_scope(:find => { :conditions => extra_options[:conditions] }) do 
     1359                  ActiveSupport::Deprecation.silence { send(finder, finder_options) } 
     1360                end 
     1361              else 
     1362                ActiveSupport::Deprecation.silence { send(finder, finder_options) } 
     1363              end 
     1364 
     1365            else 
     1366              ActiveSupport::Deprecation.silence do 
     1367                send(deprecated_finder, sanitize_sql(attributes), *arguments[facets.length..-1]) 
     1368              end 
     1369          end 
     1370        elsif match = /find_or_(initialize|create)_by_([_a-zA-Z]\w*)/.match(method_id.to_s) 
     1371          instantiator = determine_instantiator(match) 
     1372          facets = extract_attribute_names_from_match(match) 
     1373          super unless all_attributes_exists?(facets) 
     1374 
     1375          attributes = construct_attributes_from_arguments(facets, arguments) 
     1376 
     1377          unless Locale.base? 
     1378            conditions = [] 
     1379            keys = attributes.keys.collect do |k| 
     1380               replaced = self.send(:class_variable_get, :@@dyn_table_aliases)[k.intern] 
     1381               replaced ? "#{replaced}.text" : k 
     1382            end 
     1383            conditions << keys.collect {|k| "#{k} = ?"}.join(' AND ') 
     1384            conditions += attributes.values 
     1385 
     1386            attributes = conditions 
     1387          end 
     1388 
     1389          includes = self.send(:class_variable_get, :@@translation_dyn_reflections).dup 
     1390          options = { :conditions => attributes, :include => includes} 
     1391          set_readonly_option!(options) 
     1392 
     1393          find_initial(options) || send(instantiator, attributes) 
     1394        else 
     1395          super 
     1396        end 
     1397      end 
     1398    end #ViaAssociationStorageClassMethods 
     1399 
    11031400  end 
    11041401end 
  • branches/saimon/trunk_with_rfc4646_support/test/fixtures/globalize_products.yml

    r100 r129  
    4040  name_es: oreja 
    4141  name_he: סאךי 
     42  description: ear description 
  • branches/saimon/trunk_with_rfc4646_support/test/fixtures/globalize_simples.yml

    r100 r129  
    88  description_es: Esta es una descripcion del primer simple 
    99  description_he: זהו התיאוך הךאשון 
     10second_simple: 
     11  id: 2 
     12  name: first 
     13  name_es: primer 
     14  name_he: זהו השם הךאשון 
     15  description: This is a description of the second simple 
     16  description_es: Esta es una descripcion del segundo simple 
     17  description_he: זהו התיאוך הךאשון 
     18third_simple: 
     19  id: 3 
     20  name: first 
     21  name_es: primer 
     22  name_he: זהו השם הךאשון 
     23  description: This is a description of the third simple 
     24  description_es: Esta es una descripcion del tercer simple 
     25  description_he: זהו התיאוך הךאשון 
     26fourth_simple: 
     27  id: 4 
     28  name: last 
     29  name_es: ultimo 
     30  name_he: זהו השם הךאשון 
     31  description: This is a description of the fourth simple 
     32  description_es: Esta es una descripcion del cuarto simple 
     33  description_he: זהו התיאוך הךאשון 
     34fifth_simple: 
     35  id: 5 
     36  name: last 
     37  name_es: ultimo 
     38  name_he: זהו השם הךאשון 
     39  description: This is a description of the fifth simple 
     40  description_es: Esta es una descripcion del quinto simple 
     41  description_he: זהו התיאוך הךאשון 
  • branches/saimon/trunk_with_rfc4646_support/test/fixtures/globalize_translations.yml

    r118 r129  
    460460  pluralization_index: 2 
    461461  text: "%s is too short (minimum is %d characters)" 
     462 
     463prod_5_facet_2_he_tr: 
     464  id: 80 
     465  table_name: globalize_products 
     466  item_id: 5 
     467  facet: name 
     468  language_id: 7 
     469  text: oreja 
     470  type: ModelTranslation 
     471prod_5_facet_3_he_tr: 
     472  id: 81 
     473  table_name: globalize_products 
     474  item_id: 5 
     475  facet: description 
     476  language_id: 7 
     477  text: descripción de oreja 
     478  type: ModelTranslation