translateable 0.2.2 → 0.3.1
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/.github/workflows/tests.yml +10 -18
- data/README.md +57 -49
- data/gemfiles/7.0.gemfile +1 -0
- data/gemfiles/7.1.gemfile +1 -0
- data/gemfiles/7.2.gemfile +1 -0
- data/gemfiles/8.0.gemfile +1 -0
- data/gemfiles/8.1.gemfile +7 -0
- data/lib/generators/translateable/templates/migration.rb.erb +3 -3
- data/lib/translateable/version.rb +1 -1
- data/lib/translateable.rb +34 -19
- data/translateable.gemspec +3 -2
- metadata +20 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2b40a753c044fe858f9b958b677ca58bd082f5fbdcb3f0862299ce12db189333
|
|
4
|
+
data.tar.gz: c4c894a4d59f088a3b64761d09a92df27ccd9a0dba11f822da5ca323002c44e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2a313c4d934d22adda7cf0cc78edc6d7f00bb282a7b8a6b6b687780892e54a4327a60155a2917a51838a7443ffd75b7a78bd1875119c2f8ad4452dd1944f7486
|
|
7
|
+
data.tar.gz: 77e78c607e1b5b5c8dbfa7b8b3f2b7b32c734439ec1946fa98daa08672d3dcd2609c8720535200fa30d82539352eb16bfa1921312ec011a2abcdec7e0372e36b
|
data/.github/workflows/tests.yml
CHANGED
|
@@ -11,38 +11,30 @@ jobs:
|
|
|
11
11
|
image: postgres
|
|
12
12
|
env:
|
|
13
13
|
POSTGRES_PASSWORD: postgres
|
|
14
|
-
# Set health checks to wait until postgres has started
|
|
15
14
|
options: >-
|
|
16
15
|
--health-cmd pg_isready
|
|
17
16
|
--health-interval 10s
|
|
18
17
|
--health-timeout 5s
|
|
19
18
|
--health-retries 5
|
|
20
19
|
ports:
|
|
21
|
-
# Maps tcp port 5432 on service container to the host
|
|
22
20
|
- 5432:5432
|
|
23
21
|
|
|
24
22
|
strategy:
|
|
25
23
|
fail-fast: false
|
|
26
24
|
matrix:
|
|
27
|
-
ruby-version: ['3.2', '3.3', '3.4']
|
|
28
|
-
gemfile: ['7.0', '7.1', '7.2', '8.0']
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
ruby-version: ['3.2', '3.3', '3.4', '4.0']
|
|
26
|
+
gemfile: ['7.0', '7.1', '7.2', '8.0', '8.1']
|
|
27
|
+
|
|
28
|
+
env:
|
|
29
|
+
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
|
|
30
|
+
POSTGRES_HOST: localhost
|
|
31
|
+
POSTGRES_PORT: 5432
|
|
32
|
+
POSTGRES_PASSWORD: postgres
|
|
32
33
|
|
|
33
34
|
steps:
|
|
34
|
-
- uses: actions/checkout@
|
|
35
|
+
- uses: actions/checkout@v7
|
|
35
36
|
- uses: ruby/setup-ruby@v1
|
|
36
37
|
with:
|
|
37
38
|
ruby-version: ${{ matrix.ruby-version }}
|
|
38
|
-
|
|
39
|
-
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
|
40
|
-
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
|
|
39
|
+
bundler-cache: true
|
|
41
40
|
- run: bundle exec rspec
|
|
42
|
-
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
|
43
|
-
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
|
|
44
|
-
# The hostname used to communicate with the PostgreSQL service container
|
|
45
|
-
POSTGRES_HOST: localhost
|
|
46
|
-
# The default PostgreSQL port
|
|
47
|
-
POSTGRES_PORT: 5432
|
|
48
|
-
POSTGRES_PASSWORD: postgres
|
data/README.md
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
Allows you to store text data in multiple languages with your ActiveRecord models. Similar to [globalize](https://github.com/globalize/globalize), but with a few differences:
|
|
7
7
|
|
|
8
|
-
1.
|
|
9
|
-
2.
|
|
10
|
-
3. Provides easy integration with forms using nested attributes so you can create records with multiple translations in one form. Together with [nested_form_fields](https://github.com/ncri/nested_form_fields) you can dynamically add/delete/update translations without a single line of JavaScript.
|
|
8
|
+
1. Uses a single field in the table. No additional tables are required to store translated data. PostgreSQL 9.4+ is required for this (JSONB).
|
|
9
|
+
2. Provides easy integration with forms using nested attributes, so you can create records with multiple translations in one form. Together with [nested_form_fields](https://github.com/ncri/nested_form_fields) you can dynamically add/delete/update translations without a single line of JavaScript.
|
|
11
10
|
|
|
12
11
|
```ruby
|
|
13
12
|
I18n.locale = :en
|
|
@@ -22,12 +21,12 @@ I18n.locale = :en
|
|
|
22
21
|
post.title #=> hello
|
|
23
22
|
```
|
|
24
23
|
|
|
25
|
-
It adds very thin abstraction layer on top of JSONB field. All data is stored in a simple JSON structure: `{ "locale_name": "data" }`. JSONB can be indexed (this is the main reason to
|
|
24
|
+
It adds a very thin abstraction layer on top of a JSONB field. All data is stored in a simple JSON structure: `{ "locale_name": "data" }`. JSONB can be indexed (this is the main reason to use it instead of plain JSON, which is available in earlier Postgres versions).
|
|
26
25
|
|
|
27
26
|
## Requirements
|
|
28
27
|
|
|
29
28
|
- PostgreSQL >= 9.4
|
|
30
|
-
- ActiveRecord >=
|
|
29
|
+
- ActiveRecord >= 5.0 (tested with 7.0–8.1)
|
|
31
30
|
- I18n
|
|
32
31
|
|
|
33
32
|
## Installation
|
|
@@ -48,9 +47,9 @@ Or install it yourself as:
|
|
|
48
47
|
|
|
49
48
|
## Usage
|
|
50
49
|
|
|
51
|
-
Include `Translateable` into your model (or `ApplicationRecord`
|
|
50
|
+
Include `Translateable` into your model (or into `ApplicationRecord` to make it available in all models).
|
|
52
51
|
|
|
53
|
-
Call `translateable` macro with a list of attributes you want to be translateable:
|
|
52
|
+
Call the `translateable` macro with a list of attributes you want to be translateable:
|
|
54
53
|
```ruby
|
|
55
54
|
class Post < ActiveRecord::Base
|
|
56
55
|
include Translateable
|
|
@@ -59,7 +58,7 @@ class Post < ActiveRecord::Base
|
|
|
59
58
|
end
|
|
60
59
|
```
|
|
61
60
|
|
|
62
|
-
Now `title` attribute is translateable:
|
|
61
|
+
Now the `title` attribute is translateable:
|
|
63
62
|
```ruby
|
|
64
63
|
I18n.locale = :en
|
|
65
64
|
post = Post.create(title: 'hello')
|
|
@@ -81,7 +80,7 @@ You can pass multiple attributes:
|
|
|
81
80
|
translateable :title, :body
|
|
82
81
|
```
|
|
83
82
|
|
|
84
|
-
If there is no translation for
|
|
83
|
+
If there is no translation for the selected locale, then `I18n.default_locale` will be used. If there is no translation for `I18n.default_locale`, then the first available one will be used. You can override this behavior with the `strict` option; in this case you'll get `nil` if there is no translation for the selected locale:
|
|
85
84
|
```ruby
|
|
86
85
|
I18n.locale = :en
|
|
87
86
|
post = Post.create(title: 'hello')
|
|
@@ -92,7 +91,7 @@ post.title #=> hello
|
|
|
92
91
|
post.title(strict: true) #=> nil
|
|
93
92
|
```
|
|
94
93
|
|
|
95
|
-
You can assign all locales
|
|
94
|
+
You can assign data for all locales at once as a hash:
|
|
96
95
|
```ruby
|
|
97
96
|
post = Post.create(title: { en: 'hello', ru: 'привет' })
|
|
98
97
|
I18n.with_locale(:en) do
|
|
@@ -103,13 +102,13 @@ I18n.with_locale(:ru) do
|
|
|
103
102
|
end
|
|
104
103
|
```
|
|
105
104
|
|
|
106
|
-
You can easily create translated data with form using nested attributes
|
|
105
|
+
You can easily create translated data with a form using nested attributes.
|
|
107
106
|
|
|
108
|
-
For example, with [simple_form](https://github.com/
|
|
107
|
+
For example, with [simple_form](https://github.com/heartcombo/simple_form) and [nested_form_fields](https://github.com/ncri/nested_form_fields):
|
|
109
108
|
```haml
|
|
110
109
|
= simple_form_for @post do |f|
|
|
111
110
|
= f.label(:title)
|
|
112
|
-
= f.nested_fields_for Translateable.translateable_attribute_by_name(:title), class_name: '
|
|
111
|
+
= f.nested_fields_for Translateable.translateable_attribute_by_name(:title), class_name: 'Translateable::AttributeValue' do |ff|
|
|
113
112
|
= ff.input :data, label: false
|
|
114
113
|
= ff.input :locale, collection: I18n.available_locales, include_blank: false, label: false
|
|
115
114
|
= ff.remove_nested_fields_link 'Remove translation', role: 'button'
|
|
@@ -117,11 +116,11 @@ For example, with [simple_form](https://github.com/plataformatec/simple_form) an
|
|
|
117
116
|
= f.button :submit
|
|
118
117
|
```
|
|
119
118
|
|
|
120
|
-
Or with built-in `form_for` and [nested_form_fields](https://github.com/ncri/nested_form_fields):
|
|
119
|
+
Or with the built-in `form_for` and [nested_form_fields](https://github.com/ncri/nested_form_fields):
|
|
121
120
|
```haml
|
|
122
121
|
= form_for @post do |f|
|
|
123
122
|
= f.label :title
|
|
124
|
-
= f.nested_fields_for Translateable.translateable_attribute_by_name(:title), class_name: '
|
|
123
|
+
= f.nested_fields_for Translateable.translateable_attribute_by_name(:title), class_name: 'Translateable::AttributeValue' do |ff|
|
|
125
124
|
= ff.text_field :data
|
|
126
125
|
= ff.select :locale, I18n.available_locales
|
|
127
126
|
= ff.remove_nested_fields_link 'Remove translation', role: 'button'
|
|
@@ -137,53 +136,59 @@ def post_params
|
|
|
137
136
|
end
|
|
138
137
|
|
|
139
138
|
# `translateable_permitted_attributes` method provides strong_params for all translateable attributes
|
|
140
|
-
# for example with `title` attribute those will be: `title_translateable_attributes: [:locale, :data, :_destroy]`
|
|
139
|
+
# for example, with the `title` attribute those will be: `title_translateable_attributes: [:locale, :data, :_destroy]`
|
|
140
|
+
```
|
|
141
|
+
Now you can add/delete/update the `title` attribute value in different languages via a single form.
|
|
142
|
+
|
|
143
|
+
A plain field works too and edits the current locale's text, also when the form is re-rendered after a failed validation:
|
|
144
|
+
```haml
|
|
145
|
+
= form_for @post do |f|
|
|
146
|
+
= f.text_field :title
|
|
141
147
|
```
|
|
142
|
-
Now you can add/delete/update `title` attribute value in different languages via a single form.
|
|
143
148
|
|
|
144
149
|
### Migration
|
|
145
150
|
|
|
146
|
-
Attributes must exist with `JSONB` type in
|
|
151
|
+
Attributes must exist with the `JSONB` type in the database, so create a migration:
|
|
147
152
|
```ruby
|
|
148
|
-
class AddTitleToPosts < ActiveRecord::Migration
|
|
153
|
+
class AddTitleToPosts < ActiveRecord::Migration[8.1]
|
|
149
154
|
def change
|
|
150
155
|
add_column :posts, :title, :jsonb, null: false, default: {}
|
|
151
156
|
end
|
|
152
157
|
end
|
|
153
158
|
```
|
|
154
159
|
|
|
155
|
-
If you already have data
|
|
160
|
+
If you already have data and want to migrate it to the new translateable structure, use the provided generator:
|
|
156
161
|
```
|
|
157
162
|
bin/rails generate translateable:migration posts title
|
|
158
163
|
```
|
|
159
|
-
This will create a reversible migration for data in `title` field of the `posts` table. By default, the
|
|
164
|
+
This will create a reversible migration for data in the `title` field of the `posts` table. By default, the existing data will be moved into `I18n.default_locale`. If you want to use another locale, provide it as a third argument:
|
|
160
165
|
```
|
|
161
166
|
bin/rails generate translateable:migration posts title ru
|
|
162
167
|
```
|
|
163
|
-
Now all
|
|
168
|
+
Now all existing data will be transferred into the new structure with the 'ru' locale.
|
|
164
169
|
|
|
165
|
-
Example (using 'en' locale):
|
|
170
|
+
Example (using the 'en' locale):
|
|
166
171
|
```sql
|
|
167
|
-
|
|
168
|
-
SELECT id,title FROM posts;
|
|
169
|
-
id |
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
id |
|
|
172
|
+
-- before migration
|
|
173
|
+
SELECT id, title FROM posts;
|
|
174
|
+
id | title
|
|
175
|
+
----+-------
|
|
176
|
+
1 | hello
|
|
177
|
+
2 | world
|
|
178
|
+
|
|
179
|
+
-- after migration
|
|
180
|
+
SELECT id, title FROM posts;
|
|
181
|
+
id | title
|
|
177
182
|
----+-----------------
|
|
178
183
|
1 | {"en": "hello"}
|
|
179
184
|
2 | {"en": "world"}
|
|
180
185
|
```
|
|
181
186
|
|
|
182
|
-
|
|
187
|
+
If the field was indexed, the migration re-creates the index as a GIN index. If you need a different index (e.g. on a JSON path), change the migration manually.
|
|
183
188
|
|
|
184
189
|
### Queries
|
|
185
190
|
|
|
186
|
-
You'll probably want to create scopes for
|
|
191
|
+
You'll probably want to create scopes for these kinds of queries.
|
|
187
192
|
|
|
188
193
|
```ruby
|
|
189
194
|
# get posts where `title` with `en` locale is 'hello'
|
|
@@ -193,39 +198,40 @@ Post.where("title->>'en' = ?", 'hello')
|
|
|
193
198
|
Post.where("EXISTS (SELECT 1 FROM jsonb_each_text(posts.title) j WHERE j.value = ?)", 'hola')
|
|
194
199
|
|
|
195
200
|
# get posts where `title` LIKE 'прив' with any locale ignoring case
|
|
196
|
-
where("EXISTS (SELECT 1 FROM jsonb_each_text(posts.title) j WHERE lower(j.value) LIKE ?)", '%прив%')
|
|
201
|
+
Post.where("EXISTS (SELECT 1 FROM jsonb_each_text(posts.title) j WHERE lower(j.value) LIKE ?)", '%прив%')
|
|
197
202
|
```
|
|
198
203
|
|
|
199
204
|
I use this concern:
|
|
200
205
|
```ruby
|
|
201
|
-
# app/models/concerns/
|
|
202
|
-
module
|
|
206
|
+
# app/models/concerns/jsonb_queryable.rb
|
|
207
|
+
module JsonbQueryable
|
|
203
208
|
extend ActiveSupport::Concern
|
|
204
209
|
|
|
205
210
|
included do
|
|
206
211
|
# http://stackoverflow.com/questions/36250331/query-postgres-jsonb-by-value-regardless-of-keys/36251296#36251296
|
|
207
|
-
scope :where_jsonb_value, ->
|
|
208
|
-
|
|
209
|
-
where("EXISTS (SELECT 1 FROM jsonb_each_text(#{
|
|
212
|
+
scope :where_jsonb_value, ->(attribute, value) {
|
|
213
|
+
column = connection.quote_table_name("#{table_name}.#{attribute}")
|
|
214
|
+
where("EXISTS (SELECT 1 FROM jsonb_each_text(#{column}) j WHERE j.value = ?)", value)
|
|
210
215
|
}
|
|
211
216
|
|
|
212
|
-
scope :where_jsonb_value_like, ->
|
|
213
|
-
|
|
217
|
+
scope :where_jsonb_value_like, ->(attribute, value, case_sens = false) {
|
|
218
|
+
column = connection.quote_table_name("#{table_name}.#{attribute}")
|
|
219
|
+
pattern = "%#{sanitize_sql_like(value)}%"
|
|
214
220
|
if case_sens
|
|
215
|
-
where("EXISTS (SELECT 1 FROM jsonb_each_text(#{
|
|
221
|
+
where("EXISTS (SELECT 1 FROM jsonb_each_text(#{column}) j WHERE j.value LIKE ?)", pattern)
|
|
216
222
|
else
|
|
217
|
-
where("EXISTS (SELECT 1 FROM jsonb_each_text(#{
|
|
223
|
+
where("EXISTS (SELECT 1 FROM jsonb_each_text(#{column}) j WHERE lower(j.value) LIKE lower(?))", pattern)
|
|
218
224
|
end
|
|
219
225
|
}
|
|
220
226
|
end
|
|
221
227
|
end
|
|
222
228
|
```
|
|
223
229
|
|
|
224
|
-
Refer to the [Postgres documentation](
|
|
230
|
+
Refer to the [Postgres documentation](https://www.postgresql.org/docs/current/functions-json.html).
|
|
225
231
|
|
|
226
232
|
### Troubleshooting
|
|
227
233
|
|
|
228
|
-
|
|
234
|
+
When a model is loaded, `translateable` checks that each attribute has a matching column. The check is skipped if there is no active database connection or the table doesn't exist yet. If it still causes problems (e.g. a database that doesn't exist upon loading), you can disable it by setting the `DISABLE_TRANSLATEABLE_SANITY_CHECK` environment variable (any value) or by passing `sanity_checks: false`, for example `translateable :title, sanity_checks: false`.
|
|
229
235
|
|
|
230
236
|
## TODO
|
|
231
237
|
|
|
@@ -236,12 +242,14 @@ If you're having problems with `translateable_sanity_check` due to non-existing
|
|
|
236
242
|
|
|
237
243
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
238
244
|
|
|
245
|
+
Tests need a running PostgreSQL server, configured via `POSTGRES_HOST`, `POSTGRES_PORT` and `POSTGRES_PASSWORD`. To test against a specific Rails version, use one of the gemfiles, e.g. `BUNDLE_GEMFILE=gemfiles/8.1.gemfile bundle exec rspec`.
|
|
246
|
+
|
|
239
247
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
240
248
|
|
|
241
249
|
## Contributing
|
|
242
250
|
|
|
243
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/olegantonyan/translateable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](
|
|
251
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/olegantonyan/translateable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://www.contributor-covenant.org) code of conduct.
|
|
244
252
|
|
|
245
253
|
## License
|
|
246
254
|
|
|
247
|
-
The gem is available as open source under the terms of the [MIT License](
|
|
255
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/gemfiles/7.0.gemfile
CHANGED
|
@@ -2,6 +2,7 @@ source 'https://rubygems.org'
|
|
|
2
2
|
|
|
3
3
|
gem 'activerecord', '~> 7.0.0'
|
|
4
4
|
gem 'activesupport', '~> 7.0.0'
|
|
5
|
+
gem 'railties', '~> 7.0.0'
|
|
5
6
|
gem 'concurrent-ruby', '1.3.4' # https://stackoverflow.com/questions/79360526/uninitialized-constant-activesupportloggerthreadsafelevellogger-nameerror
|
|
6
7
|
|
|
7
8
|
gemspec path: '../'
|
data/gemfiles/7.1.gemfile
CHANGED
data/gemfiles/7.2.gemfile
CHANGED
data/gemfiles/8.0.gemfile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
3
|
reversible do |dir|
|
|
4
4
|
dir.up do
|
|
@@ -7,7 +7,7 @@ class MigrateTranslateable<%= table_name.capitalize + field_name.capitalize %> <
|
|
|
7
7
|
UPDATE <%= table_name %> AS m1
|
|
8
8
|
SET <%= field_name + '_t' %> = (
|
|
9
9
|
SELECT row_to_json(t) FROM (
|
|
10
|
-
SELECT <%= field_name %> AS <%= locale %> FROM <%= table_name %> AS m2 WHERE m1.id = m2.id
|
|
10
|
+
SELECT <%= field_name %> AS "<%= locale %>" FROM <%= table_name %> AS m2 WHERE m1.id = m2.id
|
|
11
11
|
) t
|
|
12
12
|
)::jsonb;
|
|
13
13
|
SQL
|
|
@@ -23,7 +23,7 @@ class MigrateTranslateable<%= table_name.capitalize + field_name.capitalize %> <
|
|
|
23
23
|
execute <<-SQL
|
|
24
24
|
UPDATE <%= table_name %> AS m1
|
|
25
25
|
SET <%= field_name + '_t' %> = (
|
|
26
|
-
SELECT <%= field_name %>->>'<%= locale %>'
|
|
26
|
+
SELECT COALESCE(<%= field_name %>->>'<%= locale %>', '') FROM <%= table_name %> AS m2 WHERE m1.id = m2.id
|
|
27
27
|
);
|
|
28
28
|
SQL
|
|
29
29
|
remove_column :<%= table_name %>, :<%= field_name %>
|
data/lib/translateable.rb
CHANGED
|
@@ -9,7 +9,13 @@ module Translateable
|
|
|
9
9
|
"#{attr}_translateable"
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
AttributeValue = Struct.new(:locale, :data)
|
|
12
|
+
AttributeValue = Struct.new(:locale, :data, keyword_init: true) do
|
|
13
|
+
def _destroy; end
|
|
14
|
+
|
|
15
|
+
def persisted?
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
end
|
|
13
19
|
|
|
14
20
|
module ClassMethods
|
|
15
21
|
def translateable(*attrs, sanity_checks: true)
|
|
@@ -17,45 +23,48 @@ module Translateable
|
|
|
17
23
|
translateable_sanity_check(attr) if sanity_checks
|
|
18
24
|
define_translateable_methods(attr)
|
|
19
25
|
end
|
|
20
|
-
|
|
26
|
+
@translateable_attributes = (@translateable_attributes || []) | attrs
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def translateable_attributes
|
|
30
|
+
inherited = superclass.respond_to?(:translateable_attributes) ? superclass.translateable_attributes : []
|
|
31
|
+
inherited | (@translateable_attributes || [])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def translateable_permitted_attributes
|
|
35
|
+
translateable_attributes.map { |attr| { "#{attr}_translateable_attributes" => %i(locale data _destroy) } }
|
|
21
36
|
end
|
|
22
37
|
|
|
23
38
|
def translateable_sanity_check(attr)
|
|
24
39
|
return if ENV['DISABLE_TRANSLATEABLE_SANITY_CHECK']
|
|
25
|
-
return unless database_connection_exists?
|
|
40
|
+
return unless database_connection_exists? && table_exists?
|
|
26
41
|
attr = attr.to_s
|
|
27
42
|
raise ArgumentError, "no such column '#{attr}' in '#{name}' model" unless column_names.include?(attr)
|
|
28
43
|
end
|
|
29
44
|
|
|
30
|
-
def define_translateable_strong_params(*attrs)
|
|
31
|
-
define_singleton_method('translateable_permitted_attributes') do
|
|
32
|
-
attrs.each_with_object([]) { |i, obj| obj << { "#{i}_translateable_attributes" => %i(locale data _destroy) } }
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
45
|
def database_connection_exists?
|
|
37
|
-
|
|
46
|
+
connection_pool.with_connection(&:active?)
|
|
38
47
|
rescue StandardError
|
|
39
48
|
false
|
|
40
49
|
end
|
|
41
50
|
|
|
42
51
|
def define_translateable_methods(attr)
|
|
43
52
|
define_method("#{attr}_fetch_translateable") do
|
|
44
|
-
|
|
45
|
-
return value.with_indifferent_access if !value.nil? && !value.empty?
|
|
46
|
-
(new_record? ? { I18n.locale => '' } : {}).with_indifferent_access
|
|
53
|
+
(self[attr] || {}).with_indifferent_access
|
|
47
54
|
end
|
|
48
55
|
|
|
49
56
|
define_method(Translateable.translateable_attribute_by_name(attr)) do
|
|
50
57
|
value = send("#{attr}_fetch_translateable")
|
|
58
|
+
value = { I18n.locale.to_s => '' } if value.empty? && new_record?
|
|
51
59
|
value.map { |k, v| AttributeValue.new(locale: k, data: v) }
|
|
52
60
|
end
|
|
53
61
|
|
|
54
62
|
define_method("#{attr}_translateable_attributes=") do |arg|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
entries = arg.respond_to?(:values) ? arg.values : arg
|
|
64
|
+
self[attr] = entries.each_with_object({}) do |entry, obj|
|
|
65
|
+
entry = entry.with_indifferent_access if entry.is_a?(Hash)
|
|
66
|
+
next if ActiveModel::Type::Boolean.new.cast(entry[:_destroy])
|
|
67
|
+
obj[entry[:locale].to_s] = entry[:data]
|
|
59
68
|
end
|
|
60
69
|
end
|
|
61
70
|
|
|
@@ -65,8 +74,14 @@ module Translateable
|
|
|
65
74
|
end
|
|
66
75
|
|
|
67
76
|
define_method("#{attr}=") do |arg|
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
self[attr] = arg.respond_to?(:to_hash) ? arg.to_hash : (self[attr] || {}).merge(I18n.locale.to_s => arg)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Rails form helpers read <attr>_before_type_cast for user-assigned values, e.g. when
|
|
81
|
+
# re-rendering a form after a failed validation. Without this they would show the raw
|
|
82
|
+
# JSONB hash instead of the current locale's text (#10).
|
|
83
|
+
define_method("#{attr}_before_type_cast") do
|
|
84
|
+
send(attr)
|
|
70
85
|
end
|
|
71
86
|
end
|
|
72
87
|
end
|
data/translateable.gemspec
CHANGED
|
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ['Oleg Antonyan']
|
|
9
9
|
spec.email = ['oleg.b.antonyan@gmail.com']
|
|
10
10
|
|
|
11
|
-
spec.summary = '
|
|
12
|
-
spec.description = "Similar to globalize, but uses PostgreSQL's JSONB to store data in a single field. No additional tables required.
|
|
11
|
+
spec.summary = 'Stores text data in multiple languages.'
|
|
12
|
+
spec.description = "Similar to globalize, but uses PostgreSQL's JSONB to store data in a single field. No additional tables required. A very thin abstraction."
|
|
13
13
|
spec.homepage = 'https://github.com/olegantonyan/translateable'
|
|
14
14
|
spec.license = 'MIT'
|
|
15
15
|
|
|
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.require_paths = %w(lib)
|
|
20
20
|
|
|
21
21
|
spec.add_development_dependency 'bundler'
|
|
22
|
+
spec.add_development_dependency 'railties'
|
|
22
23
|
spec.add_development_dependency 'rake'
|
|
23
24
|
spec.add_development_dependency 'rspec'
|
|
24
25
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: translateable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oleg Antonyan
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: bundler
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: railties
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: rake
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,7 +122,7 @@ dependencies:
|
|
|
108
122
|
- !ruby/object:Gem::Version
|
|
109
123
|
version: '0'
|
|
110
124
|
description: Similar to globalize, but uses PostgreSQL's JSONB to store data in a
|
|
111
|
-
single field. No additional tables required.
|
|
125
|
+
single field. No additional tables required. A very thin abstraction.
|
|
112
126
|
email:
|
|
113
127
|
- oleg.b.antonyan@gmail.com
|
|
114
128
|
executables: []
|
|
@@ -129,6 +143,7 @@ files:
|
|
|
129
143
|
- gemfiles/7.1.gemfile
|
|
130
144
|
- gemfiles/7.2.gemfile
|
|
131
145
|
- gemfiles/8.0.gemfile
|
|
146
|
+
- gemfiles/8.1.gemfile
|
|
132
147
|
- lib/generators/translateable/migration_generator.rb
|
|
133
148
|
- lib/generators/translateable/templates/migration.rb.erb
|
|
134
149
|
- lib/translateable.rb
|
|
@@ -152,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
152
167
|
- !ruby/object:Gem::Version
|
|
153
168
|
version: '0'
|
|
154
169
|
requirements: []
|
|
155
|
-
rubygems_version:
|
|
170
|
+
rubygems_version: 4.0.16
|
|
156
171
|
specification_version: 4
|
|
157
|
-
summary:
|
|
172
|
+
summary: Stores text data in multiple languages.
|
|
158
173
|
test_files: []
|