mobility 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +19 -0
- data/Gemfile.lock +153 -0
- data/Guardfile +70 -0
- data/README.md +603 -13
- data/Rakefile +42 -0
- data/lib/generators/mobility/install_generator.rb +45 -0
- data/lib/generators/mobility/templates/create_string_translations.rb +15 -0
- data/lib/generators/mobility/templates/create_text_translations.rb +15 -0
- data/lib/mobility.rb +203 -2
- data/lib/mobility/active_model.rb +6 -0
- data/lib/mobility/active_model/attribute_methods.rb +27 -0
- data/lib/mobility/active_model/backend_resetter.rb +26 -0
- data/lib/mobility/active_record.rb +39 -0
- data/lib/mobility/active_record/backend_resetter.rb +26 -0
- data/lib/mobility/active_record/model_translation.rb +14 -0
- data/lib/mobility/active_record/string_translation.rb +7 -0
- data/lib/mobility/active_record/text_translation.rb +7 -0
- data/lib/mobility/active_record/translation.rb +14 -0
- data/lib/mobility/attributes.rb +210 -0
- data/lib/mobility/backend.rb +152 -0
- data/lib/mobility/backend/active_model.rb +7 -0
- data/lib/mobility/backend/active_model/dirty.rb +84 -0
- data/lib/mobility/backend/active_record.rb +13 -0
- data/lib/mobility/backend/active_record/column.rb +52 -0
- data/lib/mobility/backend/active_record/column/query_methods.rb +40 -0
- data/lib/mobility/backend/active_record/hash_valued.rb +58 -0
- data/lib/mobility/backend/active_record/hstore.rb +36 -0
- data/lib/mobility/backend/active_record/hstore/query_methods.rb +53 -0
- data/lib/mobility/backend/active_record/jsonb.rb +43 -0
- data/lib/mobility/backend/active_record/jsonb/query_methods.rb +53 -0
- data/lib/mobility/backend/active_record/key_value.rb +126 -0
- data/lib/mobility/backend/active_record/key_value/query_methods.rb +63 -0
- data/lib/mobility/backend/active_record/query_methods.rb +36 -0
- data/lib/mobility/backend/active_record/serialized.rb +93 -0
- data/lib/mobility/backend/active_record/serialized/query_methods.rb +32 -0
- data/lib/mobility/backend/active_record/table.rb +197 -0
- data/lib/mobility/backend/active_record/table/query_methods.rb +91 -0
- data/lib/mobility/backend/cache.rb +110 -0
- data/lib/mobility/backend/column.rb +52 -0
- data/lib/mobility/backend/dirty.rb +28 -0
- data/lib/mobility/backend/fallbacks.rb +89 -0
- data/lib/mobility/backend/hstore.rb +21 -0
- data/lib/mobility/backend/jsonb.rb +21 -0
- data/lib/mobility/backend/key_value.rb +71 -0
- data/lib/mobility/backend/null.rb +24 -0
- data/lib/mobility/backend/orm_delegator.rb +33 -0
- data/lib/mobility/backend/sequel.rb +14 -0
- data/lib/mobility/backend/sequel/column.rb +40 -0
- data/lib/mobility/backend/sequel/column/query_methods.rb +24 -0
- data/lib/mobility/backend/sequel/dirty.rb +54 -0
- data/lib/mobility/backend/sequel/hash_valued.rb +51 -0
- data/lib/mobility/backend/sequel/hstore.rb +36 -0
- data/lib/mobility/backend/sequel/hstore/query_methods.rb +42 -0
- data/lib/mobility/backend/sequel/jsonb.rb +43 -0
- data/lib/mobility/backend/sequel/jsonb/query_methods.rb +42 -0
- data/lib/mobility/backend/sequel/key_value.rb +139 -0
- data/lib/mobility/backend/sequel/key_value/query_methods.rb +48 -0
- data/lib/mobility/backend/sequel/query_methods.rb +22 -0
- data/lib/mobility/backend/sequel/serialized.rb +133 -0
- data/lib/mobility/backend/sequel/serialized/query_methods.rb +20 -0
- data/lib/mobility/backend/sequel/table.rb +149 -0
- data/lib/mobility/backend/sequel/table/query_methods.rb +48 -0
- data/lib/mobility/backend/serialized.rb +53 -0
- data/lib/mobility/backend/table.rb +93 -0
- data/lib/mobility/backend_resetter.rb +44 -0
- data/lib/mobility/configuration.rb +31 -0
- data/lib/mobility/core_ext/nil.rb +10 -0
- data/lib/mobility/core_ext/object.rb +19 -0
- data/lib/mobility/core_ext/string.rb +16 -0
- data/lib/mobility/instance_methods.rb +34 -0
- data/lib/mobility/orm.rb +4 -0
- data/lib/mobility/sequel.rb +26 -0
- data/lib/mobility/sequel/backend_resetter.rb +26 -0
- data/lib/mobility/sequel/column_changes.rb +29 -0
- data/lib/mobility/sequel/model_translation.rb +20 -0
- data/lib/mobility/sequel/string_translation.rb +7 -0
- data/lib/mobility/sequel/text_translation.rb +7 -0
- data/lib/mobility/sequel/translation.rb +53 -0
- data/lib/mobility/translates.rb +75 -0
- data/lib/mobility/wrapper.rb +31 -0
- metadata +152 -12
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.travis.yml +0 -5
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/mobility.gemspec +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b8a61fafb34ad1bf6bbb68752bc86783129c501
|
4
|
+
data.tar.gz: abd3cc3d8c50b1ea7eac43cfe4ede3783701daf8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e83ad48432de117487fae60d0c5053d0cd72eb9ae82914d1d983d1adc1fc206ed326b200da730261b9fe76dd81dac5eb31bcb782d6094cb19efe3082fc037e1
|
7
|
+
data.tar.gz: c974558f2b6627b9e0845852000b49a5803561b9caec7370e0567998a9d85e3250d13f8640c28f4b5061060bdc2cb13b36e51b8d33479c23d48a51439a0dabbe
|
data/Gemfile
CHANGED
@@ -2,3 +2,22 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in mobility.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
group :development, :test do
|
7
|
+
if ENV['ORM'] == 'active_record'
|
8
|
+
gem 'activerecord', '>= 5.0', '< 5.1'
|
9
|
+
gem "generator_spec", '~> 0.9.3'
|
10
|
+
end
|
11
|
+
|
12
|
+
if ENV['ORM'] == 'sequel'
|
13
|
+
gem 'sequel', '>= 4.0.0', '< 5.0'
|
14
|
+
end
|
15
|
+
|
16
|
+
platforms :ruby do
|
17
|
+
gem 'guard-rspec'
|
18
|
+
gem 'pry-byebug'
|
19
|
+
gem 'sqlite3'
|
20
|
+
gem 'mysql2', '~> 0.3.10'
|
21
|
+
gem 'pg'
|
22
|
+
end
|
23
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mobility (0.0.1)
|
5
|
+
i18n
|
6
|
+
request_store (~> 1.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
actionpack (5.0.1)
|
12
|
+
actionview (= 5.0.1)
|
13
|
+
activesupport (= 5.0.1)
|
14
|
+
rack (~> 2.0)
|
15
|
+
rack-test (~> 0.6.3)
|
16
|
+
rails-dom-testing (~> 2.0)
|
17
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
18
|
+
actionview (5.0.1)
|
19
|
+
activesupport (= 5.0.1)
|
20
|
+
builder (~> 3.1)
|
21
|
+
erubis (~> 2.7.0)
|
22
|
+
rails-dom-testing (~> 2.0)
|
23
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
24
|
+
activemodel (5.0.1)
|
25
|
+
activesupport (= 5.0.1)
|
26
|
+
activerecord (5.0.1)
|
27
|
+
activemodel (= 5.0.1)
|
28
|
+
activesupport (= 5.0.1)
|
29
|
+
arel (~> 7.0)
|
30
|
+
activesupport (5.0.1)
|
31
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
32
|
+
i18n (~> 0.7)
|
33
|
+
minitest (~> 5.1)
|
34
|
+
tzinfo (~> 1.1)
|
35
|
+
arel (7.1.4)
|
36
|
+
builder (3.2.3)
|
37
|
+
byebug (9.0.6)
|
38
|
+
coderay (1.1.1)
|
39
|
+
concurrent-ruby (1.0.4)
|
40
|
+
database_cleaner (1.5.3)
|
41
|
+
diff-lcs (1.3)
|
42
|
+
erubis (2.7.0)
|
43
|
+
ffi (1.9.17)
|
44
|
+
formatador (0.2.5)
|
45
|
+
generator_spec (0.9.3)
|
46
|
+
activesupport (>= 3.0.0)
|
47
|
+
railties (>= 3.0.0)
|
48
|
+
guard (2.14.0)
|
49
|
+
formatador (>= 0.2.4)
|
50
|
+
listen (>= 2.7, < 4.0)
|
51
|
+
lumberjack (~> 1.0)
|
52
|
+
nenv (~> 0.1)
|
53
|
+
notiffany (~> 0.0)
|
54
|
+
pry (>= 0.9.12)
|
55
|
+
shellany (~> 0.0)
|
56
|
+
thor (>= 0.18.1)
|
57
|
+
guard-compat (1.2.1)
|
58
|
+
guard-rspec (4.7.3)
|
59
|
+
guard (~> 2.1)
|
60
|
+
guard-compat (~> 1.1)
|
61
|
+
rspec (>= 2.99.0, < 4.0)
|
62
|
+
i18n (0.7.0)
|
63
|
+
listen (3.1.5)
|
64
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
65
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
66
|
+
ruby_dep (~> 1.2)
|
67
|
+
loofah (2.0.3)
|
68
|
+
nokogiri (>= 1.5.9)
|
69
|
+
lumberjack (1.0.11)
|
70
|
+
method_source (0.8.2)
|
71
|
+
mini_portile2 (2.1.0)
|
72
|
+
minitest (5.10.1)
|
73
|
+
mysql2 (0.3.21)
|
74
|
+
nenv (0.3.0)
|
75
|
+
nokogiri (1.7.0.1)
|
76
|
+
mini_portile2 (~> 2.1.0)
|
77
|
+
notiffany (0.1.1)
|
78
|
+
nenv (~> 0.1)
|
79
|
+
shellany (~> 0.0)
|
80
|
+
pg (0.19.0)
|
81
|
+
pry (0.10.4)
|
82
|
+
coderay (~> 1.1.0)
|
83
|
+
method_source (~> 0.8.1)
|
84
|
+
slop (~> 3.4)
|
85
|
+
pry-byebug (3.4.2)
|
86
|
+
byebug (~> 9.0)
|
87
|
+
pry (~> 0.10)
|
88
|
+
rack (2.0.1)
|
89
|
+
rack-test (0.6.3)
|
90
|
+
rack (>= 1.0)
|
91
|
+
rails-dom-testing (2.0.2)
|
92
|
+
activesupport (>= 4.2.0, < 6.0)
|
93
|
+
nokogiri (~> 1.6)
|
94
|
+
rails-html-sanitizer (1.0.3)
|
95
|
+
loofah (~> 2.0)
|
96
|
+
railties (5.0.1)
|
97
|
+
actionpack (= 5.0.1)
|
98
|
+
activesupport (= 5.0.1)
|
99
|
+
method_source
|
100
|
+
rake (>= 0.8.7)
|
101
|
+
thor (>= 0.18.1, < 2.0)
|
102
|
+
rake (10.5.0)
|
103
|
+
rb-fsevent (0.9.8)
|
104
|
+
rb-inotify (0.9.7)
|
105
|
+
ffi (>= 0.5.0)
|
106
|
+
request_store (1.3.2)
|
107
|
+
rspec (3.5.0)
|
108
|
+
rspec-core (~> 3.5.0)
|
109
|
+
rspec-expectations (~> 3.5.0)
|
110
|
+
rspec-mocks (~> 3.5.0)
|
111
|
+
rspec-core (3.5.4)
|
112
|
+
rspec-support (~> 3.5.0)
|
113
|
+
rspec-expectations (3.5.0)
|
114
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
115
|
+
rspec-support (~> 3.5.0)
|
116
|
+
rspec-its (1.2.0)
|
117
|
+
rspec-core (>= 3.0.0)
|
118
|
+
rspec-expectations (>= 3.0.0)
|
119
|
+
rspec-mocks (3.5.0)
|
120
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
121
|
+
rspec-support (~> 3.5.0)
|
122
|
+
rspec-support (3.5.0)
|
123
|
+
ruby_dep (1.5.0)
|
124
|
+
shellany (0.0.1)
|
125
|
+
slop (3.6.0)
|
126
|
+
sqlite3 (1.3.13)
|
127
|
+
thor (0.19.4)
|
128
|
+
thread_safe (0.3.5)
|
129
|
+
tzinfo (1.2.2)
|
130
|
+
thread_safe (~> 0.1)
|
131
|
+
yard (0.9.8)
|
132
|
+
|
133
|
+
PLATFORMS
|
134
|
+
ruby
|
135
|
+
|
136
|
+
DEPENDENCIES
|
137
|
+
activerecord (>= 5.0, < 5.1)
|
138
|
+
bundler (~> 1.12)
|
139
|
+
database_cleaner (~> 1.5.3)
|
140
|
+
generator_spec (~> 0.9.3)
|
141
|
+
guard-rspec
|
142
|
+
mobility!
|
143
|
+
mysql2 (~> 0.3.10)
|
144
|
+
pg
|
145
|
+
pry-byebug
|
146
|
+
rake (~> 10.0)
|
147
|
+
rspec (~> 3.0)
|
148
|
+
rspec-its (~> 1.2.0)
|
149
|
+
sqlite3
|
150
|
+
yard
|
151
|
+
|
152
|
+
BUNDLED WITH
|
153
|
+
1.12.5
|
data/Guardfile
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
|
43
|
+
# Rails files
|
44
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
45
|
+
dsl.watch_spec_files_for(rails.app_files)
|
46
|
+
dsl.watch_spec_files_for(rails.views)
|
47
|
+
|
48
|
+
watch(rails.controllers) do |m|
|
49
|
+
[
|
50
|
+
rspec.spec.call("routing/#{m[1]}_routing"),
|
51
|
+
rspec.spec.call("controllers/#{m[1]}_controller"),
|
52
|
+
rspec.spec.call("acceptance/#{m[1]}")
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Rails config changes
|
57
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
58
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
59
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
60
|
+
|
61
|
+
# Capybara features specs
|
62
|
+
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
|
63
|
+
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
|
64
|
+
|
65
|
+
# Turnip features and steps
|
66
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
67
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
68
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
69
|
+
end
|
70
|
+
end
|
data/README.md
CHANGED
@@ -1,41 +1,631 @@
|
|
1
1
|
# Mobility
|
2
2
|
|
3
|
-
|
3
|
+
Mobility is a gem for storing and retrieving localized data through attributes
|
4
|
+
on a class. A variety of different storage strategies are supported through
|
5
|
+
pluggable, customizable "backends" implemented via a common interface.
|
4
6
|
|
5
|
-
|
7
|
+
Out of the box, Mobility supports:
|
8
|
+
|
9
|
+
- translations as localized columns on the model table (like [Traco](https://github.com/barsoom/traco))
|
10
|
+
- translations on a model-specific table (like [Globalize](https://github.com/globalize/globalize))
|
11
|
+
- translations as values on globally shared key-value tables (the default, see [below](#backend))
|
12
|
+
- translations as values of a hash serialized on a text column of the model table (like [Multilang](https://github.com/artworklv/multilang))
|
13
|
+
- translations as values of a hash stored as an hstore column on a Postgres model table (like [Trasto](https://github.com/yabawock/trasto), [Multilang-hstore](https://github.com/bithavoc/multilang-hstore), [hstore_translate](https://github.com/Leadformance/hstore_translate), etc.)
|
14
|
+
- translations as values of a hash stored as a jsonb column on a Postgres model table (like [json_translate](https://github.com/cfabianski/json_translate))
|
15
|
+
|
16
|
+
Each backend is implemented for both
|
17
|
+
[ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html) and
|
18
|
+
[Sequel](http://sequel.jeremyevans.net/) ORM, including a common interface for
|
19
|
+
[querying](#querying) the database on translated attributes using extended
|
20
|
+
scopes/datasets. Mobility is however flexible enough to support any storage
|
21
|
+
strategy, including ones not backed by a database.
|
22
|
+
|
23
|
+
All backends can optionally enable any of a set of common, ORM-independent
|
24
|
+
features, including:
|
25
|
+
|
26
|
+
- a [cache](#cache) to improve read/write performance (included by default)
|
27
|
+
- translation [fallbacks](#fallbacks), in case a translation is missing in a
|
28
|
+
given locale
|
29
|
+
- (for classes that support it) [dirty](#dirty) tracking of changed attributes
|
30
|
+
(`ActiveModel::Dirty` in Rails)
|
31
|
+
- [locale-specific accessors](#locale-accessors) for translated attributes, of
|
32
|
+
the form `<attribute>_<locale>` (similar to
|
33
|
+
[globalize-accessors](https://github.com/globalize/globalize-accessors))
|
6
34
|
|
7
35
|
## Installation
|
8
36
|
|
9
37
|
Add this line to your application's Gemfile:
|
10
38
|
|
11
39
|
```ruby
|
12
|
-
gem 'mobility'
|
40
|
+
gem 'mobility', git: "https://github.com/shioyama/mobility.git"
|
41
|
+
```
|
42
|
+
|
43
|
+
To translate attributes on a model, you must include (or extend) `Mobility`,
|
44
|
+
then call `translates` specifying the backend to use and any backend-specific
|
45
|
+
options.
|
46
|
+
|
47
|
+
### ActiveRecord (Rails)
|
48
|
+
|
49
|
+
Requirements:
|
50
|
+
- ActiveRecord >= 5.0
|
51
|
+
|
52
|
+
If using Mobility in a Rails project, you can run the generator to create an
|
53
|
+
initializer and (optionally) a migration to create shared tables for the
|
54
|
+
default key-value backend:
|
55
|
+
|
56
|
+
```
|
57
|
+
rails generate mobility:install
|
58
|
+
```
|
59
|
+
|
60
|
+
To skip the migration (if you do not plan to use the default `KeyValue`
|
61
|
+
backend), use the `--without_tables` option:
|
62
|
+
|
63
|
+
```
|
64
|
+
rails generate mobility:install --without_tables
|
65
|
+
```
|
66
|
+
|
67
|
+
The generator will create an initializer file `config/initializers/mobility.rb`
|
68
|
+
with the line:
|
69
|
+
|
70
|
+
```
|
71
|
+
Mobility.config.default_backend = :key_value
|
72
|
+
```
|
73
|
+
|
74
|
+
To set a different default backend, set `default_backend` to another value (see
|
75
|
+
possibilities below). Other configuration options can be set using the
|
76
|
+
`configure` method, see: {Mobility::Configuration} for details.
|
77
|
+
|
78
|
+
The default key-value backend, which stores attributes and their translations
|
79
|
+
as key/value pairs on shared tables, can be included in a model with the
|
80
|
+
following two lines:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class Post < ActiveRecord::Base
|
84
|
+
include Mobility
|
85
|
+
translates :title, :author, backend: :key_value, type: :string
|
86
|
+
translates :content, backend: :key_value, type: :text
|
87
|
+
end
|
13
88
|
```
|
14
89
|
|
15
|
-
|
90
|
+
You can now store translations of `title`, `author` and `content` on shared
|
91
|
+
translation tables (a string-valued translation table for the first two, and a
|
92
|
+
text-valued translation table for the last one). For more information on
|
93
|
+
backends, see [Choosing a Backend](#backend).
|
94
|
+
|
95
|
+
### Sequel
|
16
96
|
|
17
|
-
|
97
|
+
Requirements:
|
98
|
+
- Sequel >= 4.0
|
18
99
|
|
19
|
-
|
100
|
+
Essentially identical to ActiveRecord, with the exception that there is no
|
101
|
+
equivalent to a Rails generator (so you will need to create the migration for
|
102
|
+
the translation table(s) yourself, see the API docs for details).
|
20
103
|
|
21
|
-
|
104
|
+
To include translations on a model, simply call `translates`:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class Post < Sequel::Model
|
108
|
+
include Mobility
|
109
|
+
translates :title, :author, backend: :key_value, type: :string
|
110
|
+
translates :content, backend: :key_value, type: :text
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
Note that Mobility will detect the parent class and use an ORM-specific
|
115
|
+
backend, in this case the {Mobility::Backend::Sequel::KeyValue} backend.
|
22
116
|
|
23
117
|
## Usage
|
24
118
|
|
25
|
-
|
119
|
+
### Setting the Locale
|
120
|
+
|
121
|
+
Similar to [Globalize](https://github.com/globalize/globalize), Mobility has
|
122
|
+
its own `locale` which defaults to the value of `I18n.locale` but can also be
|
123
|
+
set independently with a setter:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
I18n.locale = :en
|
127
|
+
Mobility.locale #=> :en
|
128
|
+
Mobility.locale = :fr
|
129
|
+
Mobility.locale #=> :fr
|
130
|
+
I18n.locale #=> :en
|
131
|
+
```
|
132
|
+
|
133
|
+
To set the Mobility locale in a block, use {Mobility.with_locale}:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
Mobility.locale = :en
|
137
|
+
Mobility.with_locale(:ja) do
|
138
|
+
Mobility.locale #=> :ja
|
139
|
+
end
|
140
|
+
Mobility.locale #=> :en
|
141
|
+
```
|
142
|
+
|
143
|
+
### Getting and Setting Translations
|
144
|
+
|
145
|
+
Mobility defines getter, setter, and presence methods for translated attributes
|
146
|
+
on the model class. Regardless of which backend you use to store translations,
|
147
|
+
the basic interface for accessing them is the same.
|
148
|
+
|
149
|
+
Assuming we have a model `Post` as above, we can first set the locale, then
|
150
|
+
create a post with a translated attribute:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
Mobility.locale = :en
|
154
|
+
post = Post.create(title: "Mobility")
|
155
|
+
post.title
|
156
|
+
#=> "Mobility"
|
157
|
+
post.title?
|
158
|
+
#=> true
|
159
|
+
```
|
160
|
+
|
161
|
+
Attributes can similarly be written just like a normal attribute:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
post.title = "Mobility (noun): quality of being changeable, adaptable or versatile"
|
165
|
+
post.title
|
166
|
+
#=> "Mobility (noun): quality of being changeable, adaptable or versatile"
|
167
|
+
```
|
168
|
+
|
169
|
+
If you change locale, you will read/write the attribute in that locale:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
Mobility.locale = :ja
|
173
|
+
post.title
|
174
|
+
#=> nil
|
175
|
+
post.title?
|
176
|
+
#=> false
|
177
|
+
post.title = "Mobility(名詞):動きやすさ、可動性"
|
178
|
+
post.title
|
179
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
180
|
+
post.title?
|
181
|
+
#=> true
|
182
|
+
```
|
183
|
+
|
184
|
+
Internally, Mobility maps the `title` accessor method to a backend, which then
|
185
|
+
handles reading and writing of data. You can access the backend instance for a
|
186
|
+
given attribute with `<attribute>_backend`, in this case `post.title_backend`,
|
187
|
+
and read and write locale values directly to/from the backend (although this
|
188
|
+
should not generally be necessary):
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
post.title_backend.read(:ja)
|
192
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
193
|
+
post.title_backend.read(:en)
|
194
|
+
#=> "Mobility (noun): quality of being changeable, adaptable or versatile"
|
195
|
+
```
|
196
|
+
|
197
|
+
You can also access different locales by passing the locale into the getter
|
198
|
+
method in the options hash:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
post.title(locale: :ja)
|
202
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
203
|
+
post.title(locale: :en)
|
204
|
+
#=> "Mobility (noun): quality of being changeable, adaptable or versatile"
|
205
|
+
```
|
206
|
+
|
207
|
+
The translated value can be written using the backend's `write` method:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
post.title_backend.write(:en, "new title")
|
211
|
+
post.save
|
212
|
+
post.title
|
213
|
+
#=> "new title"
|
214
|
+
post.title_backend.write(:en, "Mobility (noun): quality of being changeable, adaptable or versatile")
|
215
|
+
post.save
|
216
|
+
post.title
|
217
|
+
#=> "Mobility (noun): quality of being changeable, adaptable or versatile"
|
218
|
+
```
|
219
|
+
|
220
|
+
Backends vary in how they implement reading and writing of translated
|
221
|
+
attributes. The default {Mobility::Backend::KeyValue} backend stores these translations on two
|
222
|
+
shared tables, `mobility_string_translations` and `mobility_text_translations`,
|
223
|
+
depending on the `type` of the attribute (corresponding to the type of column
|
224
|
+
used).
|
225
|
+
|
226
|
+
For more details on backend-specific options, see the documentation for each
|
227
|
+
backend ([below](#backend)).
|
228
|
+
|
229
|
+
### <a name="backend"></a>Choosing a Backend
|
230
|
+
|
231
|
+
Mobility supports six different (database) backends:
|
232
|
+
|
233
|
+
- **{Mobility::Backend::Column}**<br>
|
234
|
+
Store translations as columns on a table with locale as a postfix, of the
|
235
|
+
form `title_en`, `title_fr`, etc. for an attribute `title`.
|
236
|
+
- **{Mobility::Backend::Table}**<br>
|
237
|
+
Store translations on a model-specific table, e.g. for a model `Post` with
|
238
|
+
table `posts`, store translations on a table `post_translations`, and join
|
239
|
+
the translation table when fetching translated values.
|
240
|
+
- **{Mobility::Backend::KeyValue}**<br>
|
241
|
+
Store translations on a shared table of locale/attribute translation pairs,
|
242
|
+
associated through a polymorphic relation with multiple models.
|
243
|
+
- **{Mobility::Backend::Serialized}**<br>
|
244
|
+
Store translations as serialized YAML or JSON on a text column.
|
245
|
+
- **{Mobility::Backend::Hstore}**<br>
|
246
|
+
Store translations as values of a hash stored as a PostgreSQL hstore column.
|
247
|
+
- **{Mobility::Backend::Jsonb}**<br>
|
248
|
+
Store translations as values of a hash stored as a PostgreSQL jsonb column.
|
249
|
+
|
250
|
+
Each backend has strengths and weaknesses. If you're unsure of which backend to
|
251
|
+
use, a rule of thumb would be:
|
252
|
+
|
253
|
+
- If you're using PostgreSQL as your database, use {Mobility::Backend::Jsonb}.
|
254
|
+
- If you have a fixed, small set of locales that are not likely to increase,
|
255
|
+
and have a small number of models to translate, consider
|
256
|
+
{Mobility::Backend::Column}.
|
257
|
+
- If you have a small set of models to be translated but translation to
|
258
|
+
potentially many different languages, consider {Mobility::Backend::Table}.
|
259
|
+
- For all other cases (many locales, many translated models), or if you're just
|
260
|
+
not sure, the recommended solution is {Mobility::Backend::KeyValue} for
|
261
|
+
maximum flexibility and minimum database migrations.
|
262
|
+
|
263
|
+
|
264
|
+
### <a name="locale-accessors"></a>Locale Accessors
|
265
|
+
|
266
|
+
It can sometimes be more convenient to access translations through dedicated
|
267
|
+
locale-specific methods (for example to update multiple locales at once in a
|
268
|
+
form). For this purpose, Mobility has a `locale_accessors` option that can be
|
269
|
+
used to define such methods on a given class:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
class Post < ActiveRecord::Base
|
273
|
+
include Mobility
|
274
|
+
translates :title, locale_accessors: [:en, :ja]
|
275
|
+
end
|
276
|
+
```
|
277
|
+
|
278
|
+
(Note: The backend defaults to `key_value`, and `type` defaults to `text`, but
|
279
|
+
options described here are independent of backend so we will omit both for what
|
280
|
+
follows.)
|
281
|
+
|
282
|
+
Since we have enabled locale accessors for English and Japanese, we can access
|
283
|
+
translations for these locales with:
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
post.title_en
|
287
|
+
#=> "Mobility (noun): quality of being changeable, adaptable or versatile"
|
288
|
+
post.title_ja
|
289
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
290
|
+
post.title_en = "foo"
|
291
|
+
post.title
|
292
|
+
#=> "foo"
|
293
|
+
```
|
294
|
+
|
295
|
+
Alternatively, just using `locale_accessors: true` will enable all locales in
|
296
|
+
`I18n.available_locales`.
|
297
|
+
|
298
|
+
For more details, see: {Mobility::Attributes} (specifically, the private method
|
299
|
+
`define_locale_accessors`).
|
300
|
+
|
301
|
+
### <a name="cache"></a>Cache
|
302
|
+
|
303
|
+
The Mobility cache caches localized values that have been fetched once so they
|
304
|
+
can be quickly retrieved again, and also speeds up writes for some backends.
|
305
|
+
The cache is enabled by default and should generally only be disabled when
|
306
|
+
debugging; this can be done by passing `cache: false` to any backend.
|
307
|
+
|
308
|
+
In general, you should not need to actually see the cache, but for debugging
|
309
|
+
purposes you can access it by calling the private `cache` method on the
|
310
|
+
backend:
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
post.title_backend.send :cache
|
314
|
+
#=> #<Mobility::Backend::KeyValue::TranslationsCache:0x0056139b391b38 @cache={}>
|
315
|
+
```
|
316
|
+
|
317
|
+
For more details, see: {Mobility::Backend::Cache}.
|
318
|
+
|
319
|
+
### <a name="fallbacks"></a>Fallbacks
|
320
|
+
|
321
|
+
Mobility offers basic support for translation fallbacks (similar to gems such
|
322
|
+
as [Globalize](https://github.com/globalize/globalize) and
|
323
|
+
[Traco](https://github.com/barsoom/traco)). To enable fallbacks, pass a hash
|
324
|
+
with fallbacks for each locale as an option to the backend:
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
class Post < ActiveRecord::Base
|
328
|
+
include Mobility
|
329
|
+
translates :title, locale_accessors: [:en, :ja, :fr], fallbacks: { en: :ja, fr: :ja }
|
330
|
+
end
|
331
|
+
```
|
332
|
+
|
333
|
+
By setting fallbacks for English and French to Japanese, values will fall
|
334
|
+
through to the Japanese value if none is present for either of these locales:
|
335
|
+
|
336
|
+
```ruby
|
337
|
+
Mobility.locale = :en
|
338
|
+
post = Post.first
|
339
|
+
post.title = nil
|
340
|
+
post.save
|
341
|
+
post.title_en
|
342
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
343
|
+
post.title_ja
|
344
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
345
|
+
post.title_fr
|
346
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
347
|
+
```
|
348
|
+
|
349
|
+
You can optionally disable fallbacks to get the real value for a given locale
|
350
|
+
(for example, to check if a value in a particular locale is set or not) by
|
351
|
+
passing `fallbacks: false` to the getter method:
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
post.title(fallbacks: false)
|
355
|
+
#=> nil
|
356
|
+
post.title_fr(fallbacks: false)
|
357
|
+
#=> nil
|
358
|
+
```
|
359
|
+
|
360
|
+
(Mobility assigns the fallbacks hash to an instance of
|
361
|
+
`I18n::Locale::Fallbacks.new`.)
|
362
|
+
|
363
|
+
For more details, see: {Mobility::Backend::Fallbacks}.
|
364
|
+
|
365
|
+
### <a name="dirty"></a>Dirty Tracking
|
366
|
+
|
367
|
+
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.
|
368
|
+
|
369
|
+
Enabling dirty tracking is as simple as sending the `dirty: true` option to any
|
370
|
+
backend. The way dirty tracking works is somewhat dependent on the model class
|
371
|
+
(ActiveModel or Sequel); we will describe the ActiveModel implementation here.
|
372
|
+
|
373
|
+
First, enable dirty tracking (note that this is a persisted AR model, although
|
374
|
+
dirty tracking is not specific to AR and works for non-persisted models as well):
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
class Post < ActiveRecord::Base
|
378
|
+
include Mobility
|
379
|
+
translates :title, locale_accessors: [:en, :ja], dirty: true
|
380
|
+
end
|
381
|
+
```
|
382
|
+
|
383
|
+
Now set the attribute in both locales:
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
post.title
|
387
|
+
#=> "Mobility (noun): quality of being changeable, adaptable or versatile"
|
388
|
+
post.title = "a new title"
|
389
|
+
post.title_ja
|
390
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
391
|
+
post.title = "新しいタイトル"
|
392
|
+
```
|
393
|
+
|
394
|
+
Now you can use dirty methods as you would any other (untranslated) attribute:
|
395
|
+
|
396
|
+
```ruby
|
397
|
+
post.title_was
|
398
|
+
#=> "Mobility (noun): quality of being changeable, adaptable or versatile"
|
399
|
+
Mobility.locale = :ja
|
400
|
+
post.title_was
|
401
|
+
#=> "Mobility(名詞):動きやすさ、可動性"
|
402
|
+
post.changed
|
403
|
+
["title_en", "title_ja"]
|
404
|
+
post.save
|
405
|
+
```
|
406
|
+
|
407
|
+
You can also access `previous_changes`:
|
408
|
+
|
409
|
+
```ruby
|
410
|
+
post.previous_changes
|
411
|
+
#=>
|
412
|
+
{
|
413
|
+
"title_en" =>
|
414
|
+
[
|
415
|
+
"Mobility (noun): quality of being changeable, adaptable or versatile",
|
416
|
+
"a new title"
|
417
|
+
],
|
418
|
+
"title_ja" =>
|
419
|
+
[
|
420
|
+
"Mobility(名詞):動きやすさ、可動性",
|
421
|
+
"新しいタイトル"
|
422
|
+
]
|
423
|
+
}
|
424
|
+
```
|
425
|
+
|
426
|
+
You will notice that Mobility uses locale accessors to indicate which locale
|
427
|
+
has changed; dirty tracking is implemented this way to ensure that it is clear
|
428
|
+
what has changed in which locale, avoiding any possible ambiguity.
|
429
|
+
|
430
|
+
For more details, see: {Mobility::Backend::Dirty}.
|
431
|
+
|
432
|
+
### <a name="querying"></a>Querying
|
433
|
+
|
434
|
+
Database-backed Mobility backends also optionally support querying through
|
435
|
+
`where` and other query methods (`not` and `find_by` for ActiveRecord models,
|
436
|
+
`except` for Sequel models, etc). To query on these attributes, use the `i18n`
|
437
|
+
class method, which will return a model relation extended with
|
438
|
+
Mobility-specific query method overrides.
|
439
|
+
|
440
|
+
So assuming a model:
|
441
|
+
|
442
|
+
```ruby
|
443
|
+
class Post < ActiveRecord::Base
|
444
|
+
include Mobility
|
445
|
+
translates :title, backend: :key_value, type: :string
|
446
|
+
translates :content, backend: :key_value, type: :text
|
447
|
+
end
|
448
|
+
```
|
449
|
+
|
450
|
+
we can query for posts with title "foo" and content "bar" just as we would
|
451
|
+
query on untranslated attributes, and Mobility will convert the queries to
|
452
|
+
whatever the backend requires to actually return the correct results:
|
453
|
+
|
454
|
+
```ruby
|
455
|
+
Post.i18n.find_by(title: "foo", content: "bar")
|
456
|
+
```
|
457
|
+
|
458
|
+
results in the SQL:
|
459
|
+
|
460
|
+
```sql
|
461
|
+
SELECT "posts".* FROM "posts"
|
462
|
+
INNER JOIN "mobility_string_translations" "title_mobility_string_translations"
|
463
|
+
ON "title_mobility_string_translations"."key" = 'title'
|
464
|
+
AND "title_mobility_string_translations"."locale" = 'en'
|
465
|
+
AND "title_mobility_string_translations"."translatable_type" = 'Post'
|
466
|
+
AND "title_mobility_string_translations"."translatable_id" = "posts"."id"
|
467
|
+
INNER JOIN "mobility_text_translations" "content_mobility_text_translations"
|
468
|
+
ON "content_mobility_text_translations"."key" = 'content'
|
469
|
+
AND "content_mobility_text_translations"."locale" = 'en'
|
470
|
+
AND "content_mobility_text_translations"."translatable_type" = 'Post'
|
471
|
+
AND "content_mobility_text_translations"."translatable_id" = "posts"."id"
|
472
|
+
WHERE "content_mobility_text_translations"."value" = 'bar' AND
|
473
|
+
"title_mobility_string_translations"."value" = 'foo'
|
474
|
+
```
|
475
|
+
|
476
|
+
As can be seen in the query above, behind the scenes Mobility joins two tables,
|
477
|
+
one with string translations and one with text translations, and aliases the
|
478
|
+
joins for each attribute so as to match the particular values passed in to the
|
479
|
+
query. Details of how this is done can be found in
|
480
|
+
{Mobility::Backend::ActiveRecord::QueryMethods}.
|
481
|
+
|
482
|
+
Note that this feature is available for all backends *except* the `serialized`
|
483
|
+
backend, since serialized database values are not query-able (an
|
484
|
+
`ArgumentError` error will be raised if you try to query on attributes of this
|
485
|
+
backend).
|
486
|
+
|
487
|
+
For more details, see subclasses of
|
488
|
+
{Mobility::Backend::ActiveRecord::QueryMethods} or
|
489
|
+
{Mobility::Backend::Sequel::QueryMethods}.
|
490
|
+
|
491
|
+
## Philosophy
|
492
|
+
|
493
|
+
As its name implies, Mobility was created with a very specific design goal: to
|
494
|
+
separate the problem of translating model attributes from the constraints of
|
495
|
+
any particular translation solution, so that application designers are free to
|
496
|
+
mix, match and customize strategies to suit their needs.
|
497
|
+
|
498
|
+
To this end, Mobility backends strictly enforce the rule that *no backend
|
499
|
+
should modify a parent class in any way which would interfere with other
|
500
|
+
backends operating on the same class*. This is done using a heavy dose of
|
501
|
+
metaprogramming, details of which can be found in the [API
|
502
|
+
documentation](http://www.rubydoc.info/gems/mobility) and in the actual code.
|
503
|
+
|
504
|
+
In practice, this means that you can use different backends for different
|
505
|
+
attributes *on the same class* without any conflict, e.g. (assuming we
|
506
|
+
are using Postgres as our database):
|
507
|
+
|
508
|
+
```ruby
|
509
|
+
class Post < ActiveRecord::Base
|
510
|
+
include Mobility
|
511
|
+
translates :title, backend: :key_value, type: :string
|
512
|
+
translates :content, backend: :column, cache: false
|
513
|
+
translates :author_name, backend: :jsonb
|
514
|
+
end
|
515
|
+
```
|
516
|
+
|
517
|
+
Attributes can be set and fetched and Mobility will transparently handle
|
518
|
+
reading and writing through the respective backend: a shared
|
519
|
+
`mobility_string_translations` table for `title`, the `content_en` and
|
520
|
+
`content_ja` columns on the `posts` table for `content`, and JSON keys and
|
521
|
+
values on the jsonb `author_name` column for `author_name`.
|
522
|
+
|
523
|
+
Similarly, we can query for a particular post using the `i18n` scope without worrying about how attributes are actually stored. So this query:
|
524
|
+
|
525
|
+
```ruby
|
526
|
+
Post.i18n.where(title: "foo",
|
527
|
+
content: "bar",
|
528
|
+
author_name: "baz")
|
529
|
+
```
|
530
|
+
|
531
|
+
will result in the following SQL:
|
532
|
+
|
533
|
+
```sql
|
534
|
+
SELECT "posts".* FROM "posts"
|
535
|
+
INNER JOIN "mobility_string_translations" "title_mobility_string_translations"
|
536
|
+
ON "title_mobility_string_translations"."key" = 'title'
|
537
|
+
AND "title_mobility_string_translations"."locale" = 'en'
|
538
|
+
AND "title_mobility_string_translations"."translatable_type" = 'Post'
|
539
|
+
AND "title_mobility_string_translations"."translatable_id" = "posts"."id"
|
540
|
+
WHERE (posts.author_name @> ('{"en":"baz"}')::jsonb)
|
541
|
+
AND "posts"."content_en" = 'bar'
|
542
|
+
AND "title_mobility_string_translations"."value" = 'foo'
|
543
|
+
```
|
544
|
+
|
545
|
+
The query combines conditions specific to each backend, together fetching the
|
546
|
+
record which satisfies all of them.
|
547
|
+
|
548
|
+
Beyond the goal of making it easy to combine backends in a single class (which
|
549
|
+
admittedly is a rather specialized use-case), the flexibility Mobility enforces
|
550
|
+
makes it possible to build more complex translation-based applications without
|
551
|
+
worrying about the details of the translation storage strategy used. It also
|
552
|
+
saves effort in integrating translation storage with various other gems, since
|
553
|
+
only one integration is required rather than one for each translation gem.
|
26
554
|
|
27
555
|
## Development
|
28
556
|
|
29
|
-
|
557
|
+
### Custom Backends
|
558
|
+
|
559
|
+
Although Mobility is primarily oriented toward storing ActiveRecord model
|
560
|
+
translations, it can potentially be used to handle storing translations in
|
561
|
+
other formats, for example in the cloud through an API, or in files. In
|
562
|
+
particular, the features mentioned above (locale accessors, caching, fallbacks,
|
563
|
+
dirty tracking to some degree) are not specific to database storage.
|
564
|
+
|
565
|
+
To use a custom backend, simply pass the name of a class which includes
|
566
|
+
`Mobility::Backend` to `translates`:
|
567
|
+
|
568
|
+
```ruby
|
569
|
+
class MyBackend
|
570
|
+
include Mobility::Backend
|
571
|
+
# ...
|
572
|
+
end
|
573
|
+
|
574
|
+
class MyClass
|
575
|
+
include Mobility
|
576
|
+
translates :foo, backend: MyBackend
|
577
|
+
end
|
578
|
+
```
|
579
|
+
|
580
|
+
For details on how to define a backend class, see the {Mobility::Backend}
|
581
|
+
module and other classes defined in the [API
|
582
|
+
documentation](http://www.rubydoc.info/gems/mobility).
|
583
|
+
|
584
|
+
### Testing Backends
|
585
|
+
|
586
|
+
All included backends are tested against a suite of shared specs which ensure
|
587
|
+
they conform to the same expected behaviour. These examples can be found in:
|
30
588
|
|
31
|
-
|
589
|
+
- `spec/support/shared_examples/accessor_examples.rb` (minimal specs testing
|
590
|
+
translation setting/getting)
|
591
|
+
- `spec/support/shared_examples/querying_examples.rb` (specs for
|
592
|
+
[querying](#querying))
|
593
|
+
- `spec/support/shared_examples/serialization_examples.rb` (specialized specs
|
594
|
+
for backends which store translations as a Hash: `serialized`, `hstore` and
|
595
|
+
`jsonb` backends)
|
32
596
|
|
33
|
-
|
597
|
+
A minimal test can simply define a model class and use helpers defined in
|
598
|
+
`spec/support/helpers.rb` to run these examples, by extending either
|
599
|
+
`Helpers::ActiveRecord` or `Helpers::Sequel`:
|
34
600
|
|
35
|
-
|
601
|
+
```ruby
|
602
|
+
describe MyBackend do
|
603
|
+
extend Helpers::ActiveRecord
|
604
|
+
|
605
|
+
before do
|
606
|
+
stub_const 'MyPost', Class.new(ActiveRecord::Base)
|
607
|
+
MyPost.include Mobility
|
608
|
+
MyPost.translates :title, :content, backend: MyBackend
|
609
|
+
end
|
36
610
|
|
611
|
+
include_accessor_examples 'MyPost'
|
612
|
+
include_querying_examples 'MyPost'
|
613
|
+
# ...
|
614
|
+
end
|
615
|
+
```
|
616
|
+
|
617
|
+
Shared examples expect the model class to have translated attributes `title`
|
618
|
+
and `content`, and an untranslated boolean column `published`. These defaults
|
619
|
+
can be changed, see the shared examples for details.
|
620
|
+
|
621
|
+
Backends are also each tested against specialized specs targeted at their
|
622
|
+
particular implementations.
|
623
|
+
|
624
|
+
## More Information
|
625
|
+
|
626
|
+
- [Github repository](https://www.github.com/shioyama/mobility)
|
627
|
+
- [API documentation](http://www.rubydoc.info/gems/mobility)
|
37
628
|
|
38
629
|
## License
|
39
630
|
|
40
631
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
-
|