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