views:

217

answers:

0

Hi,

I'm using a rails plugin call acts_as_translatable. The plugin modify the sql sent to the DB to enable multiple translation a the model level.

The plugin works good with SELECT statement, through the override of the ActiveRecord::Base::construc_finder_sql method. However, it does not work for update statements. I am tring to fix it by overriding the update_all and update methods from ActiveRecord::Base

Let's simplify my request. I want to override the following update call from Base.rb

  def update(attribute_names = @attributes.keys)
    quoted_attributes = attributes_with_quotes(false, false, attribute_names)
    return 0 if quoted_attributes.empty?
    connection.update(
      "UPDATE #{self.class.quoted_table_name} " +
      "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
      "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
      "#{self.class.name} Update"
    )
  end

Can you help? I believe my issue is because I am not wrapping in the right context. Probably another update method (in the class methods of Base.rb).

Here's the full acts_as_translate.rb where I want to intercept the update and pass it to my update.rb method to be define.

==================================================================

==================================================================

==================acts_as_translatable.rb=========================

==================================================================

==================================================================

module ActiveRecord

module Acts #:nodoc: module Translatable #:nodoc:

  def self.included(base) # :nodoc:
    class << base
      unless respond_to? :old_construct_finder_sql_with_included_associations
        alias_method :old_construct_finder_sql_with_included_associations,
                     :construct_finder_sql_with_included_associations
      end
      unless respond_to? :old_column_aliases
        alias_method :old_column_aliases, :column_aliases
      end
    end
    base.extend(ClassMethods)
  end

  module ClassMethods
    #
    # Options:
    #  +attributes+  *REQUIRED* - specify attributes that will be translated 
    #  +from+ - specify translation class name. (Default is class_name + Translation)
    #  +foreign_key+ - specify foreign key of translation model
    #  +language_key+ (Default is :language_id)
    #  +prefix+ (default translation)
    def acts_as_translatable(options = {})

      raise "set attributes to be translated" if options[:attributes].nil?
      from = options[:from] || "#{self.name}Translation"
      foreign_key = options[:foreign_key] || ActiveSupport::Inflector.foreign_key(base_class.name)
      language_key = options[:language_key] || 'language_id'
      prefix = options[:prefix] || 'translation'

      begin 
        translation = from.constantize
      rescue
        raise "TranslationModel '#{from}' does not exists. Run generate translatable_model first"
      end
      translation.ensure_columns(options[:attributes]) 
      select = []
      column_names.each do |column|
        if options[:attributes].include?(column.intern)
          select << "COALESCE(#{translation.table_name}.#{column}, #{table_name}.#{column}) AS #{column}"
        else
          select << "#{table_name}.#{column} AS #{column}"
        end
      end
      select = select.join(", ")
      joins = " LEFT JOIN #{translation.table_name} ON" +
              " #{translation.table_name}.#{foreign_key} =" +
              " #{table_name}.#{primary_key} AND" +
              " #{translation.table_name}.#{language_key} = "

      write_inheritable_attribute(:acts_as_translatable_options, {
                                    :attributes => options[:attributes],
                                    :select => select,
                                    :joins => joins,
                                    :prefix => prefix,
                                    :table_name => translation.table_name })

      class_inheritable_reader :acts_as_translatable_options

      has_many :translations,
               :class_name => from,
               :dependent => :destroy

      has_one :translation,
              :class_name => from,
              :conditions => "#{language_key} = " + 
                             "'" + '#{TranslationModel.language.to_s}' + "'"



      class <<self
        unless respond_to? :old_construct_finder_sql
          alias_method :old_construct_finder_sql, :construct_finder_sql
        end            
      end

      extend ActiveRecord::Acts::Translatable::SingletonMethods
      include ActiveRecord::Acts::Translatable::InstanceMethods
    end

    def translation_columns
      @translation_columns ||= columns.reject {|c|
        !acts_as_translatable_options[:attributes].include?(c.name.intern)
      }
    end

    def define_translation_reader_method(attribute, prefix)
      self.class_eval <<-END_OF_METHOD
        attr = #{prefix}_#{attribute} 
        def #{attr} 
          if @attributes.has_key?(#{attr})
            @attributes['#{attr}']
          else
            nil
          end
        end
      END_OF_METHOD
    end

    private
      def translate_conditions!(conditions,attributes,table_name,translation_table_name)
        return if conditions.nil?
        #FIXME 20090521 GuillaumeNM does the following works only with 1 condition in array?
        where_clause = conditions.kind_of?(Array) ? conditions[0] : 
                                                    conditions
        #FIXME 20090521 GuillaumeNM only works with string condition, not hash
        return if conditions.kind_of?(Hash)

        #GuillaumeNM 20090524
        #with id in conditions, the translation doesn't work, so I forced the table name if not present
        #Replace the condition when it is id with a space before and after
        where_clause.gsub!(" id "," #{table_name}.id ")
        where_clause.gsub!("#{table_name}.#{table_name}.id","#{table_name}.id") #Ensure we didn't double added the table prefix
        where_clause.gsub!("\"#{table_name}\".\"#{table_name}.id\"","#{table_name}.id") #Ensure we didn't double added the table prefix

        attributes.each do |attr|
          where_clause.gsub!("#{table_name}.#{attr}",
                             "COALESCE(#{translation_table_name}.#{attr},#{table_name}.#{attr})")
        end
      end

      def translate_options_with_included_associations!(options,join_dependency)
        return if TranslationModel.disabled? || TranslationModel.base_language?
        joins = ""
        join_dependency.joins.each do |join|
          if join.active_record.respond_to?('acts_as_translatable_options')
            joins << " #{join.active_record.acts_as_translatable_options[:joins]}"
            joins << "'#{TranslationModel.language}'"
            translate_conditions!(options[:conditions],
                join.active_record.acts_as_translatable_options[:attributes],
                join.active_record.table_name,
                join.active_record.acts_as_translatable_options[:table_name])
          end
        end          
        options[:joins]  = joins
      end

      def construct_finder_sql_with_included_associations(options,join_dependency)
        translate_options_with_included_associations!(options,join_dependency)
        old_construct_finder_sql_with_included_associations(options,join_dependency)
      end

      def column_aliases(join_dependency)
        if TranslationModel.disabled? || TranslationModel.base_language?
          return old_column_aliases(join_dependency)
        end
        join_dependency.joins.collect{|join| 
          join.column_names_with_alias.collect{|column_name, aliased_name|
            if join.active_record.respond_to?('acts_as_translatable_options') &&
               join.active_record.acts_as_translatable_options[:attributes].include?(column_name.intern)
              "COALESCE(#{join.active_record.acts_as_translatable_options[:table_name]}.#{connection.quote_column_name column_name}, #{join.aliased_table_name}.#{connection.quote_column_name column_name}) AS #{aliased_name}"
            else
              "#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"
            end
          }
        }.flatten.join(", ")
      end



  end#ClassMethods

  module SingletonMethods

    def translate_options!(options)
      return if TranslationModel.disabled? || TranslationModel.base_language?
    if options[:select] =~ /DISTINCT/
     options[:select] = "DISTINCT "
    else
     options[:select] = ""
    end
      options[:select] << acts_as_translatable_options[:select]
      options[:joins] ||= ''
      options[:joins] << acts_as_translatable_options[:joins]
      options[:joins] << "'#{TranslationModel.language}'"
      translate_conditions!(options[:conditions],
                            acts_as_translatable_options[:attributes],
                            table_name,
                            acts_as_translatable_options[:table_name])
    end

    public
      #Guillaume Fix for update statements
      #20090524
      #Needs to be not private like overloaded method ?
      def update_all(updates, conditions = nil, options = {})
        #translate_options!(options)
        old_update_all(updates, conditions, options)
      end

    private
      def construct_finder_sql(options)
        translate_options!(options)
        old_construct_finder_sql(options)
      end

      def update(attribute_names = @attributes.keys)
        #translate_options!(options)
        old_update(attribute_names)
      end

  end#SingletonMethods


  module InstanceMethods
    #GuillaumeNM 2009-05-22
    #FIXME performance awful, should be cached.
    #Returns an array of the available translations in the format : ['en','fr']
    def translated_to?
      results=[]
      self.translations.each do |tr|
        #Add the language only once.
        results.push(tr.language_id) unless not results.index(tr.language_id).nil?
      end
      return results
    end

    #GuillaumeNM 2009-05-22
    #FIXME performance awful, should be cached.
    #Returns an array of the available translations in the format : ['en','fr']
    def translation_status?
      total=0
      translated=0
      self.translations.each do |tr|
        #Add the language only once.
        results.push(tr.language_id) unless not results.index(tr.language_id).nil?
      end
      return results
    end
  end#InstanceMethods

end#Tra1nslatable
  end#Acts
end#ActiveRecord

==================================================================

==================================================================

==================================================================

==================================================================