Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/Appraisals +33 -0
  3. data/CHANGELOG.md +111 -0
  4. data/CONTRIBUTING.md +52 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +22 -0
  7. data/README.md +437 -0
  8. data/Rakefile +55 -0
  9. data/lib/globalize.rb +98 -0
  10. data/lib/globalize/active_record.rb +14 -0
  11. data/lib/globalize/active_record/act_macro.rb +96 -0
  12. data/lib/globalize/active_record/adapter.rb +108 -0
  13. data/lib/globalize/active_record/adapter_dirty.rb +56 -0
  14. data/lib/globalize/active_record/attributes.rb +26 -0
  15. data/lib/globalize/active_record/class_methods.rb +129 -0
  16. data/lib/globalize/active_record/exceptions.rb +13 -0
  17. data/lib/globalize/active_record/instance_methods.rb +246 -0
  18. data/lib/globalize/active_record/migration.rb +215 -0
  19. data/lib/globalize/active_record/translated_attributes_query.rb +181 -0
  20. data/lib/globalize/active_record/translation.rb +45 -0
  21. data/lib/globalize/interpolation.rb +28 -0
  22. data/lib/globalize/version.rb +3 -0
  23. data/lib/i18n/missing_translations_log_handler.rb +41 -0
  24. data/lib/i18n/missing_translations_raise_handler.rb +25 -0
  25. data/lib/patches/active_record/persistence.rb +17 -0
  26. data/lib/patches/active_record/query_method.rb +3 -0
  27. data/lib/patches/active_record/rails4/query_method.rb +35 -0
  28. data/lib/patches/active_record/rails4/serialization.rb +22 -0
  29. data/lib/patches/active_record/rails4/uniqueness_validator.rb +42 -0
  30. data/lib/patches/active_record/rails5/uniqueness_validator.rb +47 -0
  31. data/lib/patches/active_record/rails5_1/serialization.rb +22 -0
  32. data/lib/patches/active_record/rails5_1/uniqueness_validator.rb +45 -0
  33. data/lib/patches/active_record/relation.rb +12 -0
  34. data/lib/patches/active_record/serialization.rb +5 -0
  35. data/lib/patches/active_record/uniqueness_validator.rb +7 -0
  36. data/lib/patches/active_record/xml_attribute_serializer.rb +23 -0
  37. metadata +264 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d8078f7a1f7ff25215d50a2ea7f8b9443ef30b1b
4
+ data.tar.gz: effc8592431b32d18d1d634864f04f9173743ce7
5
+ SHA512:
6
+ metadata.gz: df4ebd9d3f7acd7c3c2a7acb887d2fbda84c560f248f6676bb52321851ddf3a834eaf7357051fe3465f56832f6980374875149788f49b146c27faa12c9e9e4cc
7
+ data.tar.gz: 7f4926cc513a76a651b6be3089da5d89d243966fc9cea2f790fe928876c0977fe6af72776c80af51c86d046f845a013a6c37f14126af0973455f2a8c608d84b8
data/Appraisals ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ RAILS_VERSIONS = %w[
4
+ 4.2.10
5
+ 5.1.6
6
+ 5.2.1
7
+ ]
8
+
9
+ RAILS_VERSIONS.each do |version|
10
+ appraise "rails_#{version}" do
11
+ gem 'activemodel', version
12
+ gem 'activerecord', version
13
+
14
+ platforms :rbx do
15
+ gem "rubysl", "~> 2.0"
16
+ gem "rubinius-developer_tools"
17
+ end
18
+
19
+ platforms :jruby do
20
+ if !ENV['TRAVIS'] || ENV['DB'] == 'sqlite3'
21
+ gem 'activerecord-jdbcsqlite3-adapter', '~> 1'
22
+ end
23
+
24
+ if !ENV['TRAVIS'] || ENV['DB'] == 'mysql'
25
+ gem 'activerecord-jdbcmysql-adapter', '~> 1'
26
+ end
27
+
28
+ if !ENV['TRAVIS'] || %w(postgres postgresql).include?(ENV['DB'])
29
+ gem 'activerecord-jdbcpostgresql-adapter', '~> 1'
30
+ end
31
+ end
32
+ end
33
+ end
data/CHANGELOG.md ADDED
@@ -0,0 +1,111 @@
1
+ # Globalize Changelog
2
+
3
+ ## 5.1.0 (2018-01-15)
4
+
5
+ * Replaced `after_` callbacks with `before_` callbacks and set `autosave: true` by default. [#341](https://github.com/globalize/globalize/pull/341) by [Andrew Volozhanin](https://github.com/scarfacedeb)
6
+ * Add [RequestStore](https://github.com/steveklabnik/request_store) to make Globalize thread-safe again [#420](https://github.com/globalize/globalize/pull/420)
7
+ * Join the translations table when ordering by translated attributes (https://github.com/globalize/globalize/pull/447). (thanks [Thomas Maas](https://github.com/thomasmaas) & [Michal Cichra](https://github.com/mikz)).
8
+ * Add `unique` to `with_translation` to prevent duplicates if fallbacks are defined and the queried locale is not the fallback language. [#489](https://github.com/globalize/globalize/pull/489) by [krisdigital](https://github.com/krisdigital)
9
+
10
+ ## 5.0.1 (2015-02-15)
11
+
12
+ * Don't touch table when model is loaded. [#412](https://github.com/globalize/globalize/pull/412)
13
+ * Remove handling for locale attribute on parent model [#411](https://github.com/globalize/globalize/pull/411) by awesome [Tekin Suleyman](https://github.com/tekin).
14
+
15
+ ## 5.0.0 (2015-02-03)
16
+ * Added support for Rails 4.2, but removed support for every previous version of Rails. This is a backward incompatible change, thus the version is now 5.0.0. (thanks [Nico Ritsche](https://github.com/ncri) and others). [#396](https://github.com/globalize/globalize/pull/396).
17
+
18
+ ## 4.0.3 (2014-11-24)
19
+ * Fixes a problem where after dup the dup'd model and the original model shared a translation instance, which means that if you mutate a translated field on the dup and save it, the original becomes a clone of the dup. [#352](https://github.com/globalize/globalize/pull/352).
20
+ * Deprecated `with_required_attributes`, `required_attributes`, and `required_translated_attributes`. `with_translations` no longer invokes `with_required_attributes`. [#355](https://github.com/globalize/globalize/pull/355).
21
+ * Removed all usages of `Thread.local`. [#374](https://github.com/globalize/globalize/pull/374). (thanks [Hubert Lee](https://github.com/hube)).
22
+ * Added `available_locales` method. This duplicates `translated_locales` method, but it doesn't use a separate `DISTINCT` query. [#339](https://github.com/globalize/globalize/pull/339). (thanks [Andrew Volozhanin](https://github.com/scarfacedeb)).
23
+
24
+ ## 4.0.2 (2014-06-29)
25
+ * Use `reflections` class method so `ensure_foreign_key_for` works in AR >= 4.1.2, fixes [#353](https://github.com/globalize/globalize/pull/353).
26
+ * Set `touch:true` on `belongs_to` for the globalized model, fixes [#330](https://github.com/globalize/globalize/pull/330) (thanks [shlensky](https://github.com/shlensky)).
27
+ * Accept optional arguments passed to `where_values_hash`, fixes [#354](https://github.com/globalize/globalize/pull/354) (thanks [felixbuenemann](https://github.com/felixbuenemann)).
28
+
29
+ ## 4.0.1 (2014-03-29)
30
+ * Fix bug where `with_translations` only works if called after `where` in relation chain, fixes [#343](https://github.com/globalize/globalize/issues/343).
31
+ * Use `preload` and `joins` instead of `includes` in `with_translations`, fixes [#329](https://github.com/globalize/globalize/issues/329) (thanks [Andrew Volozhanin](https://github.com/scarfacedeb)).
32
+ * Update `database_cleaner` dependency to 1.2.0.
33
+ * Support use of `first`/`take`/`last` with limit on queries with translated attributes, fixes [#322](https://github.com/globalize/globalize/issues/322) (thanks [prusswan](https://github.com/prusswan)).
34
+ * Ensure that options are always extracted from `attr_names` in `Globalize::ActiveRecord::ActMacro#translates`, PR [#319](https://github.com/globalize/globalize/pull/319) (thanks [Marek](https://github.com/keram)).
35
+
36
+ ## 4.0.0 (2014-01-04)
37
+ * Extract all versioning-related code to separate [globalize-versioning](https://github.com/globalize/globalize-versioning) gem.
38
+
39
+ ## 4.0.0.alpha.5 (2014-01-04)
40
+ * Fix issue where globalize breaks has_many through when model called with `where` (thanks [Paul McMahon](https://github.com/pwim)).
41
+ * Modify dup so that translations are copied, and remove custom clone code to conform to Rails/AR semantics (thanks [Paul McMahon](https://github.com/pwim)).
42
+
43
+ ## 4.0.0.alpha.4 (2013-12-30)
44
+ * Add this changelog.
45
+ * Add contributing guidelines.
46
+ * Group options into more structured methods in act_macro.rb.
47
+ * Remove dynamic finder code from globalize3, no longer used in AR4.
48
+ * Get hash of translated attributes by calling attribute on model, not translation.
49
+ * Define translation readers/writers in separate methods.
50
+ * Test against AR 4.1 and AR 4.0.
51
+ * Switch to minitest-reporters for colouring output from minitest.
52
+ * Remove find_or_instantiator_by_attributes which is no longer used in AR4.
53
+ * Set I18n.available_locales in tests to avoid deprecation message.
54
+ * Reorganize specs into describe blocks to clarify object of specs.
55
+
56
+ ## 4.0.0.alpha.3 (2013-12-18)
57
+
58
+ * Move ActiveRecord::Relation#where_values_hash patch into globalize relation class to avoid monkeypatching.
59
+ * Add Code Climate Score (thanks [BrandonMathis](https://github.com/BrandonMathis)).
60
+ * Query using Globalize.fallbacks rather than locale only when fetching a record (thanks [@huoxito](https://github.com/huoxito)).
61
+ * Use a module (QueryMethods) rather than a class for overriding functionality of ActiveRecord::Relation.
62
+ * Use ActiveRecord::Relation#extending! to extend ActiveRecord::Base#relation with QueryMethods, works with associations as well.
63
+
64
+ ## 4.0.0.alpha.2 (2013-10-24)
65
+
66
+ * Add license to gemspec.
67
+ * Update references to ActiveRecord 3 -> ActiveRecord.
68
+ * Replace references to globalize3 with globalize and remove references to ActiveRecord 3.x.
69
+ * Document `3-0-stable` branch in readme.
70
+ * Convert test syntax to MiniTest::Spec.
71
+ * Extract easy accessors functionality, moved to new [globalize-accessors](https://github.com/globalize/globalize-accessors) gem.
72
+ * Check that `first` is not nil before reloading translations, fixes [#282](https://github.com/globalize/globalize/issues/282).
73
+ * Duplicate arguments in query finders before modifying them, fixes [#284](https://github.com/globalize/globalize/issues/284).
74
+ * Add test for `find_or_create_by` with translated attribute.
75
+
76
+ ## 4.0.0.alpha.1 (2013-10-09)
77
+
78
+ * Initial release of Rails 4-compatible gem.
79
+
80
+ ## 3.1.0 (2014-01-25)
81
+
82
+ * Backport scope support on uniqueness validation from 4.0, drop support for ActiveRecord < 3.1, fixes [#324](https://github.com/globalize/globalize/issues/324).
83
+
84
+ ## 3.0.5 (2015-04-24)
85
+
86
+ * Now working properly with the squeel library. (thanks [Toru Mori](https://github.com/torumori)). See [#437](https://github.com/globalize/globalize/pull/437)
87
+
88
+ ## 3.0.4 (2014-01-08)
89
+
90
+ * Extract all versioning-related code to separate [globalize-versioning](https://github.com/globalize/globalize-versioning) gem.
91
+
92
+ ## 3.0.3 (2013-12-26)
93
+
94
+ * Ensure that foreign key is always set when saving translations (thanks [Andrew Feng](https://github.com/mingliangfeng)).
95
+ * Patch I18n to add back I18n.interpolate after it was removed (accidentally?) in v0.5.2 (see [svenfuchs/i18n#232](https://github.com/svenfuchs/i18n/issues/232). Hopefully this patch will be temporary.
96
+ * Explicitly test compatibility with FriendlyId to avoid issues like [#306](https://github.com/globalize/globalize/issues/306).
97
+ * Only override ActiveRecord::Base#relation to patch where_values_hash if using AR >= 3.2.1.
98
+
99
+ ## 3.0.2 (2013-12-07)
100
+
101
+ * Alias `ActiveRecord::Base#relation` and include query method overrides as module, fixes [#306](https://github.com/globalize/globalize/issues/306) and [norman/friendly_id#485](https://github.com/norman/friendly_id/issues/485).
102
+
103
+ ## 3.0.1 (2013-11-07)
104
+
105
+ * Move `ActiveRecord::Relation#where_values_hash` patch to Globalize-specific Relation class that inherits from `ActiveRecord::Relation` to fix compatibility issue with Squeel ([#288](https://github.com/globalize/globalize/issues/288)).
106
+ * Use FriendlyId pattern for overriding `ActiveRecord::Base#relation` to avoid conflict.
107
+ * Remove `:null => false` condition on reference to parent model in translation table migration, partial fix for [refinery/refinerycms#2450](https://github.com/refinery/refinerycms/issues/2450).
108
+
109
+ ## 3.0.0 (2013-10-24)
110
+
111
+ * Initial release with new version numbering.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,52 @@
1
+ # Contributing to Globalize
2
+
3
+ Globalize is a community project, and would not be here today if it were not for the support of the community of [contributors](https://github.com/globalize/globalize/graphs/contributors) that have kept it alive and running. Thank you for your support!
4
+
5
+ ## Bugs
6
+
7
+ If you find a bug or something is not working as expected, please search through the [github issues](https://github.com/globalize/globalize/issues) and on [stackoverflow](http://stackoverflow.com/questions/tagged/globalize) first. If you cannot find any answers related to your issue, post a new one and we will try to address it as soon as we can. Note that we prioritize Rails 4 issues (`master` branch) over Rails 3 issues (`3-0-stable` branch).
8
+
9
+ If you also have some idea how to fix the bug, then by all means post a pull request (see below).
10
+
11
+ ## Features
12
+
13
+ Have an idea for a new feature? Great! Keep in mind though that we are trying to cut down on non-core functionality in the Globalize core and push it to separate extensions, such as [globalize-accessors](https://github.com/globalize/globalize-accessors). If you are proposing something like this, we would prefer you to create a separate repository and gem for it.
14
+
15
+ If however your feature would improve the core functionality of Globalize, please do submit a PR, preferably to the `master` branch.
16
+
17
+ ## Refactoring
18
+
19
+ Have some free time? Help us improve our [code climate score](https://codeclimate.com/github/globalize/globalize) by refactoring the codebase. If the tests still pass and the changes seem reasonable, we will happily merge them. As elsewhere, priority always goes to the Rails/AR 4 series (`master` branch).
20
+
21
+ ## Documentation
22
+
23
+ Globalize needs better documentation. That includes more inline comments explaining clearly what code is doing, as well as reference documentation beyond the [readme](readme.md) -- possibly in the github wiki. Please contact us if you would like to help with documentation.
24
+
25
+ ## Pull Requests
26
+
27
+ Have a bug fix, code improvement or proposed feature? Do the following:
28
+
29
+ 1. Fork the repository.
30
+ 2. Create your feature branch: `git checkout -b my_new_feature`
31
+ 3. Commit your changes: `git commit -am 'Add some new feature'`
32
+ 4. Push to the branch: `git push origin my_new_feature`
33
+ 5. Submit a pull request.
34
+
35
+ For pull requests to Rails/ActiveRecord 4 version of Globalize (v3.x), post to the `master` branch. For pull requests to the Rails/ActiveRecord 3.x version of Globalize (3.x), post to the `3-0-stable` branch.
36
+
37
+ When you submit the pull request, Travis CI will run the [test suite](https://travis-ci.org/globalize/globalize) against your branch and will highlight any failures. Unless there is a good reason for it, we do not generally accept pull requests that take Globalize from green to red.
38
+
39
+ ## Testing
40
+
41
+ ### Requirements
42
+
43
+ - Ruby
44
+ - Bundler
45
+ - SQLite
46
+ - You can switch the database by adding an environment variable. See `test/support/database.rb`.
47
+ - You can also configure your database configurations. See `test/support/database.yml`.
48
+
49
+ ### Run tests on your local machine
50
+
51
+ - `bundle install`
52
+ - `bundle exec rake`
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2008-2014 Sven Fuchs, Joshua Harvey, Clemens Kofler, John-Paul
4
+ Bader, Tomasz Stachewicz, Philip Arndt, Chris Salzberg
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,437 @@
1
+ ![Globalize](http://globalize.github.io/globalize/images/globalize.png)
2
+
3
+ [![Build Status](https://travis-ci.org/globalize/globalize.svg?branch=master)](https://travis-ci.org/globalize/globalize) [![Code Climate](https://codeclimate.com/github/globalize/globalize.svg)](https://codeclimate.com/github/globalize/globalize)
4
+ [![Open Source Helpers](https://www.codetriage.com/globalize/globalize/badges/users.svg)](https://www.codetriage.com/globalize/globalize)
5
+
6
+ You can chat with us using Gitter:
7
+
8
+ [![Gitter chat](https://badges.gitter.im/globalize/globalize.svg)](https://gitter.im/globalize/globalize)
9
+
10
+ Globalize builds on the [I18n API in Ruby on Rails](http://guides.rubyonrails.org/i18n.html)
11
+ to add model translations to ActiveRecord models.
12
+
13
+ In other words, a way to translate actual user-generated content, for example; a single blog post with multiple translations.
14
+
15
+ ## Requirements
16
+
17
+ * ActiveRecord >= 4.2.0 (see below for installation with ActiveRecord 3.x)
18
+ * I18n
19
+
20
+ ## Installation
21
+
22
+ To install the ActiveRecord 4.2.x compatible version of Globalize with its default setup, just use:
23
+
24
+ ```ruby
25
+ gem install globalize
26
+ ```
27
+
28
+ When using bundler put this in your Gemfile:
29
+
30
+ ```ruby
31
+ gem 'globalize', '~> 5.1.0'
32
+ ```
33
+
34
+ Please help us by letting us know what works, and what doesn't, when using pre-release code.
35
+
36
+ Put in your Gemfile
37
+
38
+ ```ruby
39
+ gem 'globalize', git: 'https://github.com/globalize/globalize'
40
+ gem 'activemodel-serializers-xml'
41
+ ```
42
+
43
+ To use the version of globalize for ActiveRecord 4.0 or 4.1, specify:
44
+
45
+ ```ruby
46
+ gem 'globalize', '~> 4.0.3'
47
+ ```
48
+
49
+ To use the version of globalize for ActiveRecord 3.1 or 3.2, specify:
50
+
51
+ ````ruby
52
+ gem 'globalize', '~> 3.1.0'
53
+ ````
54
+
55
+ (If you are using ActiveRecord 3.0, use version 3.0: `gem 'globalize', '3.0.4'`.)
56
+
57
+ The [`3-1-stable` branch](https://github.com/globalize/globalize/tree/3-1-stable) of this repository corresponds to the latest ActiveRecord 3 version of globalize. Note that `globalize3` has been deprecated and you are encouraged to update your Gemfile accordingly.
58
+
59
+ ## Model translations
60
+
61
+ Model translations allow you to translate your models' attribute values. E.g.
62
+
63
+ ```ruby
64
+ class Post < ActiveRecord::Base
65
+ translates :title, :text
66
+ end
67
+ ```
68
+
69
+ Allows you to translate the attributes :title and :text per locale:
70
+
71
+ ```ruby
72
+ I18n.locale = :en
73
+ post.title # => Globalize rocks!
74
+
75
+ I18n.locale = :he
76
+ post.title # => גלובאלייז2 שולט!
77
+ ```
78
+
79
+ You can also set translations with mass-assignment by specifying the locale:
80
+
81
+ ```ruby
82
+ post.attributes = { title: 'גלובאלייז2 שולט!', locale: :he }
83
+ ```
84
+
85
+ In order to make this work, you'll need to add the appropriate translation tables.
86
+ Globalize comes with a handy helper method to help you do this.
87
+ It's called `create_translation_table!`. Here's an example:
88
+
89
+ Note that your migrations can use `create_translation_table!` and `drop_translation_table!`
90
+ only inside the `up` and `down` instance methods, respectively. You cannot use `create_translation_table!`
91
+ and `drop_translation_table!` inside the `change` instance method.
92
+
93
+ ### Creating translation tables
94
+
95
+ Also note that before you can create a translation table, you have to define the translated attributes via `translates` in your model as shown above.
96
+
97
+ ```ruby
98
+ class CreatePosts < ActiveRecord::Migration
99
+ def change
100
+ create_table :posts do |t|
101
+ t.timestamps
102
+ end
103
+
104
+ reversible do |dir|
105
+ dir.up do
106
+ Post.create_translation_table! :title => :string, :text => :text
107
+ end
108
+
109
+ dir.down do
110
+ Post.drop_translation_table!
111
+ end
112
+ end
113
+ end
114
+ end
115
+ ```
116
+
117
+ Also, you can pass options for specific columns. Here’s an example:
118
+
119
+ ```ruby
120
+ class CreatePosts < ActiveRecord::Migration
121
+ def change
122
+ create_table :posts do |t|
123
+ t.timestamps
124
+ end
125
+
126
+ reversible do |dir|
127
+ dir.up do
128
+ Post.create_translation_table! :title => :string,
129
+ :text => {:type => :text, :null => false, :default => 'abc'}
130
+ end
131
+
132
+ dir.down do
133
+ Post.drop_translation_table!
134
+ end
135
+ end
136
+ end
137
+ end
138
+ ```
139
+
140
+ Note that the ActiveRecord model `Post` must already exist and have a `translates`
141
+ directive listing the translated fields.
142
+
143
+ ## Migrating existing data to and from the translated version
144
+
145
+ As well as creating a translation table, you can also use `create_translation_table!`
146
+ to migrate across any existing data to the default locale. This can also operate
147
+ in reverse to restore any translations from the default locale back to the model
148
+ when you don't want to use a translation table anymore using `drop_translation_table!`
149
+
150
+ This feature makes use of `untranslated_attributes` which allows access to the
151
+ model's attributes as they were before the translation was applied. Here's an
152
+ example (which assumes you already have a model called `Post` and its table
153
+ exists):
154
+
155
+ ```ruby
156
+ class TranslatePosts < ActiveRecord::Migration
157
+ def change
158
+ reversible do |dir|
159
+ dir.up do
160
+ Post.create_translation_table!({
161
+ :title => :string,
162
+ :text => :text
163
+ }, {
164
+ :migrate_data => true
165
+ })
166
+ end
167
+
168
+ dir.down do
169
+ Post.drop_translation_table! :migrate_data => true
170
+ end
171
+ end
172
+ end
173
+ end
174
+ ```
175
+
176
+ NOTE: Make sure you drop the translated columns from the parent table after all your data is safely migrated.
177
+
178
+ To automatically remove the translated columns from the parent table after the data migration, please use option `remove_source_columns`.
179
+
180
+ ```ruby
181
+ class TranslatePosts < ActiveRecord::Migration
182
+ def self.up
183
+ Post.create_translation_table!({
184
+ :title => :string,
185
+ :text => :text
186
+ }, {
187
+ :migrate_data => true,
188
+ :remove_source_columns => true
189
+ })
190
+ end
191
+
192
+ def self.down
193
+ Post.drop_translation_table! :migrate_data => true
194
+ end
195
+ end
196
+ ```
197
+
198
+
199
+ In order to use a specific locale for migrated data, you can use `I18n.with_locale`:
200
+
201
+ ```ruby
202
+ I18n.with_locale(:bo) do
203
+ Post.create_translation_table!({
204
+ :title => :string,
205
+ :text => :text
206
+ }, {
207
+ :migrate_data => true
208
+ })
209
+ end
210
+ ```
211
+
212
+ ## Adding additional fields to the translation table
213
+
214
+ In order to add a new field to an existing translation table, you can use `add_translation_fields!`:
215
+
216
+ ```ruby
217
+ class AddAuthorToPost < ActiveRecord::Migration
218
+ def change
219
+ reversible do |dir|
220
+ dir.up do
221
+ Post.add_translation_fields! author: :text
222
+ end
223
+
224
+ dir.down do
225
+ remove_column :post_translations, :author
226
+ end
227
+ end
228
+ end
229
+ end
230
+ ```
231
+
232
+ NOTE: Remember to add the new field to the model:
233
+
234
+ ```ruby
235
+ translates :title, :author
236
+ ```
237
+ ## Gotchas
238
+
239
+ Because globalize uses the `:locale` key to specify the locale during
240
+ mass-assignment, you should avoid having a `locale` attribute on the parent
241
+ model.
242
+
243
+ If you like your translated model to update if a translation changes, use the `touch: true` option together with `translates`:
244
+
245
+ ```ruby
246
+ translates :name, touch: true
247
+ ```
248
+
249
+ ## Known Issues
250
+
251
+ If you're getting the `ActiveRecord::StatementInvalid: PG::NotNullViolation: ERROR: null value in column "column_name" violates not-null constraint` error, the only known way to deal with it as of now is to remove not-null constraint for the globalized columns:
252
+
253
+ ```ruby
254
+ class RemoveNullConstraintsFromResourceTranslations < ActiveRecord::Migration
255
+ def change
256
+ change_column_null :resource_translations, :column_name, true
257
+ end
258
+ end
259
+ ```
260
+
261
+ ## Versioning with Globalize
262
+
263
+ See the [globalize-versioning](https://github.com/globalize/globalize-versioning) gem.
264
+
265
+ ## I18n fallbacks for empty translations
266
+
267
+ It is possible to enable fallbacks for empty translations. It will depend on the
268
+ configuration setting you have set for I18n translations in your Rails config.
269
+
270
+ You can enable them by adding the next line to `config/application.rb` (or only
271
+ `config/environments/production.rb` if you only want them in production)
272
+
273
+ ```ruby
274
+ config.i18n.fallbacks = true
275
+ ```
276
+
277
+ By default, globalize will only use fallbacks when your translation model does
278
+ not exist or the translation value for the item you've requested is `nil`.
279
+ However it is possible to also use fallbacks for `blank` translations by adding
280
+ `:fallbacks_for_empty_translations => true` to the `translates` method.
281
+
282
+ ```ruby
283
+ class Post < ActiveRecord::Base
284
+ translates :title, :name
285
+ end
286
+
287
+ puts post.translations.inspect
288
+ # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize rocks!", name: "Globalize">,
289
+ #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
290
+
291
+ I18n.locale = :en
292
+ post.title # => 'Globalize rocks!'
293
+ post.name # => 'Globalize'
294
+
295
+ I18n.locale = :nl
296
+ post.title # => ''
297
+ post.name # => 'Globalize'
298
+ ```
299
+
300
+ ```ruby
301
+ class Post < ActiveRecord::Base
302
+ translates :title, :name, :fallbacks_for_empty_translations => true
303
+ end
304
+
305
+ puts post.translations.inspect
306
+ # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize rocks!", name: "Globalize">,
307
+ #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
308
+
309
+ I18n.locale = :en
310
+ post.title # => 'Globalize rocks!'
311
+ post.name # => 'Globalize'
312
+
313
+ I18n.locale = :nl
314
+ post.title # => 'Globalize rocks!'
315
+ post.name # => 'Globalize'
316
+ ```
317
+
318
+ ## Fallback locales to each other
319
+
320
+ It is possible to setup locales to fallback to each other.
321
+
322
+ ```ruby
323
+ class Post < ActiveRecord::Base
324
+ translates :title, :name
325
+ end
326
+
327
+ Globalize.fallbacks = {:en => [:en, :pl], :pl => [:pl, :en]}
328
+
329
+ I18n.locale = :en
330
+ en_post = Post.create(:title => 'en_title')
331
+
332
+ I18n.locale = :pl
333
+ pl_post = Post.create(:title => 'pl_title')
334
+ en_post.title # => 'en_title'
335
+
336
+ I18n.locale = :en
337
+ en_post.title # => 'en_title'
338
+ pl_post.title # => 'pl_title'
339
+ ```
340
+
341
+
342
+ ## Scoping objects by those with translations
343
+
344
+ To only return objects that have a translation for the given locale we can use
345
+ the `with_translations` scope. This will only return records that have a
346
+ translations for the passed in locale.
347
+
348
+ ```ruby
349
+ Post.with_translations('en')
350
+ # => [
351
+ #<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize rocks!", name: "Globalize">,
352
+ #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>
353
+ ]
354
+
355
+ Post.with_translations(I18n.locale)
356
+ # => [
357
+ #<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize rocks!", name: "Globalize">,
358
+ #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>
359
+ ]
360
+
361
+ Post.with_translations('de')
362
+ # => []
363
+ ```
364
+
365
+ ## Show different languages
366
+
367
+ In views, if there is content from different locales that you wish to display,
368
+ you should use the `with_locale` option with a block, as below:
369
+
370
+ ```erb
371
+ <% Globalize.with_locale(:en) do %>
372
+ <%= render "my_translated_partial" %>
373
+ <% end %>
374
+ ```
375
+
376
+ Your partial will now be rendered with the `:en` locale set as the current locale.
377
+
378
+ ## Interpolation
379
+
380
+ Globalize supports interpolation in a similar manner to I18n.
381
+
382
+ ```ruby
383
+ class Post < ActiveRecord::Base
384
+ translates :title
385
+ end
386
+
387
+ I18n.locale = :en
388
+ post.title = "Globalize %{superlative}!"
389
+
390
+ post.title
391
+ # #=> "Globalize %{superlative}!"
392
+
393
+ post.title(:foo => "bar")
394
+ # SomeError: missing interpolation argument :superlative
395
+
396
+ post.title(:superlative => "rocks")
397
+ # #=> "Globalize rocks!"
398
+ ```
399
+
400
+ ## Fragment caching
401
+
402
+ Don't forget to add globalize locale into the `cache_key` to separate different localizations of the record.
403
+ One of the possible ways to implement it:
404
+
405
+ ```ruby
406
+ # inside translated model
407
+ def cache_key
408
+ super + '-' + Globalize.locale.to_s
409
+ end
410
+ ```
411
+
412
+ ## Thread-safety
413
+
414
+ Globalize uses [request_store](https://github.com/steveklabnik/request_store) gem to clean up thread-global variable after every request.
415
+ RequestStore includes a Railtie that will configure everything properly for Rails 3+ apps.
416
+
417
+ If you're not using Rails, you may need to consult a RequestStore's [README](https://github.com/steveklabnik/request_store#no-rails-no-problem) to configure it.
418
+
419
+ ## Tutorials and articles
420
+ * [Go Global with Rails and I18n](http://www.sitepoint.com/go-global-rails-i18n/) - introductory article about i18n in Rails (Ilya Bodrov)
421
+
422
+ ## Official Globalize extensions
423
+
424
+ * [globalize-accessors](https://github.com/globalize/globalize-accessors) - generator of accessor methods for models. *(e.g. title_en, title_cz)*
425
+ * [globalize-versioning](https://github.com/globalize/globalize-versioning) - versioning support for using Globalize with [`paper_trail`](https://github.com/airblade/paper_trail). (compatible with Globalize 3.x and 4.x)
426
+
427
+ ## Alternative solutions
428
+
429
+ * [Traco](https://github.com/barsoom/traco) - use multiple columns in the same model (Barsoom)
430
+ * [Mobility](https://github.com/shioyama/mobility) - pluggable translation framework supporting many strategies, including translatable columns, translation tables and hstore/jsonb (Chris Salzberg)
431
+ * [hstore_translate](https://github.com/cfabianski/hstore_translate) - use PostgreSQL's hstore datatype to store translations, instead of separate translation tables (Cédric Fabianski)
432
+ * [json_translate](https://github.com/cfabianski/json_translate) - use PostgreSQL's json/jsonb datatype to store translations, instead of separate translation tables (Cédric Fabianski)
433
+ * [Trasto](https://github.com/yabawock/trasto) - store translations directly in the model in a Postgres Hstore column
434
+
435
+ ## Related solutions
436
+
437
+ * [friendly_id-globalize](https://github.com/norman/friendly_id-globalize) - lets you use Globalize to translate slugs (Norman Clarke)
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Run all tests.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Globalize'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ task :load_path do
25
+ %w(lib test).each do |path|
26
+ $LOAD_PATH.unshift(File.expand_path("../#{path}", __FILE__))
27
+ end
28
+ end
29
+
30
+ namespace :db do
31
+ desc 'Create the database'
32
+ task :create => :load_path do
33
+ require 'support/database'
34
+
35
+ Globalize::Test::Database.create!
36
+ end
37
+
38
+ desc "Drop the database"
39
+ task :drop => :load_path do
40
+ require 'support/database'
41
+
42
+ Globalize::Test::Database.drop!
43
+ end
44
+
45
+ desc "Set up the database schema"
46
+ task :migrate => :load_path do
47
+ require 'support/database'
48
+
49
+ Globalize::Test::Database.migrate!
50
+ # ActiveRecord::Schema.migrate :up
51
+ end
52
+
53
+ desc "Drop and recreate the database schema"
54
+ task :reset => [:drop, :create]
55
+ end
data/lib/globalize.rb ADDED
@@ -0,0 +1,98 @@
1
+ require 'request_store'
2
+ require 'active_record'
3
+ require 'patches/active_record/xml_attribute_serializer'
4
+ require 'patches/active_record/query_method'
5
+ require 'patches/active_record/relation'
6
+ require 'patches/active_record/serialization'
7
+ require 'patches/active_record/uniqueness_validator'
8
+ require 'patches/active_record/persistence'
9
+
10
+ module Globalize
11
+ autoload :ActiveRecord, 'globalize/active_record'
12
+ autoload :Interpolation, 'globalize/interpolation'
13
+
14
+ class << self
15
+ def locale
16
+ read_locale || I18n.locale
17
+ end
18
+
19
+ def locale=(locale)
20
+ set_locale(locale)
21
+ end
22
+
23
+ def with_locale(locale, &block)
24
+ previous_locale = read_locale
25
+ begin
26
+ set_locale(locale)
27
+ result = yield(locale)
28
+ ensure
29
+ set_locale(previous_locale)
30
+ end
31
+ result
32
+ end
33
+
34
+ def with_locales(*locales, &block)
35
+ locales.flatten.map do |locale|
36
+ with_locale(locale, &block)
37
+ end
38
+ end
39
+
40
+ def fallbacks=(locales)
41
+ set_fallbacks(locales)
42
+ end
43
+
44
+ def i18n_fallbacks?
45
+ I18n.respond_to?(:fallbacks)
46
+ end
47
+
48
+ def fallbacks(for_locale = self.locale)
49
+ read_fallbacks[for_locale] || default_fallbacks(for_locale)
50
+ end
51
+
52
+ def default_fallbacks(for_locale = self.locale)
53
+ i18n_fallbacks? ? I18n.fallbacks[for_locale] : [for_locale.to_sym]
54
+ end
55
+
56
+ # Thread-safe global storage
57
+ def storage
58
+ RequestStore.store
59
+ end
60
+
61
+ def rails_5?
62
+ ::ActiveRecord.version >= Gem::Version.new('5.1.0')
63
+ end
64
+
65
+ def rails_52?
66
+ ::ActiveRecord.version >= Gem::Version.new('5.2.0')
67
+ end
68
+
69
+ protected
70
+
71
+ def read_locale
72
+ storage[:globalize_locale]
73
+ end
74
+
75
+ def set_locale(locale)
76
+ storage[:globalize_locale] = locale.try(:to_sym)
77
+ end
78
+
79
+ def read_fallbacks
80
+ storage[:globalize_fallbacks] || HashWithIndifferentAccess.new
81
+ end
82
+
83
+ def set_fallbacks(locales)
84
+ fallback_hash = HashWithIndifferentAccess.new
85
+
86
+ locales.each do |key, value|
87
+ fallback_hash[key] = value.presence || [key]
88
+ end if locales.present?
89
+
90
+ storage[:globalize_fallbacks] = fallback_hash
91
+ end
92
+ end
93
+ end
94
+
95
+ ActiveRecord::Base.class_attribute :globalize_serialized_attributes, instance_writer: false
96
+ ActiveRecord::Base.globalize_serialized_attributes = {}
97
+
98
+ ActiveRecord::Base.extend(Globalize::ActiveRecord::ActMacro)
data/lib/globalize/active_record.rb ADDED
@@ -0,0 +1,14 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ autoload :ActMacro, 'globalize/active_record/act_macro'
4
+ autoload :Adapter, 'globalize/active_record/adapter'
5
+ autoload :AdapterDirty, 'globalize/active_record/adapter_dirty'
6
+ autoload :Attributes, 'globalize/active_record/attributes'
7
+ autoload :ClassMethods, 'globalize/active_record/class_methods'
8
+ autoload :Exceptions, 'globalize/active_record/exceptions'
9
+ autoload :InstanceMethods, 'globalize/active_record/instance_methods'
10
+ autoload :Migration, 'globalize/active_record/migration'
11
+ autoload :Translation, 'globalize/active_record/translation'
12
+ autoload :TranslatedAttributesQuery, 'globalize/active_record/translated_attributes_query'
13
+ end
14
+ end
data/lib/globalize/active_record/act_macro.rb ADDED
@@ -0,0 +1,96 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module ActMacro
4
+ def translates(*attr_names)
5
+ options = attr_names.extract_options!
6
+ # Bypass setup_translates! if the initial bootstrapping is done already.
7
+ setup_translates!(options) unless translates?
8
+
9
+
10
+ # Add any extra translatable attributes.
11
+ attr_names = attr_names.map(&:to_sym)
12
+ attr_names -= translated_attribute_names if defined?(translated_attribute_names)
13
+
14
+ allow_translation_of_attributes(attr_names) if attr_names.present?
15
+ end
16
+
17
+ def class_name
18
+ @class_name ||= begin
19
+ class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].downcase.camelize
20
+ pluralize_table_names ? class_name.singularize : class_name
21
+ end
22
+ end
23
+
24
+ def translates?
25
+ included_modules.include?(InstanceMethods)
26
+ end
27
+
28
+ protected
29
+
30
+ def allow_translation_of_attributes(attr_names)
31
+ attr_names.each do |attr_name|
32
+ # Detect and apply serialization.
33
+ enable_serializable_attribute(attr_name)
34
+
35
+ # Create accessors for the attribute.
36
+ define_translated_attr_accessor(attr_name)
37
+ define_translations_accessor(attr_name)
38
+
39
+ # Add attribute to the list.
40
+ self.translated_attribute_names << attr_name
41
+ end
42
+ if ::ActiveRecord::VERSION::STRING > "5.0" && connected? && table_exists? && translation_class.table_exists?
43
+ self.ignored_columns += translated_attribute_names.map(&:to_s)
44
+ reset_column_information
45
+ end
46
+ end
47
+
48
+ def apply_globalize_options(options)
49
+ options[:table_name] ||= "#{table_name.singularize}_translations"
50
+ options[:foreign_key] ||= class_name.foreign_key
51
+
52
+ class_attribute :translated_attribute_names, :translation_options, :fallbacks_for_empty_translations
53
+ self.translated_attribute_names = []
54
+ self.translation_options = options
55
+ self.fallbacks_for_empty_translations = options[:fallbacks_for_empty_translations]
56
+ end
57
+
58
+ def enable_serializable_attribute(attr_name)
59
+ serializer = self.globalize_serialized_attributes[attr_name]
60
+ if serializer.present?
61
+ if defined?(::ActiveRecord::Coders::YAMLColumn) &&
62
+ serializer.is_a?(::ActiveRecord::Coders::YAMLColumn)
63
+ serializer = serializer.object_class
64
+ end
65
+
66
+ translation_class.send :serialize, attr_name, serializer
67
+ end
68
+ end
69
+
70
+ def setup_translates!(options)
71
+ apply_globalize_options(options)
72
+
73
+ include InstanceMethods
74
+ extend ClassMethods, Migration
75
+
76
+ translation_class.table_name = options[:table_name]
77
+
78
+ has_many :translations, :class_name => translation_class.name,
79
+ :foreign_key => options[:foreign_key],
80
+ :dependent => :destroy,
81
+ :extend => HasManyExtensions,
82
+ :autosave => false,
83
+ :inverse_of => :globalized_model
84
+
85
+ after_create :save_translations!
86
+ after_update :save_translations!
87
+ end
88
+ end
89
+
90
+ module HasManyExtensions
91
+ def find_or_initialize_by_locale(locale)
92
+ with_locale(locale.to_s).first || build(:locale => locale.to_s)
93
+ end
94
+ end
95
+ end
96
+ end
data/lib/globalize/active_record/adapter.rb ADDED
@@ -0,0 +1,108 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ class Adapter
4
+ # The cache caches attributes that already were looked up for read access.
5
+ # The stash keeps track of new or changed values that need to be saved.
6
+ attr_accessor :record, :stash
7
+ private :record=, :stash=
8
+
9
+ delegate :translation_class, :to => :'record.class'
10
+
11
+ def initialize(record)
12
+ @record = record
13
+ @stash = Attributes.new
14
+ end
15
+
16
+ def fetch_stash(locale, name)
17
+ stash.read(locale, name)
18
+ end
19
+
20
+ delegate :contains?, :to => :stash, :prefix => :stash
21
+ delegate :write, :to => :stash
22
+
23
+ def fetch(locale, name)
24
+ record.globalize_fallbacks(locale).each do |fallback|
25
+ value = stash.contains?(fallback, name) ? fetch_stash(fallback, name) : fetch_attribute(fallback, name)
26
+
27
+ unless fallbacks_for?(value)
28
+ set_metadata(value, :locale => fallback, :requested_locale => locale)
29
+ return value
30
+ end
31
+ end
32
+
33
+ return nil
34
+ end
35
+
36
+ def save_translations!
37
+ stash.each do |locale, attrs|
38
+ next if attrs.empty?
39
+
40
+ translation = record.translations_by_locale[locale] ||
41
+ record.translations.build(locale: locale.to_s)
42
+ attrs.each do |name, value|
43
+ value = value.val if value.is_a?(Arel::Nodes::Casted)
44
+ translation[name] = value
45
+ end
46
+
47
+ ensure_foreign_key_for(translation)
48
+ translation.save!
49
+ end
50
+
51
+ reset
52
+ end
53
+
54
+ def reset
55
+ stash.clear
56
+ end
57
+
58
+ protected
59
+
60
+ # Sometimes the translation is initialised before a foreign key can be set.
61
+ def ensure_foreign_key_for(translation)
62
+ # AR >= 4.1 reflections renamed to _reflections
63
+ translation[translation.class.reflections.stringify_keys["globalized_model"].foreign_key] = record.id
64
+ end
65
+
66
+ def type_cast(name, value)
67
+ return value.presence unless column = column_for_attribute(name)
68
+
69
+ column.type_cast value
70
+ end
71
+
72
+ def column_for_attribute(name)
73
+ translation_class.columns_hash[name.to_s]
74
+ end
75
+
76
+ def unserializable_attribute?(name, column)
77
+ column.text? && translation_class.serialized_attributes[name.to_s]
78
+ end
79
+
80
+ def fetch_attribute(locale, name)
81
+ translation = record.translation_for(locale, false)
82
+ if translation
83
+ translation.send(name)
84
+ else
85
+ record.class.translation_class.new.send(name)
86
+ end
87
+ end
88
+
89
+ def set_metadata(object, metadata)
90
+ object.translation_metadata.merge!(metadata) if object.respond_to?(:translation_metadata)
91
+ object
92
+ end
93
+
94
+ def translation_metadata_accessor(object)
95
+ return if obj.respond_to?(:translation_metadata)
96
+ class << object; attr_accessor :translation_metadata end
97
+ object.translation_metadata ||= {}
98
+ end
99
+
100
+ def fallbacks_for?(object)
101
+ object.nil? || (fallbacks_for_empty_translations? && object.blank?)
102
+ end
103
+
104
+ delegate :fallbacks_for_empty_translations?, :to => :record, :prefix => false
105
+ prepend AdapterDirty
106
+ end
107
+ end
108
+ end
data/lib/globalize/active_record/adapter_dirty.rb ADDED
@@ -0,0 +1,56 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module AdapterDirty
4
+ def write locale, name, value
5
+ # Dirty tracking, paraphrased from
6
+ # ActiveRecord::AttributeMethods::Dirty#write_attribute.
7
+ name = name.to_s
8
+ store_old_value name, locale
9
+ old_values = dirty[name]
10
+ old_value = old_values[locale]
11
+ is_changed = record.send :attribute_changed?, name
12
+ if is_changed && value == old_value
13
+ # If there's already a change, delete it if this undoes the change.
14
+ old_values.delete locale
15
+ if old_values.empty?
16
+ _reset_attribute name
17
+ end
18
+ elsif !is_changed
19
+ # If there's not a change yet, record it.
20
+ record.send(:attribute_will_change!, name) if old_value != value
21
+ end
22
+
23
+ super locale, name, value
24
+ end
25
+
26
+ attr_writer :dirty
27
+ def dirty
28
+ @dirty ||= {}
29
+ end
30
+
31
+ def store_old_value name, locale
32
+ dirty[name] ||= {}
33
+ unless dirty[name].key? locale
34
+ old = fetch(locale, name)
35
+ old = old.dup if old.duplicable?
36
+ dirty[name][locale] = old
37
+ end
38
+ end
39
+
40
+ def clear_dirty
41
+ self.dirty = {}
42
+ end
43
+
44
+ def _reset_attribute name
45
+ record.send("#{name}=", record.changed_attributes[name])
46
+ record.send(:clear_attribute_changes, [name])
47
+ end
48
+
49
+ def reset
50
+ clear_dirty
51
+ super
52
+ end
53
+
54
+ end
55
+ end
56
+ end
data/lib/globalize/active_record/attributes.rb ADDED
@@ -0,0 +1,26 @@
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
+ self[locale][name.to_s] = value
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/globalize/active_record/class_methods.rb ADDED
@@ -0,0 +1,129 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module ClassMethods
4
+ delegate :translated_locales, :set_translations_table_name, :to => :translation_class
5
+
6
+ if ::ActiveRecord::VERSION::STRING < "5.0.0"
7
+ def columns_hash
8
+ super.except(*translated_attribute_names.map(&:to_s))
9
+ end
10
+ end
11
+
12
+ def with_locales(*locales)
13
+ all.merge translation_class.with_locales(*locales)
14
+ end
15
+
16
+ def with_translations(*locales)
17
+ locales = translated_locales if locales.empty?
18
+ preload(:translations).joins(:translations).readonly(false).with_locales(locales).tap do |query|
19
+ query.distinct! unless locales.flatten.one?
20
+ end
21
+ end
22
+
23
+ def with_required_attributes
24
+ warn 'with_required_attributes is deprecated and will be removed in the next release of Globalize.'
25
+ required_translated_attributes.inject(all) do |scope, name|
26
+ scope.where("#{translated_column_name(name)} IS NOT NULL")
27
+ end
28
+ end
29
+
30
+ def with_translated_attribute(name, value, locales = Globalize.fallbacks)
31
+ with_translations.where(
32
+ translated_column_name(name) => value,
33
+ translated_column_name(:locale) => Array(locales).map(&:to_s)
34
+ )
35
+ end
36
+
37
+ def translated?(name)
38
+ translated_attribute_names.include?(name.to_sym)
39
+ end
40
+
41
+ def required_attributes
42
+ warn 'required_attributes is deprecated and will be removed in the next release of Globalize.'
43
+ validators.map { |v| v.attributes if v.is_a?(ActiveModel::Validations::PresenceValidator) }.flatten
44
+ end
45
+
46
+ def required_translated_attributes
47
+ warn 'required_translated_attributes is deprecated and will be removed in the next release of Globalize.'
48
+ translated_attribute_names & required_attributes
49
+ end
50
+
51
+ def translation_class
52
+ @translation_class ||= begin
53
+ if self.const_defined?(:Translation, false)
54
+ klass = self.const_get(:Translation, false)
55
+ else
56
+ klass = self.const_set(:Translation, Class.new(Globalize::ActiveRecord::Translation))
57
+ end
58
+
59
+ klass.belongs_to :globalized_model,
60
+ class_name: self.name,
61
+ foreign_key: translation_options[:foreign_key],
62
+ inverse_of: :translations,
63
+ touch: translation_options.fetch(:touch, false)
64
+ klass
65
+ end
66
+ end
67
+
68
+ def translations_table_name
69
+ translation_class.table_name
70
+ end
71
+
72
+ def translated_column_name(name)
73
+ "#{translation_class.table_name}.#{name}"
74
+ end
75
+
76
+ private
77
+
78
+ # Override the default relation method in order to return a subclass
79
+ # of ActiveRecord::Relation with custom finder and calculation methods
80
+ # for translated attributes.
81
+ def relation
82
+ super.extending!(TranslatedAttributesQuery)
83
+ end
84
+
85
+ protected
86
+
87
+ def define_translated_attr_reader(name)
88
+ define_method(name) do |*args|
89
+ Globalize::Interpolation.interpolate(name, self, args)
90
+ end
91
+ alias_method :"#{name}_before_type_cast", name
92
+ end
93
+
94
+ def define_translated_attr_writer(name)
95
+ define_method(:"#{name}=") do |value|
96
+ write_attribute(name, value)
97
+ end
98
+ end
99
+
100
+ def define_translated_attr_accessor(name)
101
+ attribute(name, ::ActiveRecord::Type::Value.new)
102
+ define_translated_attr_reader(name)
103
+ define_translated_attr_writer(name)
104
+ end
105
+
106
+ def define_translations_reader(name)
107
+ define_method(:"#{name}_translations") do
108
+ hash = translated_attribute_by_locale(name)
109
+ globalize.stash.keys.each_with_object(hash) do |locale, result|
110
+ result[locale] = globalize.fetch_stash(locale, name) if globalize.stash_contains?(locale, name)
111
+ end
112
+ end
113
+ end
114
+
115
+ def define_translations_writer(name)
116
+ define_method(:"#{name}_translations=") do |value|
117
+ value.each do |(locale, _value)|
118
+ write_attribute name, _value, :locale => locale
119
+ end
120
+ end
121
+ end
122
+
123
+ def define_translations_accessor(name)
124
+ define_translations_reader(name)
125
+ define_translations_writer(name)
126
+ end
127
+ end
128
+ end
129
+ end
data/lib/globalize/active_record/exceptions.rb ADDED
@@ -0,0 +1,13 @@
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
+ end
12
+ end
13
+ end
data/lib/globalize/active_record/instance_methods.rb ADDED
@@ -0,0 +1,246 @@
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 attributes=(new_attributes, *options)
15
+ super unless new_attributes.respond_to?(:stringify_keys) && new_attributes.present?
16
+ attributes = new_attributes.stringify_keys
17
+ with_given_locale(attributes) { super(attributes.except("locale"), *options) }
18
+ end
19
+
20
+ if Globalize.rails_52?
21
+
22
+ # In Rails 5.2 we need to override *_assign_attributes* as it's called earlier
23
+ # in the stack (before *assign_attributes*)
24
+ # See https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_assignment.rb#L11
25
+ def _assign_attributes(new_attributes)
26
+ attributes = new_attributes.stringify_keys
27
+ with_given_locale(attributes) { super(attributes.except("locale")) }
28
+ end
29
+
30
+ else
31
+
32
+ def assign_attributes(new_attributes, *options)
33
+ super unless new_attributes.respond_to?(:stringify_keys) && new_attributes.present?
34
+ attributes = new_attributes.stringify_keys
35
+ with_given_locale(attributes) { super(attributes.except("locale"), *options) }
36
+ end
37
+
38
+ end
39
+
40
+ def write_attribute(name, value, *args, &block)
41
+ return super(name, value, *args, &block) unless translated?(name)
42
+
43
+ options = {:locale => Globalize.locale}.merge(args.first || {})
44
+
45
+ globalize.write(options[:locale], name, value)
46
+ end
47
+
48
+ def [](attr_name)
49
+ if translated?(attr_name)
50
+ read_attribute(attr_name)
51
+ else
52
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
53
+ end
54
+ end
55
+
56
+ def read_attribute(attr_name, options = {}, &block)
57
+ name = if self.class.attribute_alias?(attr_name)
58
+ self.class.attribute_alias(attr_name).to_s
59
+ else
60
+ attr_name.to_s
61
+ end
62
+
63
+ name = self.class.primary_key if name == "id".freeze && self.class.primary_key
64
+
65
+ _read_attribute(name, options, &block)
66
+ end
67
+
68
+ def _read_attribute(attr_name, options = {}, &block)
69
+ translated_value = read_translated_attribute(attr_name, options, &block)
70
+ translated_value.nil? ? super(attr_name, &block) : translated_value
71
+ end
72
+
73
+ def attribute_names
74
+ translated_attribute_names.map(&:to_s) + super
75
+ end
76
+
77
+ delegate :translated?, :to => :class
78
+
79
+ def translated_attributes
80
+ translated_attribute_names.inject({}) do |attributes, name|
81
+ attributes.merge(name.to_s => send(name))
82
+ end
83
+ end
84
+
85
+ # This method is basically the method built into Rails
86
+ # but we have to pass {:translated => false}
87
+ def untranslated_attributes
88
+ attribute_names.inject({}) do |attrs, name|
89
+ attrs[name] = read_attribute(name, {:translated => false}); attrs
90
+ end
91
+ end
92
+
93
+ def set_translations(options)
94
+ options.keys.each do |locale|
95
+ translation = translation_for(locale) ||
96
+ translations.build(:locale => locale.to_s)
97
+
98
+ options[locale].each do |key, value|
99
+ translation.send :"#{key}=", value
100
+ translation.globalized_model.send :"#{key}=", value
101
+ end
102
+ translation.save if persisted?
103
+ end
104
+ globalize.reset
105
+ end
106
+
107
+ def reload(options = nil)
108
+ translation_caches.clear
109
+ translated_attribute_names.each { |name| @attributes.reset(name.to_s) }
110
+ globalize.reset
111
+ super(options)
112
+ end
113
+
114
+ def initialize_dup(other)
115
+ @globalize = nil
116
+ @translation_caches = nil
117
+ super
118
+ other.each_locale_and_translated_attribute do |locale, name|
119
+ globalize.write(locale, name, other.globalize.fetch(locale, name) )
120
+ end
121
+ end
122
+
123
+ def translation
124
+ translation_for(::Globalize.locale)
125
+ end
126
+
127
+ def translation_for(locale, build_if_missing = true)
128
+ unless translation_caches[locale]
129
+ # Fetch translations from database as those in the translation collection may be incomplete
130
+ _translation = translations.detect{|t| t.locale.to_s == locale.to_s}
131
+ _translation ||= translations.with_locale(locale).first unless translations.loaded?
132
+ _translation ||= translations.build(:locale => locale) if build_if_missing
133
+ translation_caches[locale] = _translation if _translation
134
+ end
135
+ translation_caches[locale]
136
+ end
137
+
138
+ def translation_caches
139
+ @translation_caches ||= {}
140
+ end
141
+
142
+ def translations_by_locale
143
+ translations.each_with_object(HashWithIndifferentAccess.new) do |t, hash|
144
+ hash[t.locale] = block_given? ? yield(t) : t
145
+ end
146
+ end
147
+
148
+ def translated_attribute_by_locale(name)
149
+ translations_by_locale(&:"#{name}")
150
+ end
151
+
152
+ # Get available locales from translations association, without a separate distinct query
153
+ def available_locales
154
+ translations.map(&:locale).uniq
155
+ end
156
+
157
+ def globalize_fallbacks(locale)
158
+ Globalize.fallbacks(locale)
159
+ end
160
+
161
+ def save(*)
162
+ result = Globalize.with_locale(translation.locale || I18n.default_locale) do
163
+ without_fallbacks do
164
+ super
165
+ end
166
+ end
167
+ if result
168
+ globalize.clear_dirty
169
+ end
170
+
171
+ result
172
+ end
173
+
174
+ def column_for_attribute name
175
+ return super if translated_attribute_names.exclude?(name)
176
+
177
+ globalize.send(:column_for_attribute, name)
178
+ end
179
+
180
+ def cache_key
181
+ [super, translation.cache_key].join("/")
182
+ end
183
+
184
+ def changed?
185
+ changed_attributes.present? || translations.any?(&:changed?)
186
+ end
187
+
188
+ # need to access instance variable directly since changed_attributes
189
+ # is frozen as of Rails 4.2
190
+ def original_changed_attributes
191
+ @changed_attributes
192
+ end
193
+
194
+ protected
195
+
196
+ def each_locale_and_translated_attribute
197
+ used_locales.each do |locale|
198
+ translated_attribute_names.each do |name|
199
+ yield locale, name
200
+ end
201
+ end
202
+ end
203
+
204
+ def used_locales
205
+ locales = globalize.stash.keys.concat(globalize.stash.keys).concat(translations.translated_locales)
206
+ locales.uniq!
207
+ locales
208
+ end
209
+
210
+ def save_translations!
211
+ globalize.save_translations!
212
+ translation_caches.clear
213
+ end
214
+
215
+ def with_given_locale(_attributes, &block)
216
+ attributes = _attributes.stringify_keys
217
+
218
+ if locale = attributes.try(:delete, "locale")
219
+ Globalize.with_locale(locale, &block)
220
+ else
221
+ yield
222
+ end
223
+ end
224
+
225
+ def without_fallbacks
226
+ before = self.fallbacks_for_empty_translations
227
+ self.fallbacks_for_empty_translations = false
228
+ yield
229
+ ensure
230
+ self.fallbacks_for_empty_translations = before
231
+ end
232
+
233
+ # nil or value
234
+ def read_translated_attribute(name, options)
235
+ options = {:translated => true, :locale => nil}.merge(options)
236
+ return nil unless options[:translated]
237
+ return nil unless translated?(name)
238
+
239
+ value = globalize.fetch(options[:locale] || Globalize.locale, name)
240
+ return nil if value.nil?
241
+
242
+ block_given? ? yield(value) : value
243
+ end
244
+ end
245
+ end
246
+ end
data/lib/globalize/active_record/migration.rb ADDED
@@ -0,0 +1,215 @@
1
+ require 'digest/sha1'
2
+
3
+ module Globalize
4
+ module ActiveRecord
5
+ module Migration
6
+ def globalize_migrator
7
+ @globalize_migrator ||= Migrator.new(self)
8
+ end
9
+
10
+ delegate :create_translation_table!, :add_translation_fields!,
11
+ :drop_translation_table!, :translation_index_name,
12
+ :translation_locale_index_name, :to => :globalize_migrator
13
+
14
+ class Migrator
15
+ include Globalize::ActiveRecord::Exceptions
16
+
17
+ attr_reader :model
18
+ delegate :translated_attribute_names, :connection, :table_name,
19
+ :table_name_prefix, :translations_table_name, :columns, :to => :model
20
+
21
+ def initialize(model)
22
+ @model = model
23
+ end
24
+
25
+ def fields
26
+ @fields ||= complete_translated_fields
27
+ end
28
+
29
+ def create_translation_table!(fields = {}, options = {})
30
+ extra = options.keys - [:migrate_data, :remove_source_columns, :unique_index]
31
+ if extra.any?
32
+ raise ArgumentError, "Unknown migration #{'option'.pluralize(extra.size)}: #{extra}"
33
+ end
34
+ @fields = fields
35
+ # If we have fields we only want to create the translation table with those fields
36
+ complete_translated_fields if fields.blank?
37
+ validate_translated_fields
38
+
39
+ create_translation_table
40
+ add_translation_fields!(fields, options)
41
+ create_translations_index(options)
42
+ clear_schema_cache!
43
+ end
44
+
45
+ def add_translation_fields!(fields, options = {})
46
+ @fields = fields
47
+ validate_translated_fields
48
+ add_translation_fields
49
+ clear_schema_cache!
50
+ move_data_to_translation_table if options[:migrate_data]
51
+ remove_source_columns if options[:remove_source_columns]
52
+ clear_schema_cache!
53
+ end
54
+
55
+ def remove_source_columns
56
+ column_names = *fields.keys
57
+ column_names.each do |column|
58
+ if connection.column_exists?(table_name, column)
59
+ connection.remove_column(table_name, column)
60
+ end
61
+ end
62
+ end
63
+
64
+ def drop_translation_table!(options = {})
65
+ move_data_to_model_table if options[:migrate_data]
66
+ drop_translations_index
67
+ drop_translation_table
68
+ clear_schema_cache!
69
+ end
70
+
71
+ # This adds all the current translated attributes of the model
72
+ # It's a problem because in early migrations would add all the translated attributes
73
+ def complete_translated_fields
74
+ translated_attribute_names.each do |name|
75
+ @fields[name] ||= column_type(name)
76
+ end
77
+ end
78
+
79
+ def create_translation_table
80
+ connection.create_table(translations_table_name) do |t|
81
+ t.references table_name.sub(/^#{table_name_prefix}/, '').singularize, :null => false, :index => false, :type => column_type(model.primary_key).to_sym
82
+ t.string :locale, :null => false
83
+ t.timestamps :null => false
84
+ end
85
+ end
86
+
87
+ def add_translation_fields
88
+ connection.change_table(translations_table_name) do |t|
89
+ fields.each do |name, options|
90
+ if options.is_a? Hash
91
+ t.column name, options.delete(:type), options
92
+ else
93
+ t.column name, options
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def create_translations_index(options)
100
+ foreign_key = "#{table_name.sub(/^#{table_name_prefix}/, "").singularize}_id".to_sym
101
+ connection.add_index(
102
+ translations_table_name,
103
+ foreign_key,
104
+ :name => translation_index_name
105
+ )
106
+ # index for select('DISTINCT locale') call in translation.rb
107
+ connection.add_index(
108
+ translations_table_name,
109
+ :locale,
110
+ :name => translation_locale_index_name
111
+ )
112
+
113
+ if options[:unique_index]
114
+ connection.add_index(
115
+ translations_table_name,
116
+ [foreign_key, :locale],
117
+ :name => translation_unique_index_name,
118
+ unique: true
119
+ )
120
+ end
121
+ end
122
+
123
+ def drop_translation_table
124
+ connection.drop_table(translations_table_name)
125
+ end
126
+
127
+ def drop_translations_index
128
+ if connection.indexes(translations_table_name).map(&:name).include?(translation_index_name)
129
+ connection.remove_index(translations_table_name, :name => translation_index_name)
130
+ end
131
+ if connection.indexes(translations_table_name).map(&:name).include?(translation_locale_index_name)
132
+ connection.remove_index(translations_table_name, :name => translation_locale_index_name)
133
+ end
134
+ end
135
+
136
+ def move_data_to_translation_table
137
+ model.find_each do |record|
138
+ translation = record.translation_for(I18n.locale) || record.translations.build(:locale => I18n.locale)
139
+ fields.each do |attribute_name, attribute_type|
140
+ translation[attribute_name] = record.read_attribute(attribute_name, {:translated => false})
141
+ end
142
+ translation.save!
143
+ end
144
+ end
145
+
146
+ def move_data_to_model_table
147
+ add_missing_columns
148
+
149
+ # Find all of the translated attributes for all records in the model.
150
+ all_translated_attributes = model.all.collect{|m| m.attributes}
151
+ all_translated_attributes.each do |translated_record|
152
+ # Create a hash containing the translated column names and their values.
153
+ translated_attribute_names.inject(fields_to_update={}) do |f, name|
154
+ f.update({name.to_sym => translated_record[name.to_s]})
155
+ end
156
+
157
+ # Now, update the actual model's record with the hash.
158
+ model.where(model.primary_key.to_sym => translated_record[model.primary_key]).update_all(fields_to_update)
159
+ end
160
+ end
161
+
162
+ def validate_translated_fields
163
+ fields.each do |name, options|
164
+ raise BadFieldName.new(name) unless valid_field_name?(name)
165
+ end
166
+ end
167
+
168
+ def column_type(name)
169
+ columns.detect { |c| c.name == name.to_s }.try(:type) || :string
170
+ end
171
+
172
+ def valid_field_name?(name)
173
+ translated_attribute_names.include?(name)
174
+ end
175
+
176
+ def translation_index_name
177
+ truncate_index_name "index_#{translations_table_name}_on_#{table_name.singularize}_id"
178
+ end
179
+
180
+ def translation_locale_index_name
181
+ truncate_index_name "index_#{translations_table_name}_on_locale"
182
+ end
183
+
184
+ def translation_unique_index_name
185
+ truncate_index_name "index_#{translations_table_name}_on_#{table_name.singularize}_id_and_locale"
186
+ end
187
+
188
+ def clear_schema_cache!
189
+ connection.schema_cache.clear! if connection.respond_to? :schema_cache
190
+ model::Translation.reset_column_information
191
+ model.reset_column_information
192
+ end
193
+
194
+ private
195
+
196
+ def truncate_index_name(index_name)
197
+ if index_name.size < connection.index_name_length
198
+ index_name
199
+ else
200
+ "index_#{Digest::SHA1.hexdigest(index_name)}"[0, connection.index_name_length]
201
+ end
202
+ end
203
+
204
+ def add_missing_columns
205
+ clear_schema_cache!
206
+ translated_attribute_names.map(&:to_s).each do |attribute|
207
+ unless model.column_names.include?(attribute)
208
+ connection.add_column(table_name, attribute, model::Translation.columns_hash[attribute].type)
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
data/lib/globalize/active_record/translated_attributes_query.rb ADDED
@@ -0,0 +1,181 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module TranslatedAttributesQuery
4
+ class WhereChain < ::ActiveRecord::QueryMethods::WhereChain
5
+ def not(opts, *rest)
6
+ if parsed = @scope.clone.parse_translated_conditions(opts)
7
+ @scope.join_translations.where.not(parsed, *rest)
8
+ else
9
+ super
10
+ end
11
+ end
12
+ end
13
+
14
+ def where(opts = :chain, *rest)
15
+ if opts == :chain
16
+ WhereChain.new(spawn)
17
+ elsif parsed = parse_translated_conditions(opts)
18
+ join_translations(super(parsed, *rest))
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def having(opts, *rest)
25
+ if parsed = parse_translated_conditions(opts)
26
+ join_translations(super(parsed, *rest))
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def order(opts, *rest)
33
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_order(opts)
34
+ join_translations super(parsed)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def reorder(opts, *rest)
41
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_order(opts)
42
+ join_translations super(parsed)
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ def group(*columns)
49
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(columns)
50
+ join_translations super(parsed)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def select(*columns)
57
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(columns)
58
+ join_translations super(parsed)
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ def exists?(conditions = :none)
65
+ if parsed = parse_translated_conditions(conditions)
66
+ with_translations_in_fallbacks.exists?(parsed)
67
+ else
68
+ super
69
+ end
70
+ end
71
+
72
+ def calculate(*args)
73
+ column_name = args[1]
74
+ if respond_to?(:translated_attribute_names) && translated_column?(column_name)
75
+ args[1] = translated_column_name(column_name)
76
+ join_translations.calculate(*args)
77
+ else
78
+ super
79
+ end
80
+ end
81
+
82
+ def pluck(*column_names)
83
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(column_names)
84
+ join_translations.pluck(*parsed)
85
+ else
86
+ super
87
+ end
88
+ end
89
+
90
+ def with_translations_in_fallbacks
91
+ with_translations(Globalize.fallbacks)
92
+ end
93
+
94
+ def parse_translated_conditions(opts)
95
+ if opts.is_a?(Hash) && respond_to?(:translated_attribute_names) && (keys = opts.symbolize_keys.keys & translated_attribute_names).present?
96
+ opts = opts.dup
97
+ keys.each { |key| opts[translated_column_name(key)] = opts.delete(key) || opts.delete(key.to_s) }
98
+ opts
99
+ end
100
+ end
101
+
102
+ if ::ActiveRecord::VERSION::STRING < "5.0.0"
103
+ def where_values_hash(*args)
104
+ return super unless respond_to?(:translations_table_name)
105
+ equalities = respond_to?(:with_default_scope) ? with_default_scope.where_values : where_values
106
+ equalities = equalities.grep(Arel::Nodes::Equality).find_all { |node|
107
+ node.left.relation.name == translations_table_name
108
+ }
109
+
110
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
111
+
112
+ super.merge(Hash[equalities.map { |where|
113
+ name = where.left.name
114
+ [name, binds.fetch(name.to_s) { right = where.right; right.is_a?(Arel::Nodes::Casted) ? right.val : right }]
115
+ }])
116
+ end
117
+ end
118
+
119
+ def join_translations(relation = self)
120
+ if relation.joins_values.include?(:translations)
121
+ relation
122
+ else
123
+ relation.with_translations_in_fallbacks
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def arel_translated_order_node(column, direction)
130
+ unless translated_column?(column)
131
+ return self.arel_table[column].send(direction)
132
+ end
133
+
134
+ full_column = translated_column_name(column)
135
+
136
+ # Inject `full_column` to the select values to avoid
137
+ # PG::InvalidColumnReference errors with distinct queries on Postgres
138
+ if select_values.empty?
139
+ self.select_values = [self.arel_table[Arel.star], full_column]
140
+ else
141
+ self.select_values << full_column
142
+ end
143
+
144
+ translation_class.arel_table[column].send(direction)
145
+ end
146
+
147
+ def parse_translated_order(opts)
148
+ case opts
149
+ when Hash
150
+ # Do not process nothing unless there is at least a translated column
151
+ # so that the `order` statement will be processed by the original
152
+ # ActiveRecord method
153
+ return nil unless opts.find { |col, dir| translated_column?(col) }
154
+
155
+ # Build order arel nodes for translateds and untranslateds statements
156
+ ordering = opts.map do |column, direction|
157
+ arel_translated_order_node(column, direction)
158
+ end
159
+
160
+ order(ordering).order_values
161
+ when Symbol
162
+ parse_translated_order({ opts => :asc })
163
+ when Array
164
+ parse_translated_order(Hash[opts.collect { |opt| [opt, :asc] } ])
165
+ else # failsafe returns nothing
166
+ nil
167
+ end
168
+ end
169
+
170
+ def parse_translated_columns(columns)
171
+ if columns.is_a?(Array) && (columns.flatten & translated_attribute_names).present?
172
+ columns.flatten.map { |column| translated_column?(column) ? translated_column_name(column) : column }
173
+ end
174
+ end
175
+
176
+ def translated_column?(column)
177
+ translated_attribute_names.include?(column)
178
+ end
179
+ end
180
+ end
181
+ end
data/lib/globalize/active_record/translation.rb ADDED
@@ -0,0 +1,45 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ class Translation < ::ActiveRecord::Base
4
+
5
+ validates :locale, :presence => true
6
+
7
+ class << self
8
+ # Sometimes ActiveRecord queries .table_exists? before the table name
9
+ # has even been set which results in catastrophic failure.
10
+ def table_exists?
11
+ table_name.present? && super
12
+ end
13
+
14
+ def with_locales(*locales)
15
+ # Avoid using "IN" with SQL queries when only using one locale.
16
+ locales = locales.flatten.map(&:to_s)
17
+ locales = locales.first if locales.one?
18
+ where :locale => locales
19
+ end
20
+ alias with_locale with_locales
21
+
22
+ def translated_locales
23
+ select('DISTINCT locale').order(:locale).map(&:locale)
24
+ end
25
+ end
26
+
27
+ def locale
28
+ _locale = read_attribute :locale
29
+ _locale.present? ? _locale.to_sym : _locale
30
+ end
31
+
32
+ def locale=(locale)
33
+ write_attribute :locale, locale.to_s
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ # Setting this will force polymorphic associations to subclassed objects
40
+ # to use their table_name rather than the parent object's table name,
41
+ # which will allow you to get their models back in a more appropriate
42
+ # format.
43
+ #
44
+ # See http://www.ruby-forum.com/topic/159894 for details.
45
+ Globalize::ActiveRecord::Translation.abstract_class = true
data/lib/globalize/interpolation.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Globalize
2
+ module Interpolation
3
+ def interpolate(name, model, args)
4
+ translation = model.read_attribute(name, {:locale => locale_from(args)})
5
+ try_interpolation translation, interpolation_args_from(args)
6
+ end
7
+
8
+ private
9
+
10
+ def interpolation_args_from(args)
11
+ args.detect {|a| a.is_a? Hash }
12
+ end
13
+
14
+ def locale_from(args)
15
+ args.detect {|a| !a.is_a? Hash }
16
+ end
17
+
18
+ def try_interpolation(translation,args)
19
+ if args
20
+ I18n.interpolate(translation,args)
21
+ else
22
+ translation
23
+ end
24
+ end
25
+
26
+ extend self
27
+ end
28
+ end
data/lib/globalize/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Globalize
2
+ Version = '5.2.0'
3
+ end
data/lib/i18n/missing_translations_log_handler.rb ADDED
@@ -0,0 +1,41 @@
1
+ # A simple exception handler that behaves like the default exception handler
2
+ # but additionally logs missing translations to a given log.
3
+ #
4
+ # Useful for identifying missing translations during testing.
5
+ #
6
+ # E.g.
7
+ #
8
+ # require 'globalize/i18n/missing_translations_log_handler'
9
+ # I18n.missing_translations_logger = RAILS_DEFAULT_LOGGER
10
+ # I18n.exception_handler = :missing_translations_log_handler
11
+ #
12
+ # To set up a different log file:
13
+ #
14
+ # logger = Logger.new("#{RAILS_ROOT}/log/missing_translations.log")
15
+ # I18n.missing_translations_logger = logger
16
+
17
+ module I18n
18
+ @@missing_translations_logger = nil
19
+
20
+ class << self
21
+ def missing_translations_logger
22
+ @@missing_translations_logger ||= begin
23
+ require 'logger' unless defined?(Logger)
24
+ Logger.new(STDOUT)
25
+ end
26
+ end
27
+
28
+ def missing_translations_logger=(logger)
29
+ @@missing_translations_logger = logger
30
+ end
31
+
32
+ def missing_translations_log_handler(exception, locale, key, options)
33
+ if MissingTranslationData === exception
34
+ missing_translations_logger.warn(exception.message)
35
+ return exception.message
36
+ else
37
+ raise exception
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/i18n/missing_translations_raise_handler.rb ADDED
@@ -0,0 +1,25 @@
1
+ # A simple exception handler that behaves like the default exception handler
2
+ # but also raises on missing translations.
3
+ #
4
+ # Useful for identifying missing translations during testing.
5
+ #
6
+ # E.g.
7
+ #
8
+ # require 'globalize/i18n/missing_translations_raise_handler'
9
+ # I18n.exception_handler = :missing_translations_raise_handler
10
+ module I18n
11
+ class << self
12
+ def missing_translations_raise_handler(exception, locale, key, options)
13
+ raise exception
14
+ end
15
+ end
16
+ end
17
+
18
+ I18n.exception_handler = :missing_translations_raise_handler
19
+
20
+ ActionView::Helpers::TranslationHelper.module_eval do
21
+ def translate(key, options = {})
22
+ I18n.translate(key, options)
23
+ end
24
+ alias :t :translate
25
+ end
data/lib/patches/active_record/persistence.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Globalize
2
+ module Persistence
3
+ # Updates the associated record with values matching those of the instance attributes.
4
+ # Returns the number of affected rows.
5
+ def _update_record(attribute_names = self.attribute_names)
6
+ attribute_names_without_translated = attribute_names.select{ |k| not respond_to?('translated?') or not translated?(k) }
7
+ super(attribute_names_without_translated)
8
+ end
9
+
10
+ def _create_record(attribute_names = self.attribute_names)
11
+ attribute_names_without_translated = attribute_names.select{ |k| not respond_to?('translated?') or not translated?(k) }
12
+ super(attribute_names_without_translated)
13
+ end
14
+ end
15
+ end
16
+
17
+ ActiveRecord::Persistence.send(:prepend, Globalize::Persistence)
data/lib/patches/active_record/query_method.rb ADDED
@@ -0,0 +1,3 @@
1
+ if ::ActiveRecord::VERSION::STRING < "5.0.0"
2
+ require_relative 'rails4/query_method'
3
+ end
data/lib/patches/active_record/rails4/query_method.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'active_record/attribute_methods/query'
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Query
6
+ def query_attribute(attr_name)
7
+ unless value = read_attribute(attr_name)
8
+ false
9
+ else
10
+ column = self.class.columns_hash[attr_name]
11
+ if column.nil?
12
+
13
+ # TODO submit a rails patch
14
+
15
+ # not sure what active_record tests say but i guess this should mean:
16
+ # call to_i and check zero? if the value is a Numeric or starts with
17
+ # a digit, so it can meaningfully be typecasted by to_i
18
+
19
+ # if Numeric === value || value !~ /[^0-9]/
20
+ if Numeric === value || value.to_s =~ /^[0-9]/
21
+ !value.to_i.zero?
22
+ else
23
+ return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
24
+ !value.blank?
25
+ end
26
+ elsif column.number?
27
+ !value.zero?
28
+ else
29
+ !value.blank?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/patches/active_record/rails4/serialization.rb ADDED
@@ -0,0 +1,22 @@
1
+ module Globalize
2
+ module AttributeMethods
3
+ module Serialization
4
+ def serialize(attr_name, class_name_or_coder = Object)
5
+ super(attr_name, class_name_or_coder)
6
+
7
+ coder = if class_name_or_coder == ::JSON
8
+ ::ActiveRecord::Coders::JSON
9
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
10
+ class_name_or_coder
11
+ else
12
+ ::ActiveRecord::Coders::YAMLColumn.new(class_name_or_coder)
13
+ end
14
+
15
+ self.globalize_serialized_attributes = globalize_serialized_attributes.dup
16
+ self.globalize_serialized_attributes[attr_name] = coder
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ ActiveRecord::AttributeMethods::Serialization::ClassMethods.send(:prepend, Globalize::AttributeMethods::Serialization)
data/lib/patches/active_record/rails4/uniqueness_validator.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'active_record/validations/uniqueness.rb'
2
+
3
+ module Globalize
4
+ module UniquenessValidatorOverride
5
+ def validate_each(record, attribute, value)
6
+ klass = record.class
7
+ if klass.translates? && klass.translated?(attribute)
8
+ finder_class = klass.translation_class
9
+ table = finder_class.arel_table
10
+
11
+ relation = build_relation(finder_class, table, attribute, value).and(table[:locale].eq(Globalize.locale))
12
+ relation = relation.and(table[klass.reflect_on_association(:translations).foreign_key].not_eq(record.send(:id))) if record.persisted?
13
+
14
+ translated_scopes = Array(options[:scope]) & klass.translated_attribute_names
15
+ untranslated_scopes = Array(options[:scope]) - translated_scopes
16
+
17
+ untranslated_scopes.each do |scope_item|
18
+ scope_value = record.send(scope_item)
19
+ reflection = klass.reflect_on_association(scope_item)
20
+ if reflection
21
+ scope_value = record.send(reflection.foreign_key)
22
+ scope_item = reflection.foreign_key
23
+ end
24
+ relation = relation.and(find_finder_class_for(record).arel_table[scope_item].eq(scope_value))
25
+ end
26
+
27
+ translated_scopes.each do |scope_item|
28
+ scope_value = record.send(scope_item)
29
+ relation = relation.and(table[scope_item].eq(scope_value))
30
+ end
31
+
32
+ if klass.unscoped.with_translations.where(relation).exists?
33
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
34
+ end
35
+ else
36
+ super
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ ActiveRecord::Validations::UniquenessValidator.send :prepend, Globalize::UniquenessValidatorOverride
data/lib/patches/active_record/rails5/uniqueness_validator.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Globalize
2
+ module Validations
3
+ module UniquenessValidator
4
+ def validate_each(record, attribute, value)
5
+ klass = record.class
6
+ if klass.translates? && klass.translated?(attribute)
7
+ finder_class = klass.translation_class
8
+ finder_table = finder_class.arel_table
9
+ relation = build_relation(finder_class, finder_table, attribute, value).where(locale: Globalize.locale)
10
+ relation = relation.where.not(klass.reflect_on_association(:translations).foreign_key => record.send(:id)) if record.persisted?
11
+
12
+
13
+ translated_scopes = Array(options[:scope]) & klass.translated_attribute_names
14
+ untranslated_scopes = Array(options[:scope]) - translated_scopes
15
+
16
+ relation = relation.joins(:globalized_model) if untranslated_scopes.present?
17
+ untranslated_scopes.each do |scope_item|
18
+ scope_value = record.send(scope_item)
19
+ reflection = klass.reflect_on_association(scope_item)
20
+ if reflection
21
+ scope_value = record.send(reflection.foreign_key)
22
+ scope_item = reflection.foreign_key
23
+ end
24
+ relation = relation.where(find_finder_class_for(record).table_name => { scope_item => scope_value })
25
+ end
26
+
27
+ translated_scopes.each do |scope_item|
28
+ scope_value = record.send(scope_item)
29
+ relation = relation.where(scope_item => scope_value)
30
+ end
31
+ relation = relation.merge(options[:conditions]) if options[:conditions]
32
+
33
+ # if klass.unscoped.with_translations.where(relation).exists?
34
+ if relation.exists?
35
+ error_options = options.except(:case_sensitive, :scope, :conditions)
36
+ error_options[:value] = value
37
+ record.errors.add(attribute, :taken, error_options)
38
+ end
39
+ else
40
+ super(record, attribute, value)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ ActiveRecord::Validations::UniquenessValidator.prepend Globalize::Validations::UniquenessValidator
data/lib/patches/active_record/rails5_1/serialization.rb ADDED
@@ -0,0 +1,22 @@
1
+ module Globalize
2
+ module AttributeMethods
3
+ module Serialization
4
+ def serialize(attr_name, class_name_or_coder = Object)
5
+ super(attr_name, class_name_or_coder)
6
+
7
+ coder = if class_name_or_coder == ::JSON
8
+ ::ActiveRecord::Coders::JSON
9
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
10
+ class_name_or_coder
11
+ else
12
+ ::ActiveRecord::Coders::YAMLColumn.new(attr_name, class_name_or_coder)
13
+ end
14
+
15
+ self.globalize_serialized_attributes = globalize_serialized_attributes.dup
16
+ self.globalize_serialized_attributes[attr_name] = coder
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ ActiveRecord::AttributeMethods::Serialization::ClassMethods.send(:prepend, Globalize::AttributeMethods::Serialization)
data/lib/patches/active_record/rails5_1/uniqueness_validator.rb ADDED
@@ -0,0 +1,45 @@
1
+ module Globalize
2
+ module Validations
3
+ module UniquenessValidator
4
+ def validate_each(record, attribute, value)
5
+ klass = record.class
6
+ if klass.translates? && klass.translated?(attribute)
7
+ finder_class = klass.translation_class
8
+ relation = build_relation(finder_class, attribute, value).where(locale: Globalize.locale)
9
+ relation = relation.where.not(klass.reflect_on_association(:translations).foreign_key => record.send(:id)) if record.persisted?
10
+
11
+
12
+ translated_scopes = Array(options[:scope]) & klass.translated_attribute_names
13
+ untranslated_scopes = Array(options[:scope]) - translated_scopes
14
+
15
+ relation = relation.joins(:globalized_model) if untranslated_scopes.present?
16
+ untranslated_scopes.each do |scope_item|
17
+ scope_value = record.send(scope_item)
18
+ reflection = klass.reflect_on_association(scope_item)
19
+ if reflection
20
+ scope_value = record.send(reflection.foreign_key)
21
+ scope_item = reflection.foreign_key
22
+ end
23
+ relation = relation.where(find_finder_class_for(record).table_name => { scope_item => scope_value })
24
+ end
25
+
26
+ translated_scopes.each do |scope_item|
27
+ scope_value = record.send(scope_item)
28
+ relation = relation.where(scope_item => scope_value)
29
+ end
30
+ relation = relation.merge(options[:conditions]) if options[:conditions]
31
+
32
+ if relation.exists?
33
+ error_options = options.except(:case_sensitive, :scope, :conditions)
34
+ error_options[:value] = value
35
+ record.errors.add(attribute, :taken, error_options)
36
+ end
37
+ else
38
+ super(record, attribute, value)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ ActiveRecord::Validations::UniquenessValidator.prepend Globalize::Validations::UniquenessValidator
data/lib/patches/active_record/relation.rb ADDED
@@ -0,0 +1,12 @@
1
+ if ::ActiveRecord::VERSION::STRING >= "5.0.0"
2
+ module Globalize
3
+ module Relation
4
+ def where_values_hash(relation_table_name = table_name)
5
+ return super unless respond_to?(:translations_table_name)
6
+ super.merge(super(translations_table_name))
7
+ end
8
+ end
9
+ end
10
+
11
+ ActiveRecord::Relation.prepend Globalize::Relation
12
+ end
data/lib/patches/active_record/serialization.rb ADDED
@@ -0,0 +1,5 @@
1
+ if ::ActiveRecord::VERSION::STRING < "5.1.0"
2
+ require_relative 'rails4/serialization'
3
+ else
4
+ require_relative 'rails5_1/serialization'
5
+ end
data/lib/patches/active_record/uniqueness_validator.rb ADDED
@@ -0,0 +1,7 @@
1
+ if ::ActiveRecord::VERSION::STRING < "5.0.0"
2
+ require_relative 'rails4/uniqueness_validator'
3
+ elsif ::ActiveRecord::VERSION::STRING < "5.1.0"
4
+ require_relative 'rails5/uniqueness_validator'
5
+ else
6
+ require_relative 'rails5_1/uniqueness_validator'
7
+ end
data/lib/patches/active_record/xml_attribute_serializer.rb ADDED
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'active_record/serializers/xml_serializer'
3
+ rescue LoadError
4
+ end
5
+
6
+ module Globalize
7
+ module XmlSerializer
8
+ module Attribute
9
+ def compute_type
10
+ klass = @serializable.class
11
+ if klass.translates? && klass.translated_attribute_names.include?(name.to_sym)
12
+ :string
13
+ else
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ if defined?(ActiveRecord::XmlSerializer)
22
+ ActiveRecord::XmlSerializer::Attribute.send(:prepend, Globalize::XmlSerializer::Attribute)
23
+ end
metadata ADDED
@@ -0,0 +1,264 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: globalize-danibachar
3
+ version: !ruby/object:Gem::Version
4
+ version: 5.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Sven Fuchs
8
+ - Joshua Harvey
9
+ - Clemens Kofler
10
+ - John-Paul Bader
11
+ - Tomasz Stachewicz
12
+ - Philip Arndt
13
+ - Chris Salzberg
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+ date: 2019-12-02 00:00:00.000000000 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: activerecord
21
+ requirement: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '4.2'
26
+ - - "<"
27
+ - !ruby/object:Gem::Version
28
+ version: '5.3'
29
+ type: :runtime
30
+ prerelease: false
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '4.2'
36
+ - - "<"
37
+ - !ruby/object:Gem::Version
38
+ version: '5.3'
39
+ - !ruby/object:Gem::Dependency
40
+ name: activemodel
41
+ requirement: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '4.2'
46
+ - - "<"
47
+ - !ruby/object:Gem::Version
48
+ version: '5.3'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '4.2'
56
+ - - "<"
57
+ - !ruby/object:Gem::Version
58
+ version: '5.3'
59
+ - !ruby/object:Gem::Dependency
60
+ name: request_store
61
+ requirement: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: '1.0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '1.0'
73
+ - !ruby/object:Gem::Dependency
74
+ name: appraisal
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ type: :development
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ - !ruby/object:Gem::Dependency
88
+ name: database_cleaner
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ - !ruby/object:Gem::Dependency
102
+ name: m
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ - !ruby/object:Gem::Dependency
116
+ name: minitest
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ - !ruby/object:Gem::Dependency
130
+ name: minitest-reporters
131
+ requirement: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ type: :development
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ - !ruby/object:Gem::Dependency
144
+ name: pry
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ - !ruby/object:Gem::Dependency
158
+ name: rake
159
+ requirement: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ type: :development
165
+ prerelease: false
166
+ version_requirements: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ - !ruby/object:Gem::Dependency
172
+ name: rdoc
173
+ requirement: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ type: :development
179
+ prerelease: false
180
+ version_requirements: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ - !ruby/object:Gem::Dependency
186
+ name: sqlite3
187
+ requirement: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ type: :development
193
+ prerelease: false
194
+ version_requirements: !ruby/object:Gem::Requirement
195
+ requirements:
196
+ - - ">="
197
+ - !ruby/object:Gem::Version
198
+ version: '0'
199
+ description: Rails I18n de-facto standard library for ActiveRecord model/data translation.
200
+ email: nobody@globalize-rails.org
201
+ executables: []
202
+ extensions: []
203
+ extra_rdoc_files: []
204
+ files:
205
+ - Appraisals
206
+ - CHANGELOG.md
207
+ - CONTRIBUTING.md
208
+ - Gemfile
209
+ - LICENSE
210
+ - README.md
211
+ - Rakefile
212
+ - lib/globalize.rb
213
+ - lib/globalize/active_record.rb
214
+ - lib/globalize/active_record/act_macro.rb
215
+ - lib/globalize/active_record/adapter.rb
216
+ - lib/globalize/active_record/adapter_dirty.rb
217
+ - lib/globalize/active_record/attributes.rb
218
+ - lib/globalize/active_record/class_methods.rb
219
+ - lib/globalize/active_record/exceptions.rb
220
+ - lib/globalize/active_record/instance_methods.rb
221
+ - lib/globalize/active_record/migration.rb
222
+ - lib/globalize/active_record/translated_attributes_query.rb
223
+ - lib/globalize/active_record/translation.rb
224
+ - lib/globalize/interpolation.rb
225
+ - lib/globalize/version.rb
226
+ - lib/i18n/missing_translations_log_handler.rb
227
+ - lib/i18n/missing_translations_raise_handler.rb
228
+ - lib/patches/active_record/persistence.rb
229
+ - lib/patches/active_record/query_method.rb
230
+ - lib/patches/active_record/rails4/query_method.rb
231
+ - lib/patches/active_record/rails4/serialization.rb
232
+ - lib/patches/active_record/rails4/uniqueness_validator.rb
233
+ - lib/patches/active_record/rails5/uniqueness_validator.rb
234
+ - lib/patches/active_record/rails5_1/serialization.rb
235
+ - lib/patches/active_record/rails5_1/uniqueness_validator.rb
236
+ - lib/patches/active_record/relation.rb
237
+ - lib/patches/active_record/serialization.rb
238
+ - lib/patches/active_record/uniqueness_validator.rb
239
+ - lib/patches/active_record/xml_attribute_serializer.rb
240
+ homepage: http://github.com/globalize/globalize
241
+ licenses:
242
+ - MIT
243
+ metadata: {}
244
+ post_install_message:
245
+ rdoc_options: []
246
+ require_paths:
247
+ - lib
248
+ required_ruby_version: !ruby/object:Gem::Requirement
249
+ requirements:
250
+ - - ">="
251
+ - !ruby/object:Gem::Version
252
+ version: 2.0.0
253
+ required_rubygems_version: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ requirements: []
259
+ rubyforge_project: "[none]"
260
+ rubygems_version: 2.6.14
261
+ signing_key:
262
+ specification_version: 4
263
+ summary: Rails I18n de-facto standard library for ActiveRecord model/data translation
264
+ test_files: []