mobility 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|