globalize2 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +2 -1
  2. data/README.textile +27 -143
  3. data/VERSION +1 -1
  4. data/generators/templates/db_backend_migration.rb +3 -3
  5. data/globalize2.gemspec +30 -49
  6. data/init.rb +1 -8
  7. data/lib/globalize.rb +15 -0
  8. data/lib/globalize/active_record.rb +195 -0
  9. data/lib/globalize/active_record/adapter.rb +80 -0
  10. data/lib/globalize/active_record/attributes.rb +25 -0
  11. data/lib/globalize/active_record/migration.rb +40 -0
  12. data/lib/{globalize/i18n → i18n}/missing_translations_log_handler.rb +8 -8
  13. data/lib/{globalize/i18n → i18n}/missing_translations_raise_handler.rb +3 -5
  14. data/test/active_record/fallbacks_test.rb +102 -0
  15. data/test/{model/active_record → active_record}/migration_test.rb +21 -26
  16. data/test/{model/active_record → active_record}/sti_translated_test.rb +4 -30
  17. data/test/active_record/translates_test.rb +87 -0
  18. data/test/active_record/translation_class_test.rb +30 -0
  19. data/test/active_record/validation_tests.rb +75 -0
  20. data/test/active_record_test.rb +451 -0
  21. data/test/data/models.rb +16 -0
  22. data/test/data/schema.rb +23 -7
  23. data/test/i18n/missing_translations_test.rb +6 -6
  24. data/test/test_helper.rb +55 -15
  25. metadata +33 -46
  26. data/lib/globalize/backend/chain.rb +0 -102
  27. data/lib/globalize/backend/pluralizing.rb +0 -37
  28. data/lib/globalize/backend/static.rb +0 -61
  29. data/lib/globalize/load_path.rb +0 -63
  30. data/lib/globalize/locale/fallbacks.rb +0 -63
  31. data/lib/globalize/locale/language_tag.rb +0 -81
  32. data/lib/globalize/model/active_record.rb +0 -56
  33. data/lib/globalize/model/active_record/adapter.rb +0 -100
  34. data/lib/globalize/model/active_record/translated.rb +0 -174
  35. data/lib/globalize/translation.rb +0 -32
  36. data/lib/locale/root.yml +0 -3
  37. data/lib/rails_edge_load_path_patch.rb +0 -40
  38. data/notes.textile +0 -51
  39. data/test/backends/chained_test.rb +0 -175
  40. data/test/backends/pluralizing_test.rb +0 -63
  41. data/test/backends/static_test.rb +0 -147
  42. data/test/data/locale/all.yml +0 -2
  43. data/test/data/locale/de-DE.yml +0 -2
  44. data/test/data/locale/en-US.yml +0 -2
  45. data/test/data/locale/en-US/module.yml +0 -2
  46. data/test/data/locale/fi-FI/module.yml +0 -2
  47. data/test/data/locale/root.yml +0 -0
  48. data/test/load_path_test.rb +0 -49
  49. data/test/locale/fallbacks_test.rb +0 -154
  50. data/test/locale/language_tag_test.rb +0 -130
  51. data/test/model/active_record/translated_test.rb +0 -487
  52. data/test/translation_test.rb +0 -54
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  doc
2
2
  spec/spec/db/*
3
3
  vendor
4
- NOTES
4
+ NOTES
5
+ .DS_Store
@@ -1,16 +1,17 @@
1
1
  h1. Globalize2
2
2
 
3
- Globalize2 is the successor of Globalize for Rails.
3
+ Globalize2 is the successor of Globalize for Rails.
4
4
 
5
- It is compatible with and builds on the new "I18n api in Ruby on Rails":http://rails-i18n.org. and adds model translations as well as a bunch of other useful features, such as Locale fallbacks (RFC4647 compliant) and automatic loading of Locale data from defined directory/file locations.
5
+ It is compatible with and builds on the new "I18n api in Ruby on Rails":http://guides.rubyonrails.org/i18n.html. and adds model translations to ActiveRecord.
6
6
 
7
- Globalize2 is much more lightweight and modular than its predecessor was. Content translations in Globalize2 use default ActiveRecord features and do not limit any functionality any more.
8
-
9
- All features and tools in Globalize2 are implemented in the most unobstrusive and loosely-coupled way possible, so you can pick whatever features or tools you need for your application and combine them with other tools from other libraries or plugins.
7
+ Globalize2 is much more lightweight and compatible than its predecessor was. Model translations in Globalize2 use default ActiveRecord features and do not limit any ActiveRecord functionality any more.
10
8
 
11
9
  h2. Requirements
12
10
 
13
- Rails 2.2 (currently Rails edge)
11
+ ActiveRecord
12
+ I18n
13
+
14
+ (or Rails > 2.2)
14
15
 
15
16
  h2. Installation
16
17
 
@@ -20,19 +21,9 @@ To install Globalize2 with its default setup just use:
20
21
  script/plugin install git://github.com/joshmh/globalize2.git
21
22
  </code></pre>
22
23
 
23
- This will:
24
-
25
- * activate model translations
26
- * set I18n.load_path to an instance of Globalize::LoadPath
27
- * set I18n.backend to an instance of Globalize::Backend::Static
28
-
29
- h2. Configuration
30
-
31
- You might want to add additional configuration to an initializer, e.g. config/initializers/globalize.rb
32
-
33
24
  h2. Model translations
34
25
 
35
- Model translations (or content translations) allow you to translate your models' attribute values. E.g.
26
+ Model translations allow you to translate your models' attribute values. E.g.
36
27
 
37
28
  <pre><code>
38
29
  class Post < ActiveRecord::Base
@@ -44,10 +35,10 @@ Allows you to values for the attributes :title and :text per locale:
44
35
 
45
36
  <pre><code>
46
37
  I18n.locale = :en
47
- post.title # Globalize2 rocks!
38
+ post.title # => Globalize2 rocks!
48
39
 
49
40
  I18n.locale = :he
50
- post.title # גלובאלייז2 שולט!
41
+ post.title # => גלובאלייז2 שולט!
51
42
  </code></pre>
52
43
 
53
44
  In order to make this work, you'll need to add the appropriate translation tables. Globalize2 comes with a handy helper method to help you do this. It's called @create_translation_table!@. Here's an example:
@@ -69,134 +60,27 @@ end
69
60
 
70
61
  Note that the ActiveRecord model @Post@ must already exist and have a @translates@ directive listing the translated fields.
71
62
 
72
- h2. Globalize::Backend::Static
73
-
74
- Globalize2 ships with a Static backend that builds on the Simple backend from the I18n library (which is shipped with Rails) and adds the following features:
75
-
76
- * It uses locale fallbacks when looking up translation data.
77
- * It returns an instance of Globalize::Translation::Static instead of a plain Ruby String as a translation.
78
- * It allows to hook in custom pluralization logic as lambdas.
79
-
80
- h2. Custom pluralization logic
81
-
82
- The Simple backend has its pluralization algorithm baked in hardcoded. This algorithm is only suitable for English and other languages that have the same pluralization rules. It is not suitable for, e.g., Czech though.
83
-
84
- To add custom pluralization logic to Globalize' Static backend you can do something like this:
85
-
86
- <pre><code>
87
- @backend.add_pluralizer :cz, lambda{|c|
88
- c == 1 ? :one : (2..4).include?(c) ? :few : :other
89
- }
90
- </code></pre>
91
-
92
- h2. Locale Fallbacks
93
-
94
- Globalize2 ships with a Locale fallback tool which extends the I18n module to hold a fallbacks instance which is set to an instance of Globalize::Locale::Fallbacks by default but can be swapped with a different implementation.
95
-
96
- Globalize2 fallbacks will compute a number of other locales for a given locale. For example:
97
-
98
- <pre><code>
99
- I18n.fallbacks[:"es-MX"] # => [:"es-MX", :es, :"en-US", :en]
100
- </code></pre>
101
-
102
- Globalize2 fallbacks always fall back to
103
-
104
- * all parents of a given locale (e.g. :es for :"es-MX"),
105
- * then to the fallbacks' default locales and all of their parents and
106
- * finally to the :root locale.
107
-
108
- The default locales are set to [:"en-US"] by default but can be set to something else. The root locale is a concept borrowed from "CLDR":http://unicode.org and makes sense for storing common locale data which works as a last default fallback (e.g. "ltr" for bidi directions).
109
-
110
- One can additionally add any number of additional fallback locales manually. These will be added before the default locales to the fallback chain. For example:
111
-
112
- <pre><code>
113
- fb = I18n.fallbacks
114
-
115
- fb.map :ca => :"es-ES"
116
- fb[:ca] # => [:ca, :"es-ES", :es, :"en-US", :en]
117
-
118
- fb.map :"ar-PS" => :"he-IL"
119
- fb[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en]
120
- fb[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en]
121
-
122
- fb.map :sms => [:"se-FI", :"fi-FI"]
123
- fb[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en]
124
- </code></pre>
125
-
126
- h2. Globalize::LoadPath
127
-
128
- Globalize2 replaces the plain Ruby array that is set to I18n.load_path by default through an instance of Globalize::LoadPath.
129
-
130
- This object can be populated with both paths to files and directories. If a path to a directory is added to it it will look up all locale data files present in that directory enforcing the following convention:
131
-
132
- <pre><code>
133
- I18n.load_path << "#{RAILS_ROOT}/lib/locales"
134
-
135
- # will load all the following files if present:
136
- lib/locales/all.yml
137
- lib/locales/fr.yml
138
- lib/locales/fr/*.yaml
139
- lib/locales/ru.yml
140
- lib/locales/ru/*.yaml
141
- ...
142
- </code></pre>
143
-
144
- One can also specify which locales are used. By default this is set to "*" meaning that files for all locales are added. To define that only files for the locale :es are added one can specify:
145
-
146
- <pre><code>
147
- I18n.load_path.locales = [:es]
148
- </code></pre>
149
-
150
- One can also specify which file extensions are used. By default this is set to ['rb', 'yml'] so plain Ruby and YAML files are added if found. To define that only *.sql files are added one can specify:
151
-
152
- <pre><code>
153
- I18n.load_path.extensions = ['sql']
154
- </code></pre>
155
-
156
- Note that Globalize::LoadPath "expands" a directory to its contained file paths immediately when you add it to the load_path. Thus, if you change the locales or extensions settings in the middle of your application the change won't be applied to already added file paths.
157
-
158
-
159
- h2. Globalize::Translation classes
160
-
161
- Globalize2's Static backend as well as Globalize2 model translations return instances of Globalize::Translation classes (instead of plain Ruby Strings). These are simple and lightweight value objects that carry some additional meta data about the translation and how it was looked up.
162
-
163
- Model translations return instances of Globalize::Translation::Attribute, the Static backend returns instances of Globalize::Translation::Static.
164
-
165
- For example:
166
-
167
- <pre><code>
168
- I18n.locale = :de
169
-
170
- # Translation::Attribute
171
- title = Post.first.title # assuming that no translation can be found:
172
- title.locale # => :en
173
- title.requested_locale # => :de
174
- title.fallback? # => true
175
-
176
- # Translation::Static
177
- rails = I18n.t :rails # assuming that no translation can be found:
178
- rails.locale # => :en
179
- rails.requested_locale # => :de
180
- rails.fallback? # => true
181
- rails.options # returns the options passed to #t
182
- rails.plural_key # returns the plural_key (e.g. :one, :other)
183
- rails.original # returns the original translation with no values
184
- # interpolated to it (e.g. "Hi {{name}}!")
185
- </code></pre>
63
+ h2. Migration from Globalize
186
64
 
187
- h2. Missing Translations Log Handler
65
+ See this script by Tomasz Stachewicz: http://gist.github.com/120867
188
66
 
189
- A simple exception handler that behaves like the default exception handler but additionally logs missing translations to a given log.
67
+ h2. Changes since Globalize2 v0.1.0
190
68
 
191
- Useful for identifying missing translations during testing.
69
+ * The association globalize_translations has been renamed to translations.
192
70
 
193
- E.g.
71
+ h2. Alternative Solutions
194
72
 
195
- require 'globalize/i18n/missing_translations_log_handler
196
- I18n.missing_translations_logger = RAILS_DEFAULT_LOGGER
197
- I18n.exception_handler = :missing_translations_log_handler
73
+ * "Veger's fork":http://github.com/veger/globalize2 - uses default AR schema for the default locale, delegates to the translations table for other locales only
74
+ * "TranslatableColumns":http://github.com/iain/translatable_columns - have multiple languages of the same attribute in a model (Iain Hecker)
75
+ * "localized_record":http://github.com/glennpow/localized_record - allows records to have localized attributes without any modifications to the database (Glenn Powell)
76
+ * "model_translations":http://github.com/janne/model_translations - Minimal implementation of Globalize2 style model translations (Jan Andersson)
198
77
 
199
- To set up a different log file:
78
+ h2. Related solutions
200
79
 
201
- logger = Logger.new("#{RAILS_ROOT}/log/missing_translations.log")
202
- I18n.missing_translations_logger = logger
80
+ * "globalize2_versioning":http://github.com/joshmh/globalize2_versioning - acts_as_versioned style versioning for Globalize2 (Joshua Harvey)
81
+ * "i18n_multi_locales_validations":http://github.com/ZenCocoon/i18n_multi_locales_validations - multi-locales attributes validations to validates attributes from Globalize2 translations models (Sébastien Grosjean)
82
+ * "Globalize2 Demo App":http://github.com/svenfuchs/globalize2-demo - demo application for Globalize2 (Sven Fuchs)</li>
83
+ * "migrate_from_globalize1":http://gist.github.com/120867 - migrate model translations from Globalize1 to Globalize2 (Tomasz Stachewicz)</li>
84
+ * "easy_globalize2_accessors":http://github.com/astropanic/easy_globalize2_accessors - easily access (read and write) globalize2-translated fields (astropanic, Tomasz Stachewicz)</li>
85
+ * "globalize2-easy-translate":http://github.com/bsamman/globalize2-easy-translate - adds methods to easily access or set translated attributes to your model (bsamman)</li>
86
+ * "batch_translations":http://github.com/alvarezrilla/batch_translations - allow saving multiple Globalize2 translations in the same request (Jose Alvarez Rilla)</li>
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -8,16 +8,16 @@ class ActsAsTaggableMigration < ActiveRecord::Migration
8
8
  end
9
9
 
10
10
  # TODO: FINISH DOING MIGRATION -- stopped in the middle
11
-
11
+
12
12
  create_table :globalize_translations_map do |t|
13
13
  t.string :key, :null => false
14
14
  t.integer :translation_id, :null => false
15
15
  end
16
-
16
+
17
17
  add_index :taggings, :tag_id
18
18
  add_index :taggings, [:taggable_id, :taggable_type]
19
19
  end
20
-
20
+
21
21
  def self.down
22
22
  drop_table :globalize_translations
23
23
  drop_table :tags
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{globalize2}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Sven Fuchs, Joshua Harvey, Clemens Kofler"]
12
- s.date = %q{2009-11-08}
12
+ s.date = %q{2010-04-22}
13
13
  s.description = %q{Rails I18n: de-facto standard library for ActiveRecord data translation}
14
14
  s.email = %q{joshmh@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -26,66 +26,46 @@ Gem::Specification.new do |s|
26
26
  "generators/templates/db_backend_migration.rb",
27
27
  "globalize2.gemspec",
28
28
  "init.rb",
29
- "lib/globalize/backend/chain.rb",
30
- "lib/globalize/backend/pluralizing.rb",
31
- "lib/globalize/backend/static.rb",
32
- "lib/globalize/i18n/missing_translations_log_handler.rb",
33
- "lib/globalize/i18n/missing_translations_raise_handler.rb",
34
- "lib/globalize/load_path.rb",
35
- "lib/globalize/locale/fallbacks.rb",
36
- "lib/globalize/locale/language_tag.rb",
37
- "lib/globalize/model/active_record.rb",
38
- "lib/globalize/model/active_record/adapter.rb",
39
- "lib/globalize/model/active_record/translated.rb",
40
- "lib/globalize/translation.rb",
41
- "lib/locale/root.yml",
42
- "lib/rails_edge_load_path_patch.rb",
43
- "notes.textile",
29
+ "lib/globalize.rb",
30
+ "lib/globalize/active_record.rb",
31
+ "lib/globalize/active_record/adapter.rb",
32
+ "lib/globalize/active_record/attributes.rb",
33
+ "lib/globalize/active_record/migration.rb",
34
+ "lib/i18n/missing_translations_log_handler.rb",
35
+ "lib/i18n/missing_translations_raise_handler.rb",
36
+ "test/active_record/fallbacks_test.rb",
37
+ "test/active_record/migration_test.rb",
38
+ "test/active_record/sti_translated_test.rb",
39
+ "test/active_record/translates_test.rb",
40
+ "test/active_record/translation_class_test.rb",
41
+ "test/active_record/validation_tests.rb",
42
+ "test/active_record_test.rb",
44
43
  "test/all.rb",
45
- "test/backends/chained_test.rb",
46
- "test/backends/pluralizing_test.rb",
47
- "test/backends/static_test.rb",
48
- "test/data/locale/all.yml",
49
- "test/data/locale/de-DE.yml",
50
- "test/data/locale/en-US.yml",
51
- "test/data/locale/en-US/module.yml",
52
- "test/data/locale/fi-FI/module.yml",
53
- "test/data/locale/root.yml",
54
44
  "test/data/models.rb",
55
45
  "test/data/no_globalize_schema.rb",
56
46
  "test/data/schema.rb",
57
47
  "test/i18n/missing_translations_test.rb",
58
- "test/load_path_test.rb",
59
- "test/locale/fallbacks_test.rb",
60
- "test/locale/language_tag_test.rb",
61
- "test/model/active_record/migration_test.rb",
62
- "test/model/active_record/sti_translated_test.rb",
63
- "test/model/active_record/translated_test.rb",
64
- "test/test_helper.rb",
65
- "test/translation_test.rb"
48
+ "test/test_helper.rb"
66
49
  ]
67
50
  s.homepage = %q{http://github.com/joshmh/globalize2}
68
51
  s.rdoc_options = ["--charset=UTF-8"]
69
52
  s.require_paths = ["lib"]
70
- s.rubygems_version = %q{1.3.5}
53
+ s.rubygems_version = %q{1.3.6}
71
54
  s.summary = %q{Rails I18n: de-facto standard library for ActiveRecord data translation}
72
55
  s.test_files = [
73
- "test/all.rb",
74
- "test/backends/chained_test.rb",
75
- "test/backends/pluralizing_test.rb",
76
- "test/backends/static_test.rb",
56
+ "test/active_record/fallbacks_test.rb",
57
+ "test/active_record/migration_test.rb",
58
+ "test/active_record/sti_translated_test.rb",
59
+ "test/active_record/translates_test.rb",
60
+ "test/active_record/translation_class_test.rb",
61
+ "test/active_record/validation_tests.rb",
62
+ "test/active_record_test.rb",
63
+ "test/all.rb",
77
64
  "test/data/models.rb",
78
65
  "test/data/no_globalize_schema.rb",
79
66
  "test/data/schema.rb",
80
67
  "test/i18n/missing_translations_test.rb",
81
- "test/load_path_test.rb",
82
- "test/locale/fallbacks_test.rb",
83
- "test/locale/language_tag_test.rb",
84
- "test/model/active_record/migration_test.rb",
85
- "test/model/active_record/sti_translated_test.rb",
86
- "test/model/active_record/translated_test.rb",
87
- "test/test_helper.rb",
88
- "test/translation_test.rb"
68
+ "test/test_helper.rb"
89
69
  ]
90
70
 
91
71
  if s.respond_to? :specification_version then
@@ -98,3 +78,4 @@ Gem::Specification.new do |s|
98
78
  else
99
79
  end
100
80
  end
81
+
data/init.rb CHANGED
@@ -1,8 +1 @@
1
- require 'globalize/model/active_record'
2
-
3
- I18n.backend = Globalize::Backend::Static.new
4
-
5
- I18n.load_path = Globalize::LoadPath.new I18n.load_path
6
- I18n.load_path << "#{File.dirname(__FILE__)}/lib/locale"
7
- I18n.load_path << "#{RAILS_ROOT}/lib/locale"
8
-
1
+ require 'globalize'
@@ -0,0 +1,15 @@
1
+ module Globalize
2
+ autoload :ActiveRecord, 'globalize/active_record'
3
+
4
+ class << self
5
+ def fallbacks?
6
+ I18n.respond_to?(:fallbacks)
7
+ end
8
+
9
+ def fallbacks(locale)
10
+ fallbacks? ? I18n.fallbacks[locale] : [locale.to_sym]
11
+ end
12
+ end
13
+ end
14
+
15
+ ActiveRecord::Base.send(:include, Globalize::ActiveRecord)
@@ -0,0 +1,195 @@
1
+ module Globalize
2
+ class MigrationError < StandardError; end
3
+ class MigrationMissingTranslatedField < MigrationError; end
4
+ class BadMigrationFieldType < MigrationError; end
5
+
6
+ module ActiveRecord
7
+ autoload :Adapter, 'globalize/active_record/adapter'
8
+ autoload :Attributes, 'globalize/active_record/attributes'
9
+ autoload :Migration, 'globalize/active_record/migration'
10
+
11
+ def self.included(base)
12
+ base.extend ActMacro
13
+ end
14
+
15
+ class << self
16
+ def build_translation_class(target, options)
17
+ options[:table_name] ||= "#{target.table_name.singularize}_translations"
18
+
19
+ klass = target.const_defined?(:Translation) ?
20
+ target.const_get(:Translation) :
21
+ target.const_set(:Translation, Class.new(::ActiveRecord::Base))
22
+
23
+ klass.class_eval do
24
+ set_table_name(options[:table_name])
25
+ belongs_to target.name.underscore.gsub('/', '_')
26
+ def locale; read_attribute(:locale).to_sym; end
27
+ def locale=(locale); write_attribute(:locale, locale.to_s); end
28
+ end
29
+
30
+ klass
31
+ end
32
+ end
33
+
34
+ module ActMacro
35
+ def locale
36
+ (defined?(@@locale) && @@locale)
37
+ end
38
+
39
+ def locale=(locale)
40
+ @@locale = locale
41
+ end
42
+
43
+ def translates(*attr_names)
44
+ return if translates?
45
+ options = attr_names.extract_options!
46
+
47
+ class_inheritable_accessor :translation_class, :translated_attribute_names
48
+ class_inheritable_writer :required_attributes
49
+ self.translation_class = ActiveRecord.build_translation_class(self, options)
50
+ self.translated_attribute_names = attr_names.map(&:to_sym)
51
+
52
+ include InstanceMethods
53
+ extend ClassMethods, Migration
54
+
55
+ after_save :save_translations!
56
+ has_many :translations, :class_name => translation_class.name,
57
+ :foreign_key => class_name.foreign_key,
58
+ :dependent => :delete_all,
59
+ :extend => HasManyExtensions
60
+
61
+ named_scope :with_translations, lambda { |locale|
62
+ conditions = required_attributes.map do |attribute|
63
+ "#{quoted_translation_table_name}.#{attribute} IS NOT NULL"
64
+ end
65
+ conditions << "#{quoted_translation_table_name}.locale = ?"
66
+ { :include => :translations, :conditions => [conditions.join(' AND '), locale] }
67
+ }
68
+
69
+ attr_names.each { |attr_name| translated_attr_accessor(attr_name) }
70
+ end
71
+
72
+ def translates?
73
+ included_modules.include?(InstanceMethods)
74
+ end
75
+ end
76
+
77
+ module HasManyExtensions
78
+ def by_locale(locale)
79
+ first(:conditions => { :locale => locale.to_s })
80
+ end
81
+
82
+ def by_locales(locales)
83
+ all(:conditions => { :locale => locales.map(&:to_s) })
84
+ end
85
+ end
86
+
87
+ module ClassMethods
88
+ delegate :set_translation_table_name, :to => :translation_class
89
+
90
+ def with_locale(locale)
91
+ previous_locale, self.locale = self.locale, locale
92
+ result = yield
93
+ self.locale = previous_locale
94
+ result
95
+ end
96
+
97
+ def translation_table_name
98
+ translation_class.table_name
99
+ end
100
+
101
+ def quoted_translation_table_name
102
+ translation_class.quoted_table_name
103
+ end
104
+
105
+ def required_attributes
106
+ @required_attributes ||= reflect_on_all_validations.select do |validation|
107
+ validation.macro == :validates_presence_of && translated_attribute_names.include?(validation.name)
108
+ end.map(&:name)
109
+ end
110
+
111
+ def respond_to?(method, *args, &block)
112
+ method.to_s =~ /^find_by_(\w+)$/ && translated_attribute_names.include?($1.to_sym) || super
113
+ end
114
+
115
+ def method_missing(method, *args)
116
+ if method.to_s =~ /^find_by_(\w+)$/ && translated_attribute_names.include?($1.to_sym)
117
+ find_first_by_translated_attr_and_locales($1, args.first)
118
+ else
119
+ super
120
+ end
121
+ end
122
+
123
+ protected
124
+
125
+ def find_first_by_translated_attr_and_locales(name, value)
126
+ query = "#{translated_attr_name(name)} = ? AND #{translated_attr_name('locale')} IN (?)"
127
+ locales = Globalize.fallbacks(locale || I18n.locale).map(&:to_s)
128
+ find(:first, :joins => :translations, :conditions => [query, value, locales])
129
+ end
130
+
131
+ def translated_attr_accessor(name)
132
+ define_method "#{name}=", lambda { |value|
133
+ globalize.write(self.class.locale || I18n.locale, name, value)
134
+ self[name] = value
135
+ }
136
+ define_method name, lambda { |*args|
137
+ globalize.fetch(args.first || self.class.locale || I18n.locale, name)
138
+ }
139
+ alias_method "#{name}_before_type_cast", name
140
+ end
141
+
142
+ def translated_attr_name(name)
143
+ "#{translation_class.table_name}.#{name}"
144
+ end
145
+ end
146
+
147
+ module InstanceMethods
148
+ def globalize
149
+ @globalize ||= Adapter.new self
150
+ end
151
+
152
+ def attributes=(attributes, *args)
153
+ if attributes.respond_to?(:delete) && locale = attributes.delete(:locale)
154
+ self.class.with_locale(locale) { super }
155
+ else
156
+ super
157
+ end
158
+ end
159
+
160
+ def available_locales
161
+ translations.scoped(:select => 'DISTINCT locale').map(&:locale)
162
+ end
163
+
164
+ def translated_locales
165
+ translations.map(&:locale)
166
+ end
167
+
168
+ def translated_attributes
169
+ translated_attribute_names.inject({}) do |attributes, name|
170
+ attributes.merge(name => send(name))
171
+ end
172
+ end
173
+
174
+ def set_translations(options)
175
+ options.keys.each do |locale|
176
+ translation = translations.find_by_locale(locale.to_s) ||
177
+ translations.build(:locale => locale.to_s)
178
+ translation.update_attributes!(options[locale])
179
+ end
180
+ end
181
+
182
+ def reload(options = nil)
183
+ translated_attribute_names.each { |name| @attributes.delete(name.to_s) }
184
+ globalize.reset
185
+ super(options)
186
+ end
187
+
188
+ protected
189
+
190
+ def save_translations!
191
+ globalize.save_translations!
192
+ end
193
+ end
194
+ end
195
+ end