mobility 0.1.10 → 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4e6bd70e8b65c3c32cb116227a13af4d14c7771
4
- data.tar.gz: 91d3c67192c763d729fd03e958760351d29d0330
3
+ metadata.gz: db71989aceb090cc6bc7fba2a4bfed78ae0ccc23
4
+ data.tar.gz: fa3c53b2540e63638c7e35b2fa43731b7c00ed90
5
5
  SHA512:
6
- metadata.gz: bdfd4ac8ea998c0d1d4d991dbfdabbb9b95fe33ab911ea47f68a8a89bb90216b7bf5c80167c62ede7f527cd85e017ba0ce8066e26d452f7c28dd116e76619f55
7
- data.tar.gz: 25759fa739cacccf79210270bcaf6132b909253672d80818d7be7187a9009263c81a5afdb91d252f0a16b5ff6350441001213b2c3e7083bd8b81be62dfd992aa
6
+ metadata.gz: 9cef93cb7bc0a451a3eaf7ea18b284258894a99c5214b5933f9ff54752bf525900736c35850c6a7e3a712e116e5b3c0a721b230eeefff3fbf7cab452ed6daa66
7
+ data.tar.gz: 0b682ce576e80ba2834f80fe91b19d0027277b06fea27f1fa8eca9e3aa91fc2bba627b0bfea8fe9b495bae5cd19b5ee2eb0b697a6dd592283ecc3e73453ea494
data/CHANGELOG.md CHANGED
@@ -2,9 +2,27 @@
2
2
 
3
3
  ## 0.1
4
4
 
5
+ ### 0.1.11
6
+ * Add backend-specific translations generator (`rails generate
7
+ mobility:translations`)
8
+ ([9dbe4d](https://github.com/shioyama/mobility/commit/9dbe4d2221f3c97ec265c297ad2be201a5180151),
9
+ [583a51](https://github.com/shioyama/mobility/commit/583a51c9945615460079a1f81ffbd7a69d91a581),
10
+ [6b9605](https://github.com/shioyama/mobility/commit/6b9605ed6fa599578fd36065ac17e6b2b93a8378),
11
+ [e2e807](https://github.com/shioyama/mobility/commit/e2e807494bd1f642c67a0dbd678cea49b16f11b0))
12
+ * Fix bug with combination of Column backend and fallthrough accessors
13
+ ([212f07](https://github.com/shioyama/mobility/commit/212f078145f613ab85faf7dbf993c7da9a91bcdd))
14
+ * Raise `InvalidLocale` when getting a locale that is not available
15
+ ([d4f0ee](https://github.com/shioyama/mobility/commit/d4f0ee20d5507ba147f31aa03081f685e31ab46a))
16
+ * Pass options to backend write from setter
17
+ ([5d224f](https://github.com/shioyama/mobility/commit/5d224fa7bb877d9dc1f6c3983b096b22aeea5bc7))
18
+ * Correctly include `FallthroughAccessors` module in module, not backend
19
+ ([d9471d](https://github.com/shioyama/mobility/commit/d9471db7ab71766a98e4e411b476d2197fbf7f51))
20
+ * Handle presence methods in `FallthroughAccessors`
21
+ ([66f630](https://github.com/shioyama/mobility/commit/66f630548c01b8d380c6aeeab4c32b085133c754))
22
+
5
23
  ### 0.1.10
6
24
  * Fix fallback options ([#12](https://github.com/shioyama/mobility/pull/12) and
7
- ([09a163](https://github.com/shioyama/mobility/commit/09a1636bc743633fd13dc6c59ebf1e2366a0e2c4))
25
+ [09a163](https://github.com/shioyama/mobility/commit/09a1636bc743633fd13dc6c59ebf1e2366a0e2c4))
8
26
  * Include fallbacks module by default
9
27
  ([#13](https://github.com/shioyama/mobility/pull/13/files))
10
28
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mobility (0.1.8)
4
+ mobility (0.1.10)
5
5
  i18n (>= 0.6.10)
6
6
  request_store (~> 1.0)
7
7
 
@@ -68,6 +68,7 @@ GEM
68
68
  diff-lcs (>= 1.2.0, < 2.0)
69
69
  rspec-support (~> 3.5.0)
70
70
  rspec-support (3.5.0)
71
+ sequel (4.42.1)
71
72
  shellany (0.0.1)
72
73
  slop (3.6.0)
73
74
  sqlite3 (1.3.13)
@@ -88,6 +89,7 @@ DEPENDENCIES
88
89
  rake (~> 10.0)
89
90
  rspec (~> 3.0)
90
91
  rspec-its (~> 1.2.0)
92
+ sequel (>= 4.0.0, < 5.0)
91
93
  sqlite3
92
94
  yard (~> 0.9.0)
93
95
 
data/README.md CHANGED
@@ -1,68 +1,55 @@
1
- [gem]: https://rubygems.org/gems/mobility
2
- [travis]: https://travis-ci.org/shioyama/mobility
3
- [gemnasium]: https://gemnasium.com/shioyama/mobility
4
- [codeclimate]: https://codeclimate.com/github/shioyama/mobility
5
- [docs]: http://www.rubydoc.info/gems/mobility
6
-
7
- # Mobility
1
+ Mobility
2
+ ========
8
3
 
9
4
  [![Gem Version](https://badge.fury.io/rb/mobility.svg)][gem]
10
5
  [![Build Status](https://travis-ci.org/shioyama/mobility.svg?branch=master)][travis]
11
6
  [![Dependency Status](https://gemnasium.com/shioyama/mobility.svg)][gemnasium]
12
7
  [![Code Climate](https://codeclimate.com/github/shioyama/mobility/badges/gpa.svg)][codeclimate]
13
8
 
14
- Mobility is a gem for storing and retrieving localized data through attributes
15
- on a class. A variety of different storage strategies are supported through
16
- pluggable, customizable "backends" implemented via a common interface.
17
-
18
- Out of the box, Mobility supports:
19
-
20
- - translations as localized columns on the model table (like [traco](https://rubygems.org/gems/traco))
21
- - translations on a model-specific table (like [globalize](https://rubygems.org/gems/globalize), [rails-translate-models](https://rubygems.org/gems/rails-translate-models), [puret](https://rubygems.org/gems/puret), etc)
22
- - translations as values on globally shared key-value tables (the default, see [below](#backend))
23
- - translations as values of a hash serialized on a text column of the model table (like [multilang](https://rubygems.org/gems/multilang))
24
- - translations as values of a hash stored as an hstore column on a Postgres
25
- model table (like [trasto](https://rubygems.org/gems/trasto),
26
- [multilang-hstore](https://rubygems.org/gems/multilang-hstore),
27
- [hstore_translate](https://rubygems.org/gems/hstore_translate),
28
- [sequel-hstore-translate](https://rubygems.org/gems/sequel-hstore-translate),
29
- etc.)
30
- - translations as values of a hash stored as a jsonb column on a Postgres model table (like [json_translate](https://rubygems.org/gems/json_translate))
31
-
32
- Each backend is implemented for both
33
- [ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html) and
34
- [Sequel](http://sequel.jeremyevans.net/) ORM, including a common interface for
35
- [querying](#querying) the database on translated attributes using extended
36
- scopes/datasets. Mobility is however flexible enough to support any storage
37
- strategy, including ones not backed by a database.
38
-
39
- All backends can optionally enable any of a set of common, ORM-independent
40
- features, including:
41
-
42
- - a [cache](#cache) to improve read/write performance (included by default)
43
- - translation [fallbacks](#fallbacks), in case a translation is missing in a
44
- given locale
45
- - (for classes that support it) [dirty](#dirty) tracking of changed attributes
46
- (`ActiveModel::Dirty` in Rails)
47
- - [locale-specific accessors](#locale-accessors) for translated attributes, of
48
- the form `<attribute>_<locale>` (similar to
49
- [globalize-accessors](https://github.com/globalize/globalize-accessors))
9
+ [gem]: https://rubygems.org/gems/mobility
10
+ [travis]: https://travis-ci.org/shioyama/mobility
11
+ [gemnasium]: https://gemnasium.com/shioyama/mobility
12
+ [codeclimate]: https://codeclimate.com/github/shioyama/mobility
13
+ [docs]: http://www.rubydoc.info/gems/mobility
14
+ [wiki]: https://github.com/shioyama/mobility/wiki
15
+
16
+ Mobility is a gem for storing and retrieving translations as attributes on a
17
+ class. These translations could be the content of blog posts, captions on
18
+ images, tags on bookmarks, or anything else you might want to store in
19
+ different languages.
20
+
21
+ Storage of translations is handled by customizable "backends" which encapsulate
22
+ different storage strategies. The default, preferred way to store translations
23
+ is to put them all in a set of two shared tables, but many alternatives are
24
+ also supported, including translatable columns (like
25
+ [Traco](https://github.com/barsoom/traco)) and translation tables (like
26
+ [Globalize](https://github.com/globalize/globalize)), as well as
27
+ database-specific storage solutions such as
28
+ [jsonb](https://www.postgresql.org/docs/current/static/datatype-json.html ) and
29
+ [Hstore](https://www.postgresql.org/docs/current/static/hstore.html) (for
30
+ PostgreSQL).
31
+
32
+ Mobility is a cross-platform solution, currently supporting both
33
+ [ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)
34
+ and [Sequel](http://sequel.jeremyevans.net/) ORM, with support for other
35
+ platforms planned.
50
36
 
51
37
  For a detailed introduction to Mobility, see [Translating with
52
- Mobility](http://dejimata.com/2017/3/3/translating-with-mobility).
38
+ Mobility](http://dejimata.com/2017/3/3/translating-with-mobility). See also the
39
+ [Roadmap](https://github.com/shioyama/mobility/wiki/Roadmap) for what's in the
40
+ works for future releases.
53
41
 
54
- ## Installation
42
+ Installation
43
+ ------------
55
44
 
56
45
  Add this line to your application's Gemfile:
57
46
 
58
47
  ```ruby
59
- gem 'mobility', '~> 0.1.10'
48
+ gem 'mobility', '~> 0.1.11'
60
49
  ```
61
50
 
62
- To translate attributes on a model, you must include (or extend) `Mobility`,
63
- then call `translates` passing in one or more attributes as well as a hash of
64
- options, including the backend to use. (See [Defining Backend
65
- Attributes](#attributes) below.)
51
+ To translate attributes on a model, include (or extend) `Mobility`, then call
52
+ `translates` passing in one or more attributes as well as a hash of options.
66
53
 
67
54
  ### ActiveRecord (Rails)
68
55
 
@@ -74,59 +61,32 @@ the [active_record-4.2
74
61
  branch](https://github.com/shioyama/mobility/tree/active_record_4.2).)
75
62
 
76
63
  If using Mobility in a Rails project, you can run the generator to create an
77
- initializer and (optionally) a migration to create shared tables for the
78
- default key-value backend:
64
+ initializer and a migration to create shared translation tables for the
65
+ default `KeyValue` backend:
79
66
 
80
67
  ```
81
68
  rails generate mobility:install
82
69
  ```
83
70
 
84
- To skip the migration (if you do not plan to use the default `KeyValue`
85
- backend), use the `--without_tables` option:
86
-
87
- ```
88
- rails generate mobility:install --without_tables
89
- ```
71
+ (If you do not plan to use the default backend, you may want to use
72
+ the `--without_tables` option here to skip the migration generation.)
90
73
 
91
74
  The generator will create an initializer file `config/initializers/mobility.rb`
92
- with the line:
93
-
94
- ```
95
- Mobility.config.default_backend = :key_value
96
- ```
97
-
98
- To set a different default backend, set `default_backend` to another value (see
99
- possibilities [below](#backend)). Other configuration options can be set using the
100
- `configure` method, see: {Mobility::Configuration} for details.
101
-
102
- To get started quickly, run the generator with tables, and add the following to
103
- a class to add translated attributes:
75
+ with the lines:
104
76
 
105
77
  ```ruby
106
- class Post < ActiveRecord::Base
107
- include Mobility
108
- translates :title, type: :string
109
- translates :content, type: :text
78
+ Mobility.configure do |config|
79
+ config.default_backend = :key_value
80
+ config.accessor_method = :translates
110
81
  end
111
82
  ```
112
83
 
113
- You now have translated attributes `title` and `content` on the model:
114
-
115
- ```ruby
116
- I18n.locale = :en
117
- post = Post.create(title: "Mobility")
118
- post.title #=> "Mobility"
119
- I18n.locale = :ja
120
- post.title #=> nil
121
- post.title = "モビリティ"
122
- post.save
123
- post.title #=> "モビリティ"
124
- I18n.locale = :en
125
- post.title #=> "Mobility"
126
- ```
84
+ To use a different default backend, set `default_backend` to another value (see
85
+ possibilities [below](#backends)). Other configuration options are
86
+ described in the [API
87
+ docs](http://www.rubydoc.info/gems/mobility/Mobility/Configuration).
127
88
 
128
- Congratulations! Now have a look at the [Usage](#usage) section to see what
129
- else Mobility can do.
89
+ See [Getting Started](#quickstart) to get started translating your models.
130
90
 
131
91
  ### Sequel
132
92
 
@@ -144,343 +104,348 @@ class Post < ::Sequel::Model
144
104
  end
145
105
  ```
146
106
 
147
- Otherwise everything is almost identical to AR, with the exception that there
107
+ Otherwise everything is (almost) identical to AR, with the exception that there
148
108
  is no equivalent to a Rails generator (so you will need to create the migration
149
- for any translation table(s) yourself, see the API docs for details).
150
-
151
- ## Usage
109
+ for any translation table(s) yourself, using Rails generators as a reference).
152
110
 
153
- ### <a name="attributes"></a>Defining Backend Attributes
111
+ The models in examples below all inherit from `ActiveRecord::Base`, but
112
+ everything works exactly the same if the parent class is `Sequel::Model`.
154
113
 
155
- In order to use Mobility on a class, you will need to `extend` the {Mobility}
156
- module and call `translates`, passing in one or more attribute names as well as
157
- a hash of options.
114
+ Usage
115
+ -----
158
116
 
159
- The options hash is used to generate the backend, and has several reserved keys:
117
+ ### <a name="quickstart"></a>Getting Started
160
118
 
161
- - **`backend`** (Symbol or Class)<br>
162
- The backend to use (defaults to the value of `Mobility.default_backend`). If
163
- its value is a symbol it will be converted to CamelCase and appended to the
164
- `Mobility::Backend` module name to get the backend class (so `key_value` will
165
- be converted to {Mobility::Backend::KeyValue}). See the list of [backends](#backend).
166
- - **`cache`** (Boolean)<br>
167
- Whether to use a [cache](#cache).
168
- - **`fallbacks`** (Boolean or Hash)<br>
169
- Enable [fallbacks](#fallbacks), and optionally configure them.
170
- - **`dirty`** (Boolean)<br>
171
- Whether to enable [dirty tracking](#dirty).
172
- - **`locale_accessors`** (Boolean or Array)<br>
173
- Enable [locale accessors](#locale-accessors) and optionally configure them.
174
-
175
- In addition to these, each backend may have specific configuration options. For
176
- example, the default key-value backend, which stores attributes and their
177
- translations as key/value pairs on shared tables, has a `type` option which
178
- specifies which type of column (string or text) to use for storing
179
- translations.
180
-
181
- Here is an example defining three attributes on an ActiveRecord model:
119
+ Once the install generator has been run to generate translation tables, using
120
+ Mobility is as easy as adding a few lines to any class you want to translate:
182
121
 
183
122
  ```ruby
184
- class Post < ActiveRecord::Base
123
+ class Word < ActiveRecord::Base
185
124
  include Mobility
186
- translates :title, :author, backend: :key_value, type: :string, cache: false
187
- translates :content, backend: :key_value, type: :text, fallbacks: true
125
+ translates :name, type: :string
126
+ translates :meaning, type: :text
188
127
  end
189
128
  ```
190
129
 
191
- `title`, `author` and `content` will use the `KeyValue` backend, which stores
192
- translations on two shared translation tables (one for string-valued
193
- translations and one for text-valued translations). The cache (enabled by
194
- default) is disabled for `title` and `author` (but not `content`).
195
- [Fallbacks](#fallbacks) are enabled for `content` (but not `title` or
196
- `author`).
197
-
198
- Finally, `title` and `author` store their translations as string columns
199
- (`type: :string`) whereas `content` stores its values as text columns (`type:
200
- :text`). The `type` key is a backend-specific option used by the `KeyValue`
201
- backend.
202
-
203
- Note that Mobility will detect the model class and use this to determine which
204
- ORM-specific backend to use. In the example above, it will use
205
- {Mobility::Backend::ActiveRecord::KeyValue}; if the class were a
206
- `Sequel::Model`, it would have used {Mobility::Backend::Sequel::KeyValue}. In
207
- general options to configure the backend are ORM-independent.
130
+ You now have translated attributes `name` (as a string column) and `meaning`
131
+ (as a text column) on the model `Word`. You can set their values like you
132
+ would any other attribute:
208
133
 
209
- ### Setting the Locale
210
134
 
211
- Similar to [Globalize](https://github.com/globalize/globalize), Mobility has
212
- its own `locale` which defaults to the value of `I18n.locale` but can also be
213
- set independently with a setter:
135
+ ```ruby
136
+ word = Word.new
137
+ word.name = "mobility"
138
+ word.meaning = "(noun): quality of being changeable, adaptable or versatile"
139
+ word.name
140
+ #=> "mobility"
141
+ word.meaning
142
+ #=> "(noun): quality of being changeable, adaptable or versatile"
143
+ word.save
144
+ word = Word.first
145
+ word.name
146
+ #=> "mobility"
147
+ word.meaning
148
+ #=> "(noun): quality of being changeable, adaptable or versatile"
149
+ ```
150
+
151
+ Presence methods are also supported:
214
152
 
215
153
  ```ruby
216
- I18n.locale = :en
217
- Mobility.locale #=> :en
218
- Mobility.locale = :fr
219
- Mobility.locale #=> :fr
220
- I18n.locale #=> :en
154
+ word.name?
155
+ #=> true
156
+ word.name = nil
157
+ word.name?
158
+ #=> false
159
+ word.name = ""
160
+ word.name?
161
+ #=> false
221
162
  ```
222
163
 
223
- To set the Mobility locale in a block, use {Mobility.with_locale}:
164
+ What's different here is that the value of these attributes changes with the
165
+ value of `I18n.locale`:
224
166
 
225
167
  ```ruby
226
- Mobility.locale = :en
227
- Mobility.with_locale(:ja) do
228
- Mobility.locale #=> :ja
229
- end
230
- Mobility.locale #=> :en
168
+ I18n.locale = :ja
169
+ word.name
170
+ #=> nil
171
+ word.meaning
172
+ #=> nil
231
173
  ```
232
174
 
233
- ### Getting and Setting Translations
234
-
235
- Mobility defines getter, setter, and presence methods for translated attributes
236
- on the model class. Regardless of which backend you use to store translations,
237
- the basic interface for accessing them is the same.
238
-
239
- Assuming we have a model `Post` as above, we can first set the locale, then
240
- create a post with a translated attribute:
175
+ The `name` and `meaning` of this word are not defined in any locale except
176
+ English. Let's define them in Japanese and save the model:
241
177
 
242
178
  ```ruby
243
- Mobility.locale = :en
244
- post = Post.create(title: "Mobility")
245
- post.title
246
- #=> "Mobility"
247
- post.title?
248
- #=> true
179
+ word.name = "モビリティ"
180
+ word.meaning = "(名詞):動きやすさ、可動性"
181
+ word.name
182
+ #=> "モビリティ"
183
+ word.meaning
184
+ #=> "(名詞):動きやすさ、可動性"
185
+ word.save
249
186
  ```
250
187
 
251
- Attributes can similarly be written just like a normal attribute:
188
+ Now our word has names and meanings in two different languages:
252
189
 
253
190
  ```ruby
254
- post.title = "Mobility (noun): quality of being changeable, adaptable or versatile"
255
- post.title
256
- #=> "Mobility (noun): quality of being changeable, adaptable or versatile"
191
+ word = Word.first
192
+ I18n.locale = :en
193
+ word.name
194
+ #=> "mobility"
195
+ word.meaning
196
+ #=> "(noun): quality of being changeable, adaptable or versatile"
197
+ I18n.locale = :ja
198
+ word.name
199
+ #=> "モビリティ"
200
+ word.meaning
201
+ #=> "(名詞):動きやすさ、可動性"
257
202
  ```
258
203
 
259
- If you change locale, you will read/write the attribute in that locale:
204
+ Internally, Mobility is mapping the values in different locales to storage
205
+ locations, usually database columns. By default these values are stored as keys
206
+ (attribute names) and values (attribute translations) on a set of translation
207
+ tables, one for strings and one for text columns, but this can be easily
208
+ changed and/or customized (see the [Backends](#backends) section below).
260
209
 
261
- ```ruby
262
- Mobility.locale = :ja
263
- post.title
264
- #=> nil
265
- post.title?
266
- #=> false
267
- post.title = "Mobility(名詞):動きやすさ、可動性"
268
- post.title
269
- #=> "Mobility(名詞):動きやすさ、可動性"
270
- post.title?
271
- #=> true
272
- ```
210
+ ### Getting and Setting Translations
211
+
212
+ The easiest way to get or set a translation is to use the getter and setter
213
+ methods described above (`word.name` and `word.name=`), but you may want to
214
+ access the value of an attribute in a specific locale, independent of the
215
+ current value of `I18n.locale` (or `Mobility.locale`). There are a few ways to
216
+ do this.
273
217
 
274
- Internally, Mobility maps the `title` accessor method to a backend, which then
275
- handles reading and writing of data. You can access the backend instance for a
276
- given attribute with `<attribute>_backend`, in this case `post.title_backend`,
277
- and read and write locale values directly to/from the backend (although this
278
- should not generally be necessary):
218
+ The first way is to define locale-specific methods, one for each locale you
219
+ want to access directly on a given attribute. These are called "locale
220
+ accessors" in Mobility, and they can be defined by passing a `locale_accessors`
221
+ option when defining translated attributes on the model class:
279
222
 
280
223
  ```ruby
281
- post.title_backend.read(:ja)
282
- #=> "Mobility(名詞):動きやすさ、可動性"
283
- post.title_backend.read(:en)
284
- #=> "Mobility (noun): quality of being changeable, adaptable or versatile"
224
+ class Word < ActiveRecord::Base
225
+ include Mobility
226
+ translates :name, type: :string, locale_accessors: [:en, :ja]
227
+ end
285
228
  ```
286
229
 
287
- You can also access different locales by passing the locale into the getter
288
- method in the options hash:
230
+ Since we have enabled locale accessors for English and Japanese, we can access
231
+ translations for these locales with `name_en` and `name_ja`:
289
232
 
290
233
  ```ruby
291
- post.title(locale: :ja)
292
- #=> "Mobility(名詞):動きやすさ、可動性"
293
- post.title(locale: :en)
294
- #=> "Mobility (noun): quality of being changeable, adaptable or versatile"
234
+ word.name_en
235
+ #=> "mobility"
236
+ word.name_ja
237
+ #=> "モビリティ"
238
+ word.name_en = "foo"
239
+ word.name
240
+ #=> "foo"
295
241
  ```
296
242
 
297
- The translated value can be written using the backend's `write` method:
243
+ Other locales, however, will not work:
298
244
 
299
245
  ```ruby
300
- post.title_backend.write(:en, "new title")
301
- post.save
302
- post.title
303
- #=> "new title"
304
- post.title_backend.write(:en, "Mobility (noun): quality of being changeable, adaptable or versatile")
305
- post.save
306
- post.title
307
- #=> "Mobility (noun): quality of being changeable, adaptable or versatile"
246
+ word.name_ru
247
+ #=> NoMethodError: undefined method `name_ru' for #<Word id: ... >
308
248
  ```
309
249
 
310
- Backends vary in how they implement reading and writing of translated
311
- attributes. The default {Mobility::Backend::KeyValue} backend stores these translations on two
312
- shared tables, `mobility_string_translations` and `mobility_text_translations`,
313
- depending on the `type` of the attribute (corresponding to the type of column
314
- used).
250
+ To generate methods for all locales in `I18n.available_locales` (at the time
251
+ the model is first loaded), use `locale_accessors: true`.
315
252
 
316
- For more details on backend-specific options, see the documentation for each
317
- backend ([below](#backend)).
253
+ An alternative to using the `locale_accessors` option is to use the
254
+ `fallthrough_accessors` option, with `fallthrough_accessors: true`. This uses
255
+ Ruby's [`method_missing`](http://apidock.com/ruby/BasicObject/method_missing)
256
+ method to implicitly define the same methods as above, but supporting any
257
+ locale without any method definitions. (Locale accessors and fallthrough
258
+ locales can be used together without conflict, with locale accessors taking
259
+ precedence if defined for a given locale.)
318
260
 
319
- ### <a name="backend"></a>Choosing a Backend
261
+ For example, if we define `Word` this way:
320
262
 
321
- Mobility supports six different (database) backends:
263
+ ```ruby
264
+ class Word < ActiveRecord::Base
265
+ include Mobility
266
+ translates :name, type: :string, fallthrough_accessors: true
267
+ end
268
+ ```
322
269
 
323
- - **`:column`** ({Mobility::Backend::Column})<br>
324
- Store translations as columns on a table with locale as a postfix, of the
325
- form `title_en`, `title_fr`, etc. for an attribute `title`.
326
- - **`:table`** ({Mobility::Backend::Table})<br>
327
- Store translations on a model-specific table, e.g. for a model `Post` with
328
- table `posts`, store translations on a table `post_translations`, and join
329
- the translation table when fetching translated values.
330
- - **`:key_value`** ({Mobility::Backend::KeyValue})<br>
331
- Store translations on a shared table of locale/attribute translation pairs,
332
- associated through a polymorphic relation with multiple models.
333
- - **`:serialized`** ({Mobility::Backend::Serialized})<br>
334
- Store translations as serialized YAML or JSON on a text column.
335
- - **`:hstore`** ({Mobility::Backend::Hstore})<br>
336
- Store translations as values of a hash stored as a PostgreSQL hstore column.
337
- - **`:jsonb`** ({Mobility::Backend::Jsonb})<br>
338
- Store translations as values of a hash stored as a PostgreSQL jsonb column.
270
+ ... then we can access any locale we want, without specifying them upfront:
339
271
 
340
- Each backend has strengths and weaknesses. If you're unsure of which backend to
341
- use, a rule of thumb would be:
272
+ ```ruby
273
+ word = Word.new
274
+ word.name_fr = "mobilité"
275
+ word.name_fr
276
+ #=> "mobilité"
277
+ word.name_ja = "モビリティ"
278
+ word.name_ja
279
+ #=> "モビリティ"
280
+ ```
342
281
 
343
- - If you're using PostgreSQL as your database, use `:jsonb`.
344
- - If you have a fixed, small set of locales that are not likely to increase,
345
- and have a small number of models to translate, consider `:column`.
346
- - If you have a small set of models to be translated but translation to
347
- potentially many different languages, consider `:table`.
348
- - For all other cases (many locales, many translated models), or if you're just
349
- not sure, the recommended solution is `:key_value` for maximum flexibility
350
- and minimum database migrations.
282
+ (Note however that Mobility will complain if you have
283
+ `I18n.enforce_available_locales` set to `true` and you try accessing a locale
284
+ not present in `I18n.available_locales`; set it to `false` if you want to allow
285
+ *any* locale.)
351
286
 
287
+ Another way to fetch values in a locale is to pass the `locale` option to the
288
+ getter method, like this:
352
289
 
353
- ### <a name="locale-accessors"></a>Locale Accessors
290
+ ```ruby
291
+ word.name(locale: :en)
292
+ #=> "mobility"
293
+ word.name(locale: :fr)
294
+ #=> "mobilité"
295
+ ```
354
296
 
355
- It can sometimes be more convenient to access translations through dedicated
356
- locale-specific methods (for example to update multiple locales at once in a
357
- form). For this purpose, Mobility has a `locale_accessors` option that can be
358
- used to define such methods on a given class:
297
+ You can also *set* the value of an attribute this way; however, since the
298
+ `word.name = <value>` syntax does not accept any options, the only way to do this is to
299
+ use `send` (this is included mostly for consistency):
359
300
 
360
301
  ```ruby
361
- class Post < ActiveRecord::Base
362
- include Mobility
363
- translates :title, locale_accessors: [:en, :ja]
364
- end
302
+ word.send(:name=, "mobiliteit", locale: :nl)
303
+ word.name_nl
304
+ #=> "mobiliteit"
365
305
  ```
366
306
 
367
- (Note: The backend defaults to `key_value`, and `type` defaults to `text`, but
368
- options described here are independent of backend so we will omit both for what
369
- follows.)
370
-
371
- Since we have enabled locale accessors for English and Japanese, we can access
372
- translations for these locales with:
307
+ Yet another way to get and set translated attributes is to call `read` and
308
+ `write` on the storage backend, which can be accessed using the method
309
+ `<attribute>_backend`. Without worrying too much about the details of
310
+ how this works for now, the syntax for doing this is simple:
373
311
 
374
312
  ```ruby
375
- post.title_en
376
- #=> "Mobility (noun): quality of being changeable, adaptable or versatile"
377
- post.title_ja
378
- #=> "Mobility(名詞):動きやすさ、可動性"
379
- post.title_en = "foo"
380
- post.title
313
+ word.name_backend.read(:en)
314
+ #=> "mobility"
315
+ word.name_backend.read(:nl)
316
+ #=> "mobiliteit"
317
+ word.name_backend.write(:en, "foo")
318
+ word.name_backend.read(:en)
381
319
  #=> "foo"
382
320
  ```
383
321
 
384
- Alternatively, just using `locale_accessors: true` will enable all locales in
385
- `I18n.available_locales`.
322
+ Internally, all methods for accessing translated attributes ultimately end up
323
+ reading and writing from the backend instance this way.
386
324
 
387
- An alternative to using the `locale_accessors` option is to use the
388
- `fallthrough_accessors` option (defined in {Mobility::FallthroughAccessors})
389
- with `fallthrough_accessors: true`. This uses `method_missing` to implicitly
390
- define the same methods as above, but supporting any locale without any method
391
- definitions. [Dirty tracking](#dirty) enables fallthrough locales for tracking
392
- attribute changes. (Both locale accessors and fallthrough locales can be used
393
- together without conflict.)
325
+ ### Setting the Locale
394
326
 
395
- For more details, see: {Mobility::Attributes} (specifically, the private method
396
- `define_locale_accessors`).
327
+ It may not always be desirable to use `I18n.locale` to set the locale for
328
+ content translations. For example, a user whose interface is in English
329
+ (`I18n.locale` is `:en`) may want to see content in Japanese. If you use
330
+ `I18n.locale` exclusively for the locale, you will have a hard time showing
331
+ stored translations in one language while showing the interface in another
332
+ language.
397
333
 
398
- ### <a name="cache"></a>Cache
334
+ For these cases, Mobility also has its own locale, which defaults to
335
+ `I18n.locale` but can be set independently:
399
336
 
400
- The Mobility cache caches localized values that have been fetched once so they
401
- can be quickly retrieved again, and also speeds up writes for some backends.
402
- The cache is enabled by default and should generally only be disabled when
403
- debugging; this can be done by passing `cache: false` to any backend.
337
+ ```ruby
338
+ I18n.locale = :en
339
+ Mobility.locale #=> :en
340
+ Mobility.locale = :fr
341
+ Mobility.locale #=> :fr
342
+ I18n.locale #=> :en
343
+ ```
404
344
 
405
- In general, you should not need to actually see the cache, but for debugging
406
- purposes you can access it by calling the private `cache` method on the
407
- backend:
345
+ To set the Mobility locale in a block, you can use `Mobility.with_locale` (like
346
+ `I18n.with_locale`):
408
347
 
409
348
  ```ruby
410
- post.title_backend.send :cache
411
- #=> #<Mobility::Backend::KeyValue::TranslationsCache:0x0056139b391b38 @cache={}>
349
+ Mobility.locale = :en
350
+ Mobility.with_locale(:ja) do
351
+ Mobility.locale #=> :ja
352
+ end
353
+ Mobility.locale #=> :en
412
354
  ```
413
355
 
414
- For more details, see: {Mobility::Backend::Cache}.
356
+ Mobility uses [RequestStore](https://github.com/steveklabnik/request_store) to
357
+ reset these global variables after every request, so you don't need to worry
358
+ about thread safety. If you're not using Rails, consult RequestStore's
359
+ [README](https://github.com/steveklabnik/request_store#no-rails-no-problem) for
360
+ details on how to configure it for your use case.
415
361
 
416
362
  ### <a name="fallbacks"></a>Fallbacks
417
363
 
418
- Mobility offers basic support for translation fallbacks (similar to gems such
419
- as [Globalize](https://github.com/globalize/globalize) and
420
- [Traco](https://github.com/barsoom/traco)). To enable fallbacks, pass a hash
421
- with fallbacks for each locale as an option to the backend:
364
+ Mobility offers basic support for translation fallbacks. To enable fallbacks,
365
+ pass a hash with fallbacks for each locale as an option when defining
366
+ translated attributes on a class:
422
367
 
423
368
  ```ruby
424
- class Post < ActiveRecord::Base
369
+ class Word < ActiveRecord::Base
425
370
  include Mobility
426
- translates :title, locale_accessors: [:en, :ja, :fr], fallbacks: { en: :ja, fr: :ja }
371
+ translates :name, type: :string, fallbacks: { de: :ja, fr: :ja }
372
+ translates :meaning, type: :text, fallbacks: { de: :ja, fr: :ja }
427
373
  end
428
374
  ```
429
375
 
430
376
  Internally, Mobility assigns the fallbacks hash to an instance of
431
- `I18n::Locale::Fallbacks.new`.
377
+ `I18n::Locale::Fallbacks.new` (this can be customized by setting the
378
+ `default_fallbacks` configuration option, see the [API documentation on
379
+ configuration](http://www.rubydoc.info/gems/mobility/Mobility/Configuration)).
432
380
 
433
- By setting fallbacks for English and French to Japanese, values will fall
434
- through to the Japanese value if none is present for either of these locales:
381
+ By setting fallbacks for German and French to Japanese, values will fall
382
+ through to the Japanese value if none is present for either of these locales,
383
+ but not for other locales:
435
384
 
436
385
  ```ruby
437
- Mobility.locale = :en
438
- post = Post.first
439
- post.title = nil
440
- post.save
441
- post.title_en
442
- #=> "Mobility(名詞):動きやすさ、可動性"
443
- post.title_ja
444
- #=> "Mobility(名詞):動きやすさ、可動性"
445
- post.title_fr
446
- #=> "Mobility(名詞):動きやすさ、可動性"
386
+ Mobility.locale = :ja
387
+ word = Word.create(name: "モビリティ", meaning: "(名詞):動きやすさ、可動性")
388
+ word.name(locale: :de)
389
+ #=> "モビリティ"
390
+ word.meaning(locale: :de)
391
+ #=> "(名詞):動きやすさ、可動性"
392
+ word.name(locale: :fr)
393
+ #=> "モビリティ"
394
+ word.meaning(locale: :fr)
395
+ #=> "(名詞):動きやすさ、可動性"
396
+ word.name(locale: :ru)
397
+ #=> nil
398
+ word.meaning(locale: :ru)
399
+ #=> nil
447
400
  ```
448
401
 
449
402
  You can optionally disable fallbacks to get the real value for a given locale
450
403
  (for example, to check if a value in a particular locale is set or not) by
451
- passing `fallback: false` (note that the key is the *singular*, not plural) to
452
- the getter method:
404
+ passing `fallback: false` (*singular*, not plural) to the getter method:
453
405
 
454
406
  ```ruby
455
- post.title(fallback: false)
407
+ word.meaning(locale: :de, fallback: false)
456
408
  #=> nil
457
- post.title(locale: :fr, fallback: false)
409
+ word.meaning(locale: :fr, fallback: false)
458
410
  #=> nil
411
+ word.meaning(locale: :ja, fallback: false)
412
+ #=> "(名詞):動きやすさ、可動性"
459
413
  ```
460
414
 
461
415
  You can also set the fallback locales for a single read by passing one or more
462
- locales, like this:
416
+ locales:
463
417
 
464
418
  ```ruby
465
- post.title_fr = "mobilité: aptitude à bouger, à se déplacer, à changer, à évoluer"
466
- post.save
467
- post.title
419
+ Mobility.with_locale(:fr) do
420
+ word.meaning = "(nf): aptitude à bouger, à se déplacer, à changer, à évoluer"
421
+ end
422
+ word.save
423
+ word.meaning(locale: :de, fallback: false)
468
424
  #=> nil
469
- post.title(fallback: :fr)
470
- #=> "mobilité: aptitude à bouger, à se déplacer, à changer, à évoluer"
471
- post.title(fallback: [:ja, :fr])
472
- #=> "Mobility(名詞):動きやすさ、可動性"
425
+ word.meaning(locale: :de, fallback: :fr)
426
+ #=> "(nf): aptitude à bouger, à se déplacer, à changer, à évoluer"
427
+ word.meaning(locale: :de, fallback: [:ja, :fr])
428
+ #=> "(名詞):動きやすさ、可動性"
473
429
  ```
474
430
 
475
- For more details, see: {Mobility::Backend::Fallbacks}.
431
+ For more details, see the [API documentation on
432
+ fallbacks](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Fallbacks)
433
+ and [this article on I18n
434
+ fallbacks](https://github.com/svenfuchs/i18n/wiki/Fallbacks).
476
435
 
477
436
  ### <a name="dirty"></a>Dirty Tracking
478
437
 
479
- Dirty tracking (tracking of changed attributes) can be enabled for models which support it. Currently this includes models including `ActiveModel::Dirty` or Sequel models with the `dirty` plugin enabled.
438
+ Dirty tracking (tracking of changed attributes) can be enabled for models which
439
+ support it. Currently this is models which include
440
+ [ActiveModel::Dirty](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html)
441
+ (like `ActiveRecord::Base`) and Sequel models (through the
442
+ [dirty](http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/Dirty.html)
443
+ plugin).
480
444
 
481
- Enabling dirty tracking is as simple as sending the `dirty: true` option to any
482
- backend. The way dirty tracking works is somewhat dependent on the model class
483
- (ActiveModel or Sequel); we will describe the ActiveModel implementation here.
445
+ Enabling dirty tracking is as simple as sending the `dirty: true` option when
446
+ defining a translated attribute. The way dirty tracking works is somewhat
447
+ dependent on the model class (ActiveModel or Sequel); we will describe the
448
+ ActiveModel implementation here.
484
449
 
485
450
  First, enable dirty tracking (note that this is a persisted AR model, although
486
451
  dirty tracking is not specific to AR and works for non-persisted models as well):
@@ -488,29 +453,39 @@ dirty tracking is not specific to AR and works for non-persisted models as well)
488
453
  ```ruby
489
454
  class Post < ActiveRecord::Base
490
455
  include Mobility
491
- translates :title, dirty: true
456
+ translates :title, type: :string, dirty: true
492
457
  end
493
458
  ```
494
459
 
495
- Now set the attribute in two locales:
460
+ Let's assume we start with a post with a title in English and Japanese:
461
+
462
+ ```ruby
463
+ post = Post.create(title: "Introducing Mobility")
464
+ Mobility.with_locale(:ja) { post.title = "モビリティの紹介" }
465
+ post.save
466
+ ```
467
+
468
+ Now let's change the title:
496
469
 
497
470
  ```ruby
498
- post.title
499
- #=> "Mobility (noun): quality of being changeable, adaptable or versatile"
471
+ post = Post.first
472
+ post.title #=> "Introducing Mobility"
500
473
  post.title = "a new title"
501
- post.title_ja
502
- #=> "Mobility(名詞):動きやすさ、可動性"
503
- post.title = "新しいタイトル"
474
+ Mobility.with_locale(:ja) do
475
+ post.title #=> "モビリティの紹介"
476
+ post.title = "新しいタイトル"
477
+ post.title #=> "新しいタイトル"
478
+ end
504
479
  ```
505
480
 
506
481
  Now you can use dirty methods as you would any other (untranslated) attribute:
507
482
 
508
483
  ```ruby
509
484
  post.title_was
510
- #=> "Mobility (noun): quality of being changeable, adaptable or versatile"
485
+ #=> "Introducing Mobility"
511
486
  Mobility.locale = :ja
512
487
  post.title_was
513
- #=> "Mobility(名詞):動きやすさ、可動性"
488
+ #=> "モビリティの紹介"
514
489
  post.changed
515
490
  ["title_en", "title_ja"]
516
491
  post.save
@@ -524,22 +499,41 @@ post.previous_changes
524
499
  {
525
500
  "title_en" =>
526
501
  [
527
- "Mobility (noun): quality of being changeable, adaptable or versatile",
502
+ "Introducing Mobility",
528
503
  "a new title"
529
504
  ],
530
505
  "title_ja" =>
531
506
  [
532
- "Mobility(名詞):動きやすさ、可動性",
507
+ "モビリティの紹介",
533
508
  "新しいタイトル"
534
509
  ]
535
510
  }
536
511
  ```
537
512
 
538
- You will notice that Mobility uses locale accessor methods to indicate which
539
- locale has changed; dirty tracking is implemented this way to ensure that it is
540
- clear what has changed in which locale, avoiding any possible ambiguity.
513
+ Notice that Mobility uses locale suffixes to indicate which locale has changed;
514
+ dirty tracking is implemented this way to ensure that it is clear what
515
+ has changed in which locale, avoiding any possible ambiguity.
516
+
517
+ For more details, see the [API documentation on dirty
518
+ tracking](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Dirty).
541
519
 
542
- For more details, see: {Mobility::Backend::Dirty}.
520
+ ### Cache
521
+
522
+ The Mobility cache caches localized values that have been fetched once so they
523
+ can be quickly retrieved again. The cache is enabled by default and should
524
+ generally only be disabled when debugging; this can be done by passing `cache:
525
+ false` when defining an attribute, like this:
526
+
527
+ ```ruby
528
+ class Word < ActiveRecord::Base
529
+ include Mobility
530
+ translates :name, type: :string, cache: false
531
+ end
532
+ ```
533
+
534
+ The cache is normally just a hash with locale keys and string (translation)
535
+ values, but some backends (e.g. KeyValue and Table backends) have slightly more
536
+ complex implementations.
543
537
 
544
538
  ### <a name="querying"></a>Querying
545
539
 
@@ -554,12 +548,12 @@ So assuming a model:
554
548
  ```ruby
555
549
  class Post < ActiveRecord::Base
556
550
  include Mobility
557
- translates :title, backend: :key_value, type: :string
558
- translates :content, backend: :key_value, type: :text
551
+ translates :title, type: :string
552
+ translates :content, type: :text
559
553
  end
560
554
  ```
561
555
 
562
- we can query for posts with title "foo" and content "bar" just as we would
556
+ ... we can query for posts with title "foo" and content "bar" just as we would
563
557
  query on untranslated attributes, and Mobility will convert the queries to
564
558
  whatever the backend requires to actually return the correct results:
565
559
 
@@ -571,108 +565,128 @@ results in the SQL:
571
565
 
572
566
  ```sql
573
567
  SELECT "posts".* FROM "posts"
574
- INNER JOIN "mobility_string_translations" "title_mobility_string_translations"
568
+ INNER JOIN "mobility_string_translations" "title_mobility_string_translations"
575
569
  ON "title_mobility_string_translations"."key" = 'title'
576
570
  AND "title_mobility_string_translations"."locale" = 'en'
577
571
  AND "title_mobility_string_translations"."translatable_type" = 'Post'
578
572
  AND "title_mobility_string_translations"."translatable_id" = "posts"."id"
579
- INNER JOIN "mobility_text_translations" "content_mobility_text_translations"
573
+ INNER JOIN "mobility_text_translations" "content_mobility_text_translations"
580
574
  ON "content_mobility_text_translations"."key" = 'content'
581
575
  AND "content_mobility_text_translations"."locale" = 'en'
582
576
  AND "content_mobility_text_translations"."translatable_type" = 'Post'
583
577
  AND "content_mobility_text_translations"."translatable_id" = "posts"."id"
584
- WHERE "content_mobility_text_translations"."value" = 'bar' AND
585
- "title_mobility_string_translations"."value" = 'foo'
578
+ WHERE "content_mobility_text_translations"."value" = 'bar'
579
+ AND "title_mobility_string_translations"."value" = 'foo'
586
580
  ```
587
581
 
588
582
  As can be seen in the query above, behind the scenes Mobility joins two tables,
589
583
  one with string translations and one with text translations, and aliases the
590
584
  joins for each attribute so as to match the particular values passed in to the
591
- query. Details of how this is done can be found in
592
- {Mobility::Backend::ActiveRecord::QueryMethods}.
585
+ query. Details of how this is done can be found in the [API documentation for
586
+ AR query
587
+ methods](http://www.rubydoc.info/gems/mobility/Mobility/Backend/ActiveRecord/KeyValue/QueryMethods).
588
+
589
+ If you would prefer to avoid the `i18n` scope everywhere, define it as a
590
+ default scope on your model:
593
591
 
594
- Note that this feature is available for all backends *except* the `serialized`
595
- backend, since serialized database values are not query-able (an
596
- `ArgumentError` error will be raised if you try to query on attributes of this
597
- backend).
592
+ ```ruby
593
+ class Post < ActiveRecord::Base
594
+ include Mobility
595
+ translates :title, type: :string
596
+ translates :content, type: :text
597
+ default_scope { i18n }
598
+ end
599
+ ```
598
600
 
599
- For more details, see subclasses of
600
- {Mobility::Backend::ActiveRecord::QueryMethods} or
601
- {Mobility::Backend::Sequel::QueryMethods}.
601
+ Now translated attributes can be queried just like normal attributes:
602
602
 
603
- ## Philosophy
603
+ ```ruby
604
+ Post.find_by(title: "Introducing Mobility")
605
+ #=> finds post with English title "Introducing Mobility"
606
+ ```
604
607
 
605
- As its name implies, Mobility was created with a very specific design goal: to
606
- separate the problem of translating model attributes from the constraints of
607
- any particular translation solution, so that application designers are free to
608
- mix, match and customize strategies to suit their needs.
608
+ <a name="backends"></a>Backends
609
+ --------
609
610
 
610
- To this end, Mobility backends strictly enforce the rule that *no backend
611
- should modify a parent class in any way which would interfere with other
612
- backends operating on the same class*. This is done using a heavy dose of
613
- metaprogramming, details of which can be found in the [API
614
- documentation][docs]
611
+ Mobility supports different storage strategies, called "backends". The default
612
+ backend is the `KeyValue` backend, which stores translations in two tables, by
613
+ default named `mobility_text_translations` and `mobility_string_translations`.
615
614
 
616
- In practice, this means that you can use different backends for different
617
- attributes *on the same class* without any conflict, e.g. (assuming we
618
- are using Postgres as our database):
615
+ You can set the default backend to a different value in the global
616
+ configuration, or you can set it explicitly when defining a translated
617
+ attribute, like this:
619
618
 
620
619
  ```ruby
621
- class Post < ActiveRecord::Base
622
- include Mobility
623
- translates :title, backend: :key_value, type: :string
624
- translates :content, backend: :column, cache: false
625
- translates :author_name, backend: :jsonb
620
+ class Word < ActiveRecord::Base
621
+ translates :name, backend: :table
626
622
  end
627
623
  ```
628
624
 
629
- Attributes can be set and fetched and Mobility will transparently handle
630
- reading and writing through the respective backend: a shared
631
- `mobility_string_translations` table for `title`, the `content_en` and
632
- `content_ja` columns on the `posts` table for `content`, and JSON keys and
633
- values on the jsonb `author_name` column for `author_name`.
625
+ This would set the `name` attribute to use the `Table` backend (see below).
626
+ The `type` option (`type: :string` or `type: :text`) is missing here because
627
+ this is an option specific to the KeyValue backend (specifying which shared
628
+ table to store translations on). Backends have their own specific options; see
629
+ the API documentation for which options are available for each.
634
630
 
635
- Similarly, we can query for a particular post using the `i18n` scope without worrying about how attributes are actually stored. So this query:
631
+ Everything else described above (fallbacks, dirty tracking, locale accessors,
632
+ caching, querying, etc) is the same regardless of which backend you use.
636
633
 
637
- ```ruby
638
- Post.i18n.where(title: "foo",
639
- content: "bar",
640
- author_name: "baz")
634
+ ### Table Backend (like Globalize)
635
+
636
+ The `Table` backend stores translations as columns on a model-specific table. If
637
+ your model uses the table `posts`, then by default this backend will store an
638
+ attribute `title` on a table `post_translations`, and join the table to
639
+ retrieve the translated value.
640
+
641
+ To use the table backend on a model, you will need to first create a
642
+ translation table for the model, which (with Rails) you can do using the
643
+ `mobility:translations` generator:
644
+
645
+ ```
646
+ rails generate mobility:translations post title:string content:text
641
647
  ```
642
648
 
643
- will result in the following SQL:
649
+ This will generate the `post_translations` table with columns `title` and
650
+ `content`, and all other necessary columns and indices. For more details see
651
+ the API documentation on the [`Mobility::Backend::Table`
652
+ class](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Table).
653
+
654
+ ### Column Backend (like Traco)
655
+
656
+ The `Column` backend stores translationsi as columns with locale suffixes on
657
+ the model table. For an attribute `title`, these would be of the form
658
+ `title_en`, `title_fr`, etc.
659
+
660
+ Use the `mobility:translations` generator to add columns for locales in
661
+ `I18n.available_locales` to your model:
644
662
 
645
- ```sql
646
- SELECT "posts".* FROM "posts"
647
- INNER JOIN "mobility_string_translations" "title_mobility_string_translations"
648
- ON "title_mobility_string_translations"."key" = 'title'
649
- AND "title_mobility_string_translations"."locale" = 'en'
650
- AND "title_mobility_string_translations"."translatable_type" = 'Post'
651
- AND "title_mobility_string_translations"."translatable_id" = "posts"."id"
652
- WHERE (posts.author_name @> ('{"en":"baz"}')::jsonb)
653
- AND "posts"."content_en" = 'bar'
654
- AND "title_mobility_string_translations"."value" = 'foo'
655
663
  ```
664
+ rails generate mobility:translations post title:string content:text
665
+ ```
666
+
667
+ For more details, see the API documentation on the [`Mobility::Backend::Column`
668
+ class](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Column).
656
669
 
657
- The query combines conditions specific to each backend, together fetching the
658
- record which satisfies all of them.
670
+ ### PostgreSQL-specific Backends
659
671
 
660
- Beyond the goal of making it easy to combine backends in a single class (which
661
- admittedly is a rather specialized use-case), the flexibility Mobility enforces
662
- makes it possible to build more complex translation-based applications without
663
- worrying about the details of the translation storage strategy used. It also
664
- saves effort in integrating translation storage with various other gems, since
665
- only one integration is required rather than one for each translation gem.
672
+ Mobility also supports jsonb and Hstore storage options, if you are using
673
+ PostgreSQL as your database. To use this option, create column(s) on the model
674
+ table for each translated attribute, and set your backend to `:jsonb` or
675
+ `:hstore`. Other details are covered in the API documentation
676
+ ([`Mobility::Backend::Jsonb`](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Jsonb)
677
+ and
678
+ [`Mobility::Backend::Hstore`](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Hstore)).
666
679
 
667
- ## Development
680
+ Development
681
+ -----------
668
682
 
669
683
  ### Custom Backends
670
684
 
671
685
  Although Mobility is primarily oriented toward storing ActiveRecord model
672
686
  translations, it can potentially be used to handle storing translations in
673
- other formats, for example in the cloud through an API, or in files. In
674
- particular, the features mentioned above (locale accessors, caching, fallbacks,
675
- dirty tracking to some degree) are not specific to database storage.
687
+ other formats. In particular, the features mentioned above (locale accessors,
688
+ caching, fallbacks, dirty tracking to some degree) are not specific to database
689
+ storage.
676
690
 
677
691
  To use a custom backend, simply pass the name of a class which includes
678
692
  `Mobility::Backend` to `translates`:
@@ -689,9 +703,9 @@ class MyClass
689
703
  end
690
704
  ```
691
705
 
692
- For details on how to define a backend class, see the {Mobility::Backend}
693
- module and other classes defined in the [API
694
- documentation][docs]
706
+ For details on how to define a backend class, see the [API documentation on the
707
+ `Mobility::Backend`
708
+ module](http://www.rubydoc.info/gems/mobility/Mobility/Backend).
695
709
 
696
710
  ### Testing Backends
697
711
 
@@ -733,11 +747,14 @@ can be changed, see the shared examples for details.
733
747
  Backends are also each tested against specialized specs targeted at their
734
748
  particular implementations.
735
749
 
736
- ## More Information
750
+ More Information
751
+ ----------------
737
752
 
738
753
  - [Github repository](https://www.github.com/shioyama/mobility)
739
754
  - [API documentation][docs]
755
+ - [Wiki][wiki]
740
756
 
741
- ## License
757
+ License
758
+ -------
742
759
 
743
760
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).