globalize 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +87 -0
- data/LICENSE +21 -0
- data/Rakefile +22 -0
- data/lib/globalize.rb +80 -0
- data/lib/globalize/active_record.rb +12 -0
- data/lib/globalize/active_record/act_macro.rb +96 -0
- data/lib/globalize/active_record/adapter.rb +111 -0
- data/lib/globalize/active_record/attributes.rb +27 -0
- data/lib/globalize/active_record/class_methods.rb +173 -0
- data/lib/globalize/active_record/exceptions.rb +19 -0
- data/lib/globalize/active_record/instance_methods.rb +224 -0
- data/lib/globalize/active_record/migration.rb +191 -0
- data/lib/globalize/active_record/translation.rb +46 -0
- data/lib/globalize/interpolation.rb +28 -0
- data/lib/globalize/version.rb +3 -0
- data/lib/globalize/versioning.rb +5 -0
- data/lib/globalize/versioning/paper_trail.rb +41 -0
- data/lib/i18n/missing_translations_log_handler.rb +41 -0
- data/lib/i18n/missing_translations_raise_handler.rb +25 -0
- data/lib/patches/active_record/query_method.rb +35 -0
- data/lib/patches/active_record/relation.rb +15 -0
- data/lib/patches/active_record/uniqueness_validator.rb +55 -0
- data/lib/patches/active_record/xml_attribute_serializer.rb +13 -0
- metadata +186 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# Helper class for storing values per locale. Used by Globalize::Adapter
|
2
|
+
# to stash and cache attribute values.
|
3
|
+
|
4
|
+
module Globalize
|
5
|
+
module ActiveRecord
|
6
|
+
class Attributes < Hash # TODO: Think about using HashWithIndifferentAccess ?
|
7
|
+
def [](locale)
|
8
|
+
locale = locale.to_sym
|
9
|
+
self[locale] = {} unless has_key?(locale)
|
10
|
+
self.fetch(locale)
|
11
|
+
end
|
12
|
+
|
13
|
+
def contains?(locale, name)
|
14
|
+
self[locale].has_key?(name.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(locale, name)
|
18
|
+
self[locale][name.to_s]
|
19
|
+
end
|
20
|
+
|
21
|
+
def write(locale, name, value)
|
22
|
+
#raise 'z' if value.nil? # TODO
|
23
|
+
self[locale][name.to_s] = value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
module ClassMethods
|
4
|
+
delegate :translated_locales, :set_translations_table_name, :to => :translation_class
|
5
|
+
|
6
|
+
def with_locales(*locales)
|
7
|
+
scoped.merge(translation_class.with_locales(*locales))
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_translations(*locales)
|
11
|
+
locales = translated_locales if locales.empty?
|
12
|
+
includes(:translations).with_locales(locales).with_required_attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_required_attributes
|
16
|
+
required_translated_attributes.inject(scoped) do |scope, name|
|
17
|
+
scope.where("#{translated_column_name(name)} IS NOT NULL")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_translated_attribute(name, value, locales = nil)
|
22
|
+
locales ||= Globalize.fallbacks
|
23
|
+
with_translations.where(
|
24
|
+
translated_column_name(name) => value,
|
25
|
+
translated_column_name(:locale) => Array(locales).map(&:to_s)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def translated?(name)
|
30
|
+
translated_attribute_names.include?(name.to_sym)
|
31
|
+
end
|
32
|
+
|
33
|
+
def required_attributes
|
34
|
+
validators.map { |v| v.attributes if v.is_a?(ActiveModel::Validations::PresenceValidator) }.flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def required_translated_attributes
|
38
|
+
translated_attribute_names & required_attributes
|
39
|
+
end
|
40
|
+
|
41
|
+
def translation_class
|
42
|
+
@translation_class ||= begin
|
43
|
+
klass = self.const_get(:Translation) rescue nil
|
44
|
+
if klass.nil? || klass.class_name != (self.class_name + "Translation")
|
45
|
+
klass = self.const_set(:Translation, Class.new(Globalize::ActiveRecord::Translation))
|
46
|
+
end
|
47
|
+
|
48
|
+
klass.belongs_to :globalized_model, :class_name => self.name, :foreign_key => translation_options[:foreign_key]
|
49
|
+
klass
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def translations_table_name
|
54
|
+
translation_class.table_name
|
55
|
+
end
|
56
|
+
|
57
|
+
def translated_column_name(name)
|
58
|
+
"#{translation_class.table_name}.#{name}"
|
59
|
+
end
|
60
|
+
|
61
|
+
if RUBY_VERSION < '1.9'
|
62
|
+
def respond_to?(method_id, *args, &block)
|
63
|
+
supported_on_missing?(method_id) || super
|
64
|
+
end
|
65
|
+
else
|
66
|
+
def respond_to_missing?(method_id, include_private = false)
|
67
|
+
supported_on_missing?(method_id) || super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def supported_on_missing?(method_id)
|
72
|
+
return super unless RUBY_VERSION < '1.9' || respond_to?(:translated_attribute_names)
|
73
|
+
match = defined?(::ActiveRecord::DynamicFinderMatch) && (::ActiveRecord::DynamicFinderMatch.match(method_id) || ::ActiveRecord::DynamicScopeMatch.match(method_id))
|
74
|
+
return false if match.nil?
|
75
|
+
|
76
|
+
attribute_names = match.attribute_names.map(&:to_sym)
|
77
|
+
translated_attributes = attribute_names & translated_attribute_names
|
78
|
+
return false if translated_attributes.empty?
|
79
|
+
|
80
|
+
untranslated_attributes = attribute_names - translated_attributes
|
81
|
+
return false if untranslated_attributes.any?{|unt| ! respond_to?(:"scoped_by_#{unt}")}
|
82
|
+
return [match, attribute_names, translated_attributes, untranslated_attributes]
|
83
|
+
end
|
84
|
+
|
85
|
+
def method_missing(method_id, *arguments, &block)
|
86
|
+
match, attribute_names, translated_attributes, untranslated_attributes = supported_on_missing?(method_id)
|
87
|
+
return super unless match
|
88
|
+
|
89
|
+
scope = scoped
|
90
|
+
|
91
|
+
translated_attributes.each do |attr|
|
92
|
+
scope = scope.with_translated_attribute(attr, arguments[attribute_names.index(attr)])
|
93
|
+
end
|
94
|
+
|
95
|
+
untranslated_attributes.each do |unt|
|
96
|
+
index = attribute_names.index(unt)
|
97
|
+
raise StandarError unless index
|
98
|
+
scope = scope.send(:"scoped_by_#{unt}", arguments[index])
|
99
|
+
end
|
100
|
+
|
101
|
+
if defined?(::ActiveRecord::DynamicFinderMatch) && match.is_a?(::ActiveRecord::DynamicFinderMatch)
|
102
|
+
if match.instantiator? and scope.blank?
|
103
|
+
return scope.find_or_instantiator_by_attributes match, attribute_names, *arguments, &block
|
104
|
+
end
|
105
|
+
match_finder_method = match.finder.to_s
|
106
|
+
match_finder_method << "!" if match.bang? && ::ActiveRecord::VERSION::STRING >= "3.1.0"
|
107
|
+
return scope.send(match_finder_method).tap do |found|
|
108
|
+
found.is_a?(Array) ? found.map { |f| f.translations.reload } : found.translations.reload unless found.nil?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
return scope
|
112
|
+
end
|
113
|
+
|
114
|
+
def find_or_instantiator_by_attributes(match, attributes, *args)
|
115
|
+
options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
|
116
|
+
protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
|
117
|
+
args.each_with_index do |arg, i|
|
118
|
+
if arg.is_a?(Hash)
|
119
|
+
protected_attributes_for_create = args[i].with_indifferent_access
|
120
|
+
else
|
121
|
+
unprotected_attributes_for_create[attributes[i]] = args[i]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
record = if ::ActiveRecord::VERSION::STRING < "3.1.0"
|
126
|
+
new do |r|
|
127
|
+
r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
|
128
|
+
r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
|
129
|
+
end
|
130
|
+
else
|
131
|
+
new(protected_attributes_for_create, options) do |r|
|
132
|
+
r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
yield(record) if block_given?
|
136
|
+
record.send(match.bang? ? :save! : :save) if match.instantiator.eql?(:create)
|
137
|
+
|
138
|
+
record
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
|
143
|
+
def translated_attr_accessor(name)
|
144
|
+
define_method(:"#{name}=") do |value|
|
145
|
+
write_attribute(name, value)
|
146
|
+
end
|
147
|
+
define_method(name) do |*args|
|
148
|
+
Globalize::Interpolation.interpolate(name, self, args)
|
149
|
+
end
|
150
|
+
alias_method :"#{name}_before_type_cast", name
|
151
|
+
end
|
152
|
+
|
153
|
+
def translations_accessor(name)
|
154
|
+
define_method(:"#{name}_translations") do
|
155
|
+
result = translations.each_with_object(HashWithIndifferentAccess.new) do |translation, result|
|
156
|
+
result[translation.locale] = translation.send(name)
|
157
|
+
end
|
158
|
+
globalize.stash.keys.each_with_object(result) do |locale, result|
|
159
|
+
result[locale] = globalize.fetch_stash(locale, name) if globalize.stash_contains?(locale, name)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
define_method(:"#{name}_translations=") do |value|
|
163
|
+
value.each do |(locale, value)|
|
164
|
+
write_attribute name, value, :locale => locale
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
module Exceptions
|
4
|
+
class MigrationError < StandardError; end
|
5
|
+
|
6
|
+
class BadFieldName < MigrationError
|
7
|
+
def initialize(field)
|
8
|
+
super("Missing translated field #{field.inspect}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class BadFieldType < MigrationError
|
13
|
+
def initialize(name, type)
|
14
|
+
super("Bad field type for field #{name.inspect} (#{type.inspect}), should be :string or :text")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
module InstanceMethods
|
4
|
+
delegate :translated_locales, :to => :translations
|
5
|
+
|
6
|
+
def globalize
|
7
|
+
@globalize ||= Adapter.new(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def attributes
|
11
|
+
super.merge(translated_attributes)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
# Maintain Rails 3.0.x compatibility while adding Rails 3.1.x compatibility
|
16
|
+
if base.method_defined?(:assign_attributes)
|
17
|
+
base.class_eval %{
|
18
|
+
def assign_attributes(attributes, options = {})
|
19
|
+
with_given_locale(attributes) { super }
|
20
|
+
end
|
21
|
+
}
|
22
|
+
else
|
23
|
+
base.class_eval %{
|
24
|
+
def attributes=(attributes, *args)
|
25
|
+
with_given_locale(attributes) { super }
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_attributes!(attributes, *args)
|
29
|
+
with_given_locale(attributes) { super }
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_attributes(attributes, *args)
|
33
|
+
with_given_locale(attributes) { super }
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def write_attribute(name, value, options = {})
|
40
|
+
if translated?(name)
|
41
|
+
# Deprecate old use of locale
|
42
|
+
unless options.is_a?(Hash)
|
43
|
+
warn "[DEPRECATION] passing 'locale' as #{options.inspect} is deprecated. Please use {:locale => #{options.inspect}} instead."
|
44
|
+
options = {:locale => options}
|
45
|
+
end
|
46
|
+
options = {:locale => Globalize.locale}.merge(options)
|
47
|
+
|
48
|
+
# Dirty tracking, paraphrased from
|
49
|
+
# ActiveRecord::AttributeMethods::Dirty#write_attribute.
|
50
|
+
name_str = name.to_s
|
51
|
+
if attribute_changed?(name_str)
|
52
|
+
# If there's already a change, delete it if this undoes the change.
|
53
|
+
old = changed_attributes[name_str]
|
54
|
+
changed_attributes.delete(name_str) if value == old
|
55
|
+
else
|
56
|
+
# If there's not a change yet, record it.
|
57
|
+
old = globalize.fetch(options[:locale], name)
|
58
|
+
old = old.clone if old.duplicable?
|
59
|
+
changed_attributes[name_str] = old if value != old
|
60
|
+
end
|
61
|
+
|
62
|
+
globalize.write(options[:locale], name, value)
|
63
|
+
else
|
64
|
+
super(name, value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def read_attribute(name, options = {})
|
69
|
+
# Deprecate old use of locale
|
70
|
+
unless options.is_a?(Hash)
|
71
|
+
warn "[DEPRECATION] passing 'locale' as #{options.inspect} is deprecated. Please use {:locale => #{options.inspect}} instead."
|
72
|
+
options = {:locale => options}
|
73
|
+
end
|
74
|
+
|
75
|
+
options = {:translated => true, :locale => nil}.merge(options)
|
76
|
+
if self.class.translated?(name) and options[:translated]
|
77
|
+
if (value = globalize.fetch(options[:locale] || Globalize.locale, name))
|
78
|
+
value
|
79
|
+
else
|
80
|
+
super(name)
|
81
|
+
end
|
82
|
+
else
|
83
|
+
super(name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def attribute_names
|
88
|
+
translated_attribute_names.map(&:to_s) + super
|
89
|
+
end
|
90
|
+
|
91
|
+
def translated?(name)
|
92
|
+
self.class.translated?(name)
|
93
|
+
end
|
94
|
+
|
95
|
+
def translated_attributes
|
96
|
+
translated_attribute_names.inject({}) do |attributes, name|
|
97
|
+
attributes.merge(name.to_s => translation.send(name))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# This method is basically the method built into Rails
|
102
|
+
# but we have to pass {:translated => false}
|
103
|
+
def untranslated_attributes
|
104
|
+
attrs = {}
|
105
|
+
attribute_names.each do |name|
|
106
|
+
attrs[name] = read_attribute(name, {:translated => false})
|
107
|
+
end
|
108
|
+
attrs
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_translations(options)
|
112
|
+
options.keys.each do |locale|
|
113
|
+
translation = translation_for(locale) ||
|
114
|
+
translations.build(:locale => locale.to_s)
|
115
|
+
|
116
|
+
options[locale].each do |key, value|
|
117
|
+
translation.send :"#{key}=", value
|
118
|
+
end
|
119
|
+
translation.save
|
120
|
+
end
|
121
|
+
globalize.reset
|
122
|
+
end
|
123
|
+
|
124
|
+
def reload(options = nil)
|
125
|
+
translation_caches.clear
|
126
|
+
translated_attribute_names.each { |name| @attributes.delete(name.to_s) }
|
127
|
+
globalize.reset
|
128
|
+
super(options)
|
129
|
+
end
|
130
|
+
|
131
|
+
def clone
|
132
|
+
obj = super
|
133
|
+
return obj unless respond_to?(:translated_attribute_names)
|
134
|
+
|
135
|
+
obj.instance_variable_set(:@translations, nil) if new_record? # Reset the collection because of rails bug: http://pastie.org/1521874
|
136
|
+
obj.instance_variable_set(:@globalize, nil )
|
137
|
+
each_locale_and_translated_attribute do |locale, name|
|
138
|
+
obj.globalize.write(locale, name, globalize.fetch(locale, name) )
|
139
|
+
end
|
140
|
+
|
141
|
+
return obj
|
142
|
+
end
|
143
|
+
|
144
|
+
def translation
|
145
|
+
translation_for(::Globalize.locale)
|
146
|
+
end
|
147
|
+
|
148
|
+
def translation_for(locale, build_if_missing = true)
|
149
|
+
unless translation_caches[locale]
|
150
|
+
# Fetch translations from database as those in the translation collection may be incomplete
|
151
|
+
_translation = translations.detect{|t| t.locale.to_s == locale.to_s}
|
152
|
+
_translation ||= translations.with_locale(locale).first unless translations.loaded?
|
153
|
+
_translation ||= translations.build(:locale => locale) if build_if_missing
|
154
|
+
translation_caches[locale] = _translation if _translation
|
155
|
+
end
|
156
|
+
translation_caches[locale]
|
157
|
+
end
|
158
|
+
|
159
|
+
def translation_caches
|
160
|
+
@translation_caches ||= {}
|
161
|
+
end
|
162
|
+
|
163
|
+
def globalize_fallbacks(locale)
|
164
|
+
Globalize.fallbacks(locale)
|
165
|
+
end
|
166
|
+
|
167
|
+
def rollback
|
168
|
+
translation_caches[::Globalize.locale] = translation.previous_version
|
169
|
+
end
|
170
|
+
|
171
|
+
def column_for_attribute name
|
172
|
+
translated_attribute_names.include?(name) ? globalize.send(:column_for_attribute, name) : super
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def update(*)
|
178
|
+
I18n.with_locale(read_attribute(:locale) || I18n.default_locale) do
|
179
|
+
super
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def create(*)
|
184
|
+
I18n.with_locale(read_attribute(:locale) || I18n.default_locale) do
|
185
|
+
super
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
protected
|
190
|
+
|
191
|
+
def each_locale_and_translated_attribute
|
192
|
+
used_locales.each do |locale|
|
193
|
+
translated_attribute_names.each do |name|
|
194
|
+
yield locale, name
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def used_locales
|
200
|
+
locales = globalize.stash.keys.concat(globalize.stash.keys).concat(translations.translated_locales)
|
201
|
+
locales.uniq!
|
202
|
+
locales
|
203
|
+
end
|
204
|
+
|
205
|
+
def save_translations!
|
206
|
+
globalize.save_translations!
|
207
|
+
translation_caches.clear
|
208
|
+
end
|
209
|
+
|
210
|
+
def with_given_locale(attributes, &block)
|
211
|
+
attributes.symbolize_keys! if attributes.respond_to?(:symbolize_keys!)
|
212
|
+
|
213
|
+
locale = respond_to?(:locale=) ? attributes.try(:[], :locale) :
|
214
|
+
attributes.try(:delete, :locale)
|
215
|
+
|
216
|
+
if locale
|
217
|
+
Globalize.with_locale(locale, &block)
|
218
|
+
else
|
219
|
+
yield
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Globalize
|
4
|
+
module ActiveRecord
|
5
|
+
module Migration
|
6
|
+
attr_reader :globalize_migrator
|
7
|
+
|
8
|
+
def globalize_migrator
|
9
|
+
@globalize_migrator ||= Migrator.new(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
delegate :create_translation_table!, :add_translation_fields!, :drop_translation_table!,
|
13
|
+
:translation_index_name, :translation_locale_index_name,
|
14
|
+
:to => :globalize_migrator
|
15
|
+
|
16
|
+
class Migrator
|
17
|
+
include Globalize::ActiveRecord::Exceptions
|
18
|
+
|
19
|
+
attr_reader :model, :fields
|
20
|
+
delegate :translated_attribute_names, :connection, :table_name,
|
21
|
+
:table_name_prefix, :translations_table_name, :columns, :to => :model
|
22
|
+
|
23
|
+
def initialize(model)
|
24
|
+
@model = model
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_translation_table!(fields = {}, options = {})
|
28
|
+
@fields = fields
|
29
|
+
# If we have fields we only want to create the translation table with those fields
|
30
|
+
complete_translated_fields if fields.blank?
|
31
|
+
validate_translated_fields
|
32
|
+
|
33
|
+
create_translation_table
|
34
|
+
add_translation_fields!(fields, options)
|
35
|
+
create_translations_index
|
36
|
+
clear_schema_cache!
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_translation_fields!(fields, options = {})
|
40
|
+
@fields = fields
|
41
|
+
validate_translated_fields
|
42
|
+
|
43
|
+
add_translation_fields
|
44
|
+
clear_schema_cache!
|
45
|
+
move_data_to_translation_table if options[:migrate_data]
|
46
|
+
remove_source_columns if options[:remove_source_columns]
|
47
|
+
clear_schema_cache!
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_source_columns
|
51
|
+
connection.remove_columns(table_name, *fields.keys)
|
52
|
+
end
|
53
|
+
|
54
|
+
def drop_translation_table!(options = {})
|
55
|
+
move_data_to_model_table if options[:migrate_data]
|
56
|
+
drop_translations_index
|
57
|
+
drop_translation_table
|
58
|
+
clear_schema_cache!
|
59
|
+
end
|
60
|
+
|
61
|
+
# This adds all the current translated attributes of the model
|
62
|
+
# It's a problem because in early migrations would add all the translated attributes
|
63
|
+
def complete_translated_fields
|
64
|
+
translated_attribute_names.each do |name|
|
65
|
+
fields[name] = column_type(name) unless fields[name]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_translation_table
|
70
|
+
connection.create_table(translations_table_name) do |t|
|
71
|
+
t.references table_name.sub(/^#{table_name_prefix}/, '').singularize, :null => false
|
72
|
+
t.string :locale, :null => false
|
73
|
+
t.timestamps
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_translation_fields
|
78
|
+
connection.change_table(translations_table_name) do |t|
|
79
|
+
fields.each do |name, options|
|
80
|
+
if options.is_a? Hash
|
81
|
+
t.column name, options.delete(:type), options
|
82
|
+
else
|
83
|
+
t.column name, options
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_translations_index
|
90
|
+
connection.add_index(
|
91
|
+
translations_table_name,
|
92
|
+
"#{table_name.sub(/^#{table_name_prefix}/, "").singularize}_id",
|
93
|
+
:name => translation_index_name
|
94
|
+
)
|
95
|
+
# index for select('DISTINCT locale') call in translation.rb
|
96
|
+
connection.add_index(
|
97
|
+
translations_table_name,
|
98
|
+
:locale,
|
99
|
+
:name => translation_locale_index_name
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def drop_translation_table
|
104
|
+
connection.drop_table(translations_table_name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def drop_translations_index
|
108
|
+
connection.remove_index(translations_table_name, :name => translation_index_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
def move_data_to_translation_table
|
112
|
+
model.find_each do |record|
|
113
|
+
translation = record.translation_for(I18n.default_locale) || record.translations.build(:locale => I18n.default_locale)
|
114
|
+
fields.each do |attribute_name, attribute_type|
|
115
|
+
translation[attribute_name] = record.read_attribute(attribute_name, {:translated => false})
|
116
|
+
end
|
117
|
+
translation.save!
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def move_data_to_model_table
|
122
|
+
add_missing_columns
|
123
|
+
|
124
|
+
# Find all of the translated attributes for all records in the model.
|
125
|
+
all_translated_attributes = @model.all.collect{|m| m.attributes}
|
126
|
+
all_translated_attributes.each do |translated_record|
|
127
|
+
# Create a hash containing the translated column names and their values.
|
128
|
+
translated_attribute_names.inject(fields_to_update={}) do |f, name|
|
129
|
+
f.update({name.to_sym => translated_record[name.to_s]})
|
130
|
+
end
|
131
|
+
|
132
|
+
# Now, update the actual model's record with the hash.
|
133
|
+
@model.update_all(fields_to_update, {:id => translated_record['id']})
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def validate_translated_fields
|
138
|
+
fields.each do |name, options|
|
139
|
+
raise BadFieldName.new(name) unless valid_field_name?(name)
|
140
|
+
if options.is_a? Hash
|
141
|
+
raise BadFieldType.new(name, options[:type]) unless valid_field_type?(name, options[:type])
|
142
|
+
else
|
143
|
+
raise BadFieldType.new(name, options) unless valid_field_type?(name, options)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def column_type(name)
|
149
|
+
columns.detect { |c| c.name == name.to_s }.try(:type)
|
150
|
+
end
|
151
|
+
|
152
|
+
def valid_field_name?(name)
|
153
|
+
translated_attribute_names.include?(name)
|
154
|
+
end
|
155
|
+
|
156
|
+
def valid_field_type?(name, type)
|
157
|
+
!translated_attribute_names.include?(name) || [:string, :text].include?(type)
|
158
|
+
end
|
159
|
+
|
160
|
+
def translation_index_name
|
161
|
+
index_name = "index_#{translations_table_name}_on_#{table_name.singularize}_id"
|
162
|
+
index_name.size < connection.index_name_length ? index_name :
|
163
|
+
"index_#{Digest::SHA1.hexdigest(index_name)}"[0, connection.index_name_length]
|
164
|
+
end
|
165
|
+
|
166
|
+
def translation_locale_index_name
|
167
|
+
index_name = "index_#{translations_table_name}_on_locale"
|
168
|
+
index_name.size < connection.index_name_length ? index_name :
|
169
|
+
"index_#{Digest::SHA1.hexdigest(index_name)}"[0, connection.index_name_length]
|
170
|
+
end
|
171
|
+
|
172
|
+
def clear_schema_cache!
|
173
|
+
connection.schema_cache.clear! if connection.respond_to? :schema_cache
|
174
|
+
model::Translation.reset_column_information
|
175
|
+
model.reset_column_information
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def add_missing_columns
|
181
|
+
translated_attribute_names.map(&:to_s).each do |attribute|
|
182
|
+
unless model.column_names.include?(attribute)
|
183
|
+
connection.add_column(table_name, attribute, model::Translation.columns_hash[attribute].type)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|