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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1059a6090c3c3293f2b2207bd0e5872e372c9b433433c0f01f14ca48e7a6cf62
4
- data.tar.gz: c03b8680e50c211713084f0a7997946a7742bd6acb579119ba7e9b95664085a2
3
+ metadata.gz: 2b40a753c044fe858f9b958b677ca58bd082f5fbdcb3f0862299ce12db189333
4
+ data.tar.gz: c4c894a4d59f088a3b64761d09a92df27ccd9a0dba11f822da5ca323002c44e6
5
5
  SHA512:
6
- metadata.gz: 4fb7cad691ae0c16bf50cff559faa736cad5c5c27628239af267b86613da3b4c2fa44875a40097dbf46e531d5b5e8690f64706f44cb8f9595e2bef85e3ef893c
7
- data.tar.gz: 7ff0bc5d5c6efeed70f3f1d61c8119b8055d17d4ad652f3233a2d2e22bb52ad38d3466fb5b54330f07bff27ff09d45d4662e63ec333efc602d3f8e04df7dfc4b
6
+ metadata.gz: 2a313c4d934d22adda7cf0cc78edc6d7f00bb282a7b8a6b6b687780892e54a4327a60155a2917a51838a7443ffd75b7a78bd1875119c2f8ad4452dd1944f7486
7
+ data.tar.gz: 77e78c607e1b5b5c8dbfa7b8b3f2b7b32c734439ec1946fa98daa08672d3dcd2609c8720535200fa30d82539352eb16bfa1921312ec011a2abcdec7e0372e36b
@@ -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
- exclude:
30
- - ruby-version: '3.4'
31
- gemfile: '7.0'
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@v4
35
+ - uses: actions/checkout@v7
35
36
  - uses: ruby/setup-ruby@v1
36
37
  with:
37
38
  ruby-version: ${{ matrix.ruby-version }}
38
- - run: bundle install
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. Works with Rails 5
9
- 2. Uses single field in the table. No additional tables required to store translated data. PostgreSQL 9.4 is required to do this (JSONB)
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 ​use​ it instead of just JSON, ​available​ in earlier Postgres versions).
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 >= 4.2
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` if you are on Rails 5 and want to include it into all models).
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 a selected locale, than `I18n.default_locale` will be used. If there is no translation for `I18n.default_locale`, than the first available ​one will be used. You can override this behavior with `strict` option, in this case you'll get `nil` if there is no translation for the selected locale:
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 data as a hash at once:
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/plataformatec/simple_form) and [nested_form_fields](https://github.com/ncri/nested_form_fields):
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: 'OpenStruct' do |ff|
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: 'OpenStruct' do |ff|
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 a database, so create a migration:
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​ and you want to migrate it to a new translateable structure, use ​a generator provided:
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 existent data will be moved into `I18n.default_locale`. If you want to use another locale, provide it as a third argument:
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 existent data will be transfered into new structure with 'ru' locale.
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
- # before migration
168
- SELECT id,title FROM posts;
169
- id | title
170
- ----+-----------------
171
- 1 | "hello"
172
- 2 | "world"
173
-
174
- # after migration
175
- SELECT id,title FROM posts;
176
- id | title
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
- By default, generator will create a migration with 'gin' index on translateable field. If you you need to use custom path index you have to change it manually.
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 this kind of queries.
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/jsonb_querable
202
- module JsonbQuerable
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, -> (attribute, value) {
208
- ta = sanitize("#{table_name}.#{attribute}")[1..-2]
209
- where("EXISTS (SELECT 1 FROM jsonb_each_text(#{ta}) j WHERE j.value = ?)", value)
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, -> (attribute, value, case_sens = false) {
213
- ta = sanitize("#{table_name}.#{attribute}")[1..-2]
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(#{ta}) j WHERE j.value LIKE ?)", "%#{value}%")
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(#{ta}) j WHERE lower(j.value) LIKE lower(?))", "%#{value}%")
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](http://www.postgresql.org/docs/9.4/static/functions-json.html).
230
+ Refer to the [Postgres documentation](https://www.postgresql.org/docs/current/functions-json.html).
225
231
 
226
232
  ### Troubleshooting
227
233
 
228
- If you're having problems with `translateable_sanity_check` due to non-existing database upon loading, you can disable those checks vie environment variable `DISABLE_TRANSLATEABLE_SANITY_CHECK=true` or by specifying `sanity_checks: false`, for example `translateable :title, sanity_checks: false`.
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](http://contributor-covenant.org) code of conduct.
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](http://opensource.org/licenses/MIT).
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
@@ -2,5 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  gem 'activerecord', '~> 7.1.0'
4
4
  gem 'activesupport', '~> 7.1.0'
5
+ gem 'railties', '~> 7.1.0'
5
6
 
6
7
  gemspec path: '../'
data/gemfiles/7.2.gemfile CHANGED
@@ -2,5 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  gem 'activerecord', '~> 7.2.0'
4
4
  gem 'activesupport', '~> 7.2.0'
5
+ gem 'railties', '~> 7.2.0'
5
6
 
6
7
  gemspec path: '../'
data/gemfiles/8.0.gemfile CHANGED
@@ -2,5 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  gem 'activerecord', '~> 8.0.0'
4
4
  gem 'activesupport', '~> 8.0.0'
5
+ gem 'railties', '~> 8.0.0'
5
6
 
6
7
  gemspec path: '../'
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~> 8.1.0'
4
+ gem 'activesupport', '~> 8.1.0'
5
+ gem 'railties', '~> 8.1.0'
6
+
7
+ gemspec path: '../'
@@ -1,4 +1,4 @@
1
- class MigrateTranslateable<%= table_name.capitalize + field_name.capitalize %> < ActiveRecord::Migration<%= migration_version %>
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 %>' AS <%= field_name %> FROM <%= table_name %> AS m2 WHERE m1.id = m2.id
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 %>
@@ -1,3 +1,3 @@
1
1
  module Translateable
2
- VERSION = '0.2.2'.freeze
2
+ VERSION = '0.3.1'.freeze
3
3
  end
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
- define_translateable_strong_params(*attrs)
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
- ActiveRecord::Base.connection_pool.with_connection(&:active?)
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
- value = self[attr]
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
- self[attr] = arg.each_with_object({}) do |i, obj|
56
- hash = i.second
57
- next if hash[:_destroy]
58
- obj[hash[:locale]] = hash[:data]
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
- value = arg.is_a?(Hash) ? arg : (self[attr] || {}).merge(I18n.locale => arg)
69
- self[attr] = value
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
@@ -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 = 'Allows to store text data in different languages.'
12
- spec.description = "Similar to globalize, but uses PostgreSQL's JSONB to store data in a single field. No additional tables required. Very thin abstraction"
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.2.2
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: 2025-04-01 00:00:00.000000000 Z
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. Very thin abstraction
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: 3.6.2
170
+ rubygems_version: 4.0.16
156
171
  specification_version: 4
157
- summary: Allows to store text data in different languages.
172
+ summary: Stores text data in multiple languages.
158
173
  test_files: []