lolita-translation 0.0.3 → 0.0.4

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.
@@ -1,8 +1,8 @@
1
- Description:
2
- Explain the generator
3
-
4
- Example:
5
- ./script/generate has_translations Thing
6
-
7
- This will create:
8
- what/will/it/create
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ ./script/generate has_translations Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -1,8 +1,8 @@
1
- class HasTranslationsGenerator < Rails::Generator::NamedBase
2
- def manifest
3
- record do |m|
4
- # m.directory "lib"
5
- # m.template 'README', "README"
6
- end
7
- end
8
- end
1
+ class HasTranslationsGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ # m.directory "lib"
5
+ # m.template 'README', "README"
6
+ end
7
+ end
8
+ end
@@ -1,12 +1,12 @@
1
-
2
- require 'lolita-translation/string.rb'
3
- require 'lolita-translation/has_translations.rb'
4
- require 'lolita-translation/rails'
5
-
6
- module Lolita
7
- module Configuration
8
- module Tab
9
- autoload :Translation, "lolita-translation/configuration/tab/translation"
10
- end
11
- end
1
+
2
+ require 'lolita-translation/string.rb'
3
+ require 'lolita-translation/has_translations.rb'
4
+ require 'lolita-translation/rails'
5
+
6
+ module Lolita
7
+ module Configuration
8
+ module Tab
9
+ autoload :Translation, "lolita-translation/configuration/tab/translation"
10
+ end
11
+ end
12
12
  end
@@ -1,14 +1,14 @@
1
- module Lolita
2
- module Configuration
3
- module Tab
4
- class Translation < Lolita::Configuration::Tab::Base
5
-
6
- def initialize(dbi,*args,&block)
7
- @type=:translation
8
- super
9
- end
10
-
11
- end
12
- end
13
- end
1
+ module Lolita
2
+ module Configuration
3
+ module Tab
4
+ class Translation < Lolita::Configuration::Tab::Base
5
+
6
+ def initialize(dbi,*args,&block)
7
+ @type=:translation
8
+ super
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
14
  end
@@ -1,295 +1,295 @@
1
- require 'stringio'
2
-
3
- class ActiveRecord::Base
4
- # Provides ability to add the translations for the model using delegate pattern.
5
- # Uses has_many association to the ModelNameTranslation.
6
- #
7
- # For example you have model Article with attributes title and text.
8
- # You want that attributes title and text to be translated.
9
- # For this reason you need to generate new model ArticleTranslation.
10
- # In migration you need to add:
11
- #
12
- # create_table :article_translations do |t|
13
- # t.references :article, :null => false
14
- # t.string :locale, :length => 2, :null => false
15
- # t.string :name, :null => false
16
- # end
17
- #
18
- # add_index :articles, [:article_id, :locale], :unique => true, :name => 'unique_locale_for_article_id'
19
- #
20
- # And in the Article model:
21
- #
22
- # translations :title, :text
23
- #
24
- # This will adds:
25
- #
26
- # * named_scope (translated) and has_many association to the Article model
27
- # * locale presence validation to the ArticleTranslation model.
28
- #
29
- # Notice: if you want to have validates_presence_of :article, you should use :inverse_of.
30
- # Support this by yourself. Better is always to use artile.translations.build() method.
31
- #
32
- # For more information please read API. Feel free to write me an email to:
33
- # dmitry.polushkin@gmail.com.
34
- #
35
- # ===
36
- #
37
- # You also can pass attributes and options to the translations class method:
38
- #
39
- # translations :title, :text, :fallback => true, :writer => true, :nil => nil
40
- #
41
- # ===
42
- #
43
- # Configuration options:
44
- #
45
- # * <tt>:fallback</tt> - if translation for the current locale not found.
46
- # By default true.
47
- # Uses algorithm of fallback:
48
- # 0) current translation (using I18n.locale);
49
- # 1) default locale (using I18n.default_locale);
50
- # 2) :nil value (see <tt>:nil</tt> configuration option)
51
- # * <tt>:reader</tt> - add reader attributes to the model and delegate them
52
- # to the translation model columns. Add's fallback if it is set to true.
53
- # * <tt>:writer</tt> - add writer attributes to the model and assign them
54
- # to the translation model attributes.
55
- # * <tt>:nil</tt> - when reader cant find string, it returns by default an
56
- # empty string. If you want to change this setting for example to nil,
57
- # add :nil => nil
58
- #
59
- # ===
60
- #
61
- # When you are using <tt>:writer</tt> option, you can create translations using
62
- # update_attributes method. For example:
63
- #
64
- # Article.create!
65
- # Article.update_attributes(:title => 'title', :text => 'text')
66
- #
67
- # ===
68
- #
69
- # <tt>translated</tt> named_scope is useful when you want to find only those
70
- # records that are translated to a specific locale.
71
- # For example if you want to find all Articles that is translated to an english
72
- # language, you can write: Article.translated(:en)
73
- #
74
- # <tt>has_translation?(locale)</tt> method, that returns true if object's model
75
- # have a translation for a specified locale
76
- #
77
- # <tt>translation(locale)</tt> method finds translation with specified locale.
78
- #
79
- # <tt>all_translations</tt> method that returns all possible translations in
80
- # ordered hash (useful when creating forms with nested attributes).
81
- def self.translations(*attrs)
82
- options = {
83
- :fallback => true,
84
- :reader => true,
85
- :writer => false,
86
- :nil => ''
87
- }.merge(attrs.extract_options!)
88
- options.assert_valid_keys([:fallback, :reader, :writer, :nil])
89
-
90
- class << self
91
- # adds :translations to :includes if current locale differs from default
92
- #FIXME is this enough with find or need to create chain for find_last, find_first and others?
93
- alias_method(:find_without_translations, :find) unless method_defined?(:find_without_translations)
94
- def find(*args)
95
- if args[0].kind_of?(Hash)
96
- args[0][:include] ||= []
97
- args[0][:include] << :translations
98
- end unless I18n.locale == I18n.default_locale
99
- find_without_translations(*args)
100
- end
101
- # Defines given class recursively
102
- # Example:
103
- # create_class('Cms::Text::Page', Object, ActiveRecord::Base)
104
- # => Cms::Text::Page
105
- def create_class(class_name, parent, superclass, &block)
106
- first,*other = class_name.split("::")
107
- if other.empty?
108
- klass = Class.new superclass, &block
109
- parent.const_set(first, klass)
110
- else
111
- klass = Class.new
112
- parent = unless parent.const_defined?(first)
113
- parent.const_set(first, klass)
114
- else
115
- first.constantize
116
- end
117
- create_class(other.join('::'), parent, superclass, &block)
118
- end
119
- end
120
- # defines "ModelNameTranslation" if it's not defined manualy
121
- def define_translation_class name, attrs
122
- klass = name.constantize rescue nil
123
- unless klass
124
- klass = create_class(name, Object, ActiveRecord::Base) do
125
- # set's real table name
126
- set_table_name name.sub('Translation','').constantize.table_name.singularize + "_translations"
127
- cattr_accessor :translate_attrs, :master_id
128
- # override validate to vaidate only translate fields from master Class
129
- def validate
130
- item = self.class.name.sub('Translation','').constantize.new(self.attributes.clone.delete_if{|k,_| !self.class.translate_attrs.include?(k.to_sym)})
131
- was_table_name = item.class.table_name
132
- item.class.set_table_name self.class.table_name
133
- item.valid? rescue
134
- self.class.translate_attrs.each do |attr|
135
- errors_on_attr = item.errors.on(attr)
136
- self.errors.add(attr,errors_on_attr) if errors_on_attr
137
- end
138
- item.class.set_table_name was_table_name
139
- end
140
- extend TranslationClassMethods
141
- end
142
- klass.translate_attrs = attrs
143
- else
144
- unless klass.respond_to?(:translate_attrs)
145
- klass.send(:cattr_accessor, :translate_attrs, :master_id)
146
- klass.send(:extend,TranslationClassMethods)
147
- klass.translate_attrs = attrs
148
- end
149
- end
150
-
151
- klass.extract_master_id(name)
152
- klass
153
- end
154
- # creates translation table and adds missing fields
155
- # So at first add the "translations :name, :desc" in your model
156
- # then put YourModel.sync_translation_table! in db/seed.rb and run "rake db:seed"
157
- # Later adding more fields in translations array, just run agin "rake db:seed"
158
- # If you want to remove fields do it manualy, it's safer
159
- def sync_translation_table!
160
- out = StringIO.new
161
- $stdout = out
162
- translations_class = reflections[:translations].class_name.constantize
163
- translations_table = translations_class.table_name
164
- unless ActiveRecord::Migration::table_exists?(translations_table)
165
- ActiveRecord::Migration.create_table translations_table do |t|
166
- t.integer translations_class.master_id, :null => false
167
- t.string :locale, :null => false, :limit => 5
168
- columns_has_translations.each do |col|
169
- t.send(col.type,col.name)
170
- end
171
- end
172
- ActiveRecord::Migration.add_index translations_table, [translations_class.master_id, :locale], :unique => true
173
- translations_class.reset_column_information
174
- else
175
- changes = false
176
- columns_has_translations.each do |col|
177
- unless translations_class.columns_hash.has_key?(col.name)
178
- ActiveRecord::Migration.add_column(translations_table, col.name, col.type)
179
- changes = true
180
- end
181
- end
182
- translations_class.reset_column_information if changes
183
- end
184
- $stdout = STDOUT
185
- end
186
- end
187
-
188
- translation_class_name = "#{self.name}Translation"
189
- translation_class = self.define_translation_class(translation_class_name, attrs)
190
- belongs_to = self.name.demodulize.underscore.to_sym
191
-
192
- write_inheritable_attribute :has_translations_options, options
193
- class_inheritable_reader :has_translations_options
194
-
195
- write_inheritable_attribute :columns_has_translations, (columns rescue []).collect{|col| col if attrs.include?(col.name.to_sym)}.compact
196
- class_inheritable_reader :columns_has_translations
197
-
198
- # forces given locale
199
- # I18n.locale = :lv
200
- # a = Article.find 18
201
- # a.title
202
- # => "LV title"
203
- # a.in(:en).title
204
- # => "EN title"
205
- def in locale
206
- locale.to_sym == I18n.default_locale ? self : find_translation(locale)
207
- end
208
-
209
- def find_or_build_translation(*args)
210
- locale = args.first.to_s
211
- build = args.second.present?
212
- find_translation(locale) || (build ? self.translations.build(:locale => locale) : self.translations.new(:locale => locale))
213
- end
214
-
215
- def translation(locale)
216
- find_translation(locale.to_s)
217
- end
218
-
219
- def all_translations
220
- t = I18n.available_locales.map do |locale|
221
- [locale, find_or_build_translation(locale)]
222
- end
223
- ActiveSupport::OrderedHash[t]
224
- end
225
-
226
- def has_translation?(locale)
227
- return true if locale == I18n.default_locale
228
- find_translation(locale).present?
229
- end
230
-
231
- # if object is new, then nested slaves ar built for all available locales
232
- def build_nested_translations
233
- if (I18n.available_locales.size - 1) > self.translations.size
234
- I18n.available_locales.clone.delete_if{|l| l == I18n.default_locale}.each do |l|
235
- options = {:locale => l.to_s}
236
- options[self.class.reflections[:translations].class_name.constantize.master_id] = self.id unless self.new_record?
237
- self.translations.build(options) unless self.translations.map(&:locale).include?(l.to_s)
238
- end
239
- end
240
- end
241
-
242
- if options[:reader]
243
- attrs.each do |name|
244
- send :define_method, name do
245
- unless I18n.default_locale == I18n.locale
246
- translation = self.translation(I18n.locale)
247
- if translation.nil?
248
- if has_translations_options[:fallback]
249
- (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
250
- else
251
- has_translations_options[:nil]
252
- end
253
- else
254
- if @return_raw_data
255
- (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
256
- else
257
- value = translation.send(name) and value.set_origins(self,name)
258
- end
259
- end
260
- else
261
- (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
262
- end
263
- end
264
- end
265
- end
266
-
267
- has_many :translations, :class_name => translation_class_name, :foreign_key => translation_class.master_id, :dependent => :destroy
268
- accepts_nested_attributes_for :translations, :allow_destroy => true, :reject_if => proc { |attributes| columns_has_translations.collect{|col| attributes[col.name].blank? ? nil : 1}.compact.empty? }
269
- translation_class.belongs_to belongs_to
270
- translation_class.validates_presence_of :locale
271
- translation_class.validates_uniqueness_of :locale, :scope => translation_class.master_id
272
-
273
- # Workaround to support Rails 2
274
- scope_method = if ActiveRecord::VERSION::MAJOR < 3 then :named_scope else :scope end
275
-
276
- send scope_method, :translated, lambda { |locale| {:conditions => ["#{translation_class.table_name}.locale = ?", locale.to_s], :joins => :translations} }
277
-
278
- #private is no good
279
-
280
- def find_translation(locale)
281
- locale = locale.to_s
282
- translations.detect { |t| t.locale == locale }
283
- end
284
- end
285
- end
286
-
287
- module TranslationClassMethods
288
- # sets real master_id it's aware of STI
289
- def extract_master_id name
290
- master_class = name.sub('Translation','').constantize
291
- #FIXME why need to check superclass ?
292
- class_name = master_class.name #!master_class.superclass.abstract_class? ? master_class.superclass.name : master_class.name
293
- self.master_id = :"#{class_name.demodulize.underscore}_id"
294
- end
1
+ require 'stringio'
2
+
3
+ class ActiveRecord::Base
4
+ # Provides ability to add the translations for the model using delegate pattern.
5
+ # Uses has_many association to the ModelNameTranslation.
6
+ #
7
+ # For example you have model Article with attributes title and text.
8
+ # You want that attributes title and text to be translated.
9
+ # For this reason you need to generate new model ArticleTranslation.
10
+ # In migration you need to add:
11
+ #
12
+ # create_table :article_translations do |t|
13
+ # t.references :article, :null => false
14
+ # t.string :locale, :length => 2, :null => false
15
+ # t.string :name, :null => false
16
+ # end
17
+ #
18
+ # add_index :articles, [:article_id, :locale], :unique => true, :name => 'unique_locale_for_article_id'
19
+ #
20
+ # And in the Article model:
21
+ #
22
+ # translations :title, :text
23
+ #
24
+ # This will adds:
25
+ #
26
+ # * named_scope (translated) and has_many association to the Article model
27
+ # * locale presence validation to the ArticleTranslation model.
28
+ #
29
+ # Notice: if you want to have validates_presence_of :article, you should use :inverse_of.
30
+ # Support this by yourself. Better is always to use artile.translations.build() method.
31
+ #
32
+ # For more information please read API. Feel free to write me an email to:
33
+ # dmitry.polushkin@gmail.com.
34
+ #
35
+ # ===
36
+ #
37
+ # You also can pass attributes and options to the translations class method:
38
+ #
39
+ # translations :title, :text, :fallback => true, :writer => true, :nil => nil
40
+ #
41
+ # ===
42
+ #
43
+ # Configuration options:
44
+ #
45
+ # * <tt>:fallback</tt> - if translation for the current locale not found.
46
+ # By default true.
47
+ # Uses algorithm of fallback:
48
+ # 0) current translation (using I18n.locale);
49
+ # 1) default locale (using I18n.default_locale);
50
+ # 2) :nil value (see <tt>:nil</tt> configuration option)
51
+ # * <tt>:reader</tt> - add reader attributes to the model and delegate them
52
+ # to the translation model columns. Add's fallback if it is set to true.
53
+ # * <tt>:writer</tt> - add writer attributes to the model and assign them
54
+ # to the translation model attributes.
55
+ # * <tt>:nil</tt> - when reader cant find string, it returns by default an
56
+ # empty string. If you want to change this setting for example to nil,
57
+ # add :nil => nil
58
+ #
59
+ # ===
60
+ #
61
+ # When you are using <tt>:writer</tt> option, you can create translations using
62
+ # update_attributes method. For example:
63
+ #
64
+ # Article.create!
65
+ # Article.update_attributes(:title => 'title', :text => 'text')
66
+ #
67
+ # ===
68
+ #
69
+ # <tt>translated</tt> named_scope is useful when you want to find only those
70
+ # records that are translated to a specific locale.
71
+ # For example if you want to find all Articles that is translated to an english
72
+ # language, you can write: Article.translated(:en)
73
+ #
74
+ # <tt>has_translation?(locale)</tt> method, that returns true if object's model
75
+ # have a translation for a specified locale
76
+ #
77
+ # <tt>translation(locale)</tt> method finds translation with specified locale.
78
+ #
79
+ # <tt>all_translations</tt> method that returns all possible translations in
80
+ # ordered hash (useful when creating forms with nested attributes).
81
+ def self.translations(*attrs)
82
+ options = {
83
+ :fallback => true,
84
+ :reader => true,
85
+ :writer => false,
86
+ :nil => ''
87
+ }.merge(attrs.extract_options!)
88
+ options.assert_valid_keys([:fallback, :reader, :writer, :nil])
89
+
90
+ class << self
91
+ # adds :translations to :includes if current locale differs from default
92
+ #FIXME is this enough with find or need to create chain for find_last, find_first and others?
93
+ alias_method(:find_without_translations, :find) unless method_defined?(:find_without_translations)
94
+ def find(*args)
95
+ if args[0].kind_of?(Hash)
96
+ args[0][:include] ||= []
97
+ args[0][:include] << :translations
98
+ end unless I18n.locale == I18n.default_locale
99
+ find_without_translations(*args)
100
+ end
101
+ # Defines given class recursively
102
+ # Example:
103
+ # create_class('Cms::Text::Page', Object, ActiveRecord::Base)
104
+ # => Cms::Text::Page
105
+ def create_class(class_name, parent, superclass, &block)
106
+ first,*other = class_name.split("::")
107
+ if other.empty?
108
+ klass = Class.new superclass, &block
109
+ parent.const_set(first, klass)
110
+ else
111
+ klass = Class.new
112
+ parent = unless parent.const_defined?(first)
113
+ parent.const_set(first, klass)
114
+ else
115
+ first.constantize
116
+ end
117
+ create_class(other.join('::'), parent, superclass, &block)
118
+ end
119
+ end
120
+ # defines "ModelNameTranslation" if it's not defined manualy
121
+ def define_translation_class name, attrs
122
+ klass = name.constantize rescue nil
123
+ unless klass
124
+ klass = create_class(name, Object, ActiveRecord::Base) do
125
+ # set's real table name
126
+ set_table_name name.sub('Translation','').constantize.table_name.singularize + "_translations"
127
+ cattr_accessor :translate_attrs, :master_id
128
+ # override validate to vaidate only translate fields from master Class
129
+ def validate
130
+ item = self.class.name.sub('Translation','').constantize.new(self.attributes.clone.delete_if{|k,_| !self.class.translate_attrs.include?(k.to_sym)})
131
+ was_table_name = item.class.table_name
132
+ item.class.set_table_name self.class.table_name
133
+ item.valid? rescue
134
+ self.class.translate_attrs.each do |attr|
135
+ errors_on_attr = item.errors.on(attr)
136
+ self.errors.add(attr,errors_on_attr) if errors_on_attr
137
+ end
138
+ item.class.set_table_name was_table_name
139
+ end
140
+ extend TranslationClassMethods
141
+ end
142
+ klass.translate_attrs = attrs
143
+ else
144
+ unless klass.respond_to?(:translate_attrs)
145
+ klass.send(:cattr_accessor, :translate_attrs, :master_id)
146
+ klass.send(:extend,TranslationClassMethods)
147
+ klass.translate_attrs = attrs
148
+ end
149
+ end
150
+
151
+ klass.extract_master_id(name)
152
+ klass
153
+ end
154
+ # creates translation table and adds missing fields
155
+ # So at first add the "translations :name, :desc" in your model
156
+ # then put YourModel.sync_translation_table! in db/seed.rb and run "rake db:seed"
157
+ # Later adding more fields in translations array, just run agin "rake db:seed"
158
+ # If you want to remove fields do it manualy, it's safer
159
+ def sync_translation_table!
160
+ out = StringIO.new
161
+ $stdout = out
162
+ translations_class = reflections[:translations].class_name.constantize
163
+ translations_table = translations_class.table_name
164
+ unless ActiveRecord::Migration::table_exists?(translations_table)
165
+ ActiveRecord::Migration.create_table translations_table do |t|
166
+ t.integer translations_class.master_id, :null => false
167
+ t.string :locale, :null => false, :limit => 5
168
+ columns_has_translations.each do |col|
169
+ t.send(col.type,col.name)
170
+ end
171
+ end
172
+ ActiveRecord::Migration.add_index translations_table, [translations_class.master_id, :locale], :unique => true
173
+ translations_class.reset_column_information
174
+ else
175
+ changes = false
176
+ columns_has_translations.each do |col|
177
+ unless translations_class.columns_hash.has_key?(col.name)
178
+ ActiveRecord::Migration.add_column(translations_table, col.name, col.type)
179
+ changes = true
180
+ end
181
+ end
182
+ translations_class.reset_column_information if changes
183
+ end
184
+ $stdout = STDOUT
185
+ end
186
+ end
187
+
188
+ translation_class_name = "#{self.name}Translation"
189
+ translation_class = self.define_translation_class(translation_class_name, attrs)
190
+ belongs_to = self.name.demodulize.underscore.to_sym
191
+
192
+ write_inheritable_attribute :has_translations_options, options
193
+ class_inheritable_reader :has_translations_options
194
+
195
+ write_inheritable_attribute :columns_has_translations, (columns rescue []).collect{|col| col if attrs.include?(col.name.to_sym)}.compact
196
+ class_inheritable_reader :columns_has_translations
197
+
198
+ # forces given locale
199
+ # I18n.locale = :lv
200
+ # a = Article.find 18
201
+ # a.title
202
+ # => "LV title"
203
+ # a.in(:en).title
204
+ # => "EN title"
205
+ def in locale
206
+ locale.to_sym == I18n.default_locale ? self : find_translation(locale)
207
+ end
208
+
209
+ def find_or_build_translation(*args)
210
+ locale = args.first.to_s
211
+ build = args.second.present?
212
+ find_translation(locale) || (build ? self.translations.build(:locale => locale) : self.translations.new(:locale => locale))
213
+ end
214
+
215
+ def translation(locale)
216
+ find_translation(locale.to_s)
217
+ end
218
+
219
+ def all_translations
220
+ t = I18n.available_locales.map do |locale|
221
+ [locale, find_or_build_translation(locale)]
222
+ end
223
+ ActiveSupport::OrderedHash[t]
224
+ end
225
+
226
+ def has_translation?(locale)
227
+ return true if locale == I18n.default_locale
228
+ find_translation(locale).present?
229
+ end
230
+
231
+ # if object is new, then nested slaves ar built for all available locales
232
+ def build_nested_translations
233
+ if (I18n.available_locales.size - 1) > self.translations.size
234
+ I18n.available_locales.clone.delete_if{|l| l == I18n.default_locale}.each do |l|
235
+ options = {:locale => l.to_s}
236
+ options[self.class.reflections[:translations].class_name.constantize.master_id] = self.id unless self.new_record?
237
+ self.translations.build(options) unless self.translations.map(&:locale).include?(l.to_s)
238
+ end
239
+ end
240
+ end
241
+
242
+ if options[:reader]
243
+ attrs.each do |name|
244
+ send :define_method, name do
245
+ unless I18n.default_locale == I18n.locale
246
+ translation = self.translation(I18n.locale)
247
+ if translation.nil?
248
+ if has_translations_options[:fallback]
249
+ (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
250
+ else
251
+ has_translations_options[:nil]
252
+ end
253
+ else
254
+ if @return_raw_data
255
+ (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
256
+ else
257
+ value = translation.send(name) and value.set_origins(self,name)
258
+ end
259
+ end
260
+ else
261
+ (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ has_many :translations, :class_name => translation_class_name, :foreign_key => translation_class.master_id, :dependent => :destroy
268
+ accepts_nested_attributes_for :translations, :allow_destroy => true, :reject_if => proc { |attributes| columns_has_translations.collect{|col| attributes[col.name].blank? ? nil : 1}.compact.empty? }
269
+ translation_class.belongs_to belongs_to
270
+ translation_class.validates_presence_of :locale
271
+ translation_class.validates_uniqueness_of :locale, :scope => translation_class.master_id
272
+
273
+ # Workaround to support Rails 2
274
+ scope_method = if ActiveRecord::VERSION::MAJOR < 3 then :named_scope else :scope end
275
+
276
+ send scope_method, :translated, lambda { |locale| {:conditions => ["#{translation_class.table_name}.locale = ?", locale.to_s], :joins => :translations} }
277
+
278
+ #private is no good
279
+
280
+ def find_translation(locale)
281
+ locale = locale.to_s
282
+ translations.detect { |t| t.locale == locale }
283
+ end
284
+ end
285
+ end
286
+
287
+ module TranslationClassMethods
288
+ # sets real master_id it's aware of STI
289
+ def extract_master_id name
290
+ master_class = name.sub('Translation','').constantize
291
+ #FIXME why need to check superclass ?
292
+ class_name = master_class.name #!master_class.superclass.abstract_class? ? master_class.superclass.name : master_class.name
293
+ self.master_id = :"#{class_name.demodulize.underscore}_id"
294
+ end
295
295
  end