translateable 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1059a6090c3c3293f2b2207bd0e5872e372c9b433433c0f01f14ca48e7a6cf62
4
- data.tar.gz: c03b8680e50c211713084f0a7997946a7742bd6acb579119ba7e9b95664085a2
3
+ metadata.gz: aeb41f65842e0bad160968db1710b45da49c12619b6e705526014fa99038bbf3
4
+ data.tar.gz: fddb74f188f3ecfe4d9abe94a43199f019510d8deb64fbba08706c1e449e632f
5
5
  SHA512:
6
- metadata.gz: 4fb7cad691ae0c16bf50cff559faa736cad5c5c27628239af267b86613da3b4c2fa44875a40097dbf46e531d5b5e8690f64706f44cb8f9595e2bef85e3ef893c
7
- data.tar.gz: 7ff0bc5d5c6efeed70f3f1d61c8119b8055d17d4ad652f3233a2d2e22bb52ad38d3466fb5b54330f07bff27ff09d45d4662e63ec333efc602d3f8e04df7dfc4b
6
+ metadata.gz: fdcbcbd924443fac434c66ea35a8df53928c61a4e58d83868d74c6711195457c07c4619cb324ec47d0ace9f047168a10d17f43eebbb0dc6d81934d422a7aa5b6
7
+ data.tar.gz: dc66c4efaf4ec49d590bf739b09b71bea0dc583e60709520634d2b9314d1ac4975275365570a975c8288ac9f0c4065a44e99793297444465a699a045b2244b08
@@ -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,53 @@ 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]`
141
140
  ```
142
- Now you can add/delete/update `title` attribute value in different languages via a single form.
141
+ Now you can add/delete/update the `title` attribute value in different languages via a single form.
143
142
 
144
143
  ### Migration
145
144
 
146
- Attributes must exist with `JSONB` type in a database, so create a migration:
145
+ Attributes must exist with the `JSONB` type in the database, so create a migration:
147
146
  ```ruby
148
- class AddTitleToPosts < ActiveRecord::Migration
147
+ class AddTitleToPosts < ActiveRecord::Migration[8.1]
149
148
  def change
150
149
  add_column :posts, :title, :jsonb, null: false, default: {}
151
150
  end
152
151
  end
153
152
  ```
154
153
 
155
- If you already have data and you want to migrate it to a new translateable structure, use ​a generator provided:
154
+ If you already have data and want to migrate it to the new translateable structure, use the provided generator:
156
155
  ```
157
156
  bin/rails generate translateable:migration posts title
158
157
  ```
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:
158
+ 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
159
  ```
161
160
  bin/rails generate translateable:migration posts title ru
162
161
  ```
163
- Now all existent data will be transfered into new structure with 'ru' locale.
162
+ Now all existing data will be transferred into the new structure with the 'ru' locale.
164
163
 
165
- Example (using 'en' locale):
164
+ Example (using the 'en' locale):
166
165
  ```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
166
+ -- before migration
167
+ SELECT id, title FROM posts;
168
+ id | title
169
+ ----+-------
170
+ 1 | hello
171
+ 2 | world
172
+
173
+ -- after migration
174
+ SELECT id, title FROM posts;
175
+ id | title
177
176
  ----+-----------------
178
177
  1 | {"en": "hello"}
179
178
  2 | {"en": "world"}
180
179
  ```
181
180
 
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.
181
+ 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
182
 
184
183
  ### Queries
185
184
 
186
- You'll probably want to create scopes for this kind of queries.
185
+ You'll probably want to create scopes for these kinds of queries.
187
186
 
188
187
  ```ruby
189
188
  # get posts where `title` with `en` locale is 'hello'
@@ -193,39 +192,40 @@ Post.where("title->>'en' = ?", 'hello')
193
192
  Post.where("EXISTS (SELECT 1 FROM jsonb_each_text(posts.title) j WHERE j.value = ?)", 'hola')
194
193
 
195
194
  # 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 ?)", '%прив%')
195
+ Post.where("EXISTS (SELECT 1 FROM jsonb_each_text(posts.title) j WHERE lower(j.value) LIKE ?)", '%прив%')
197
196
  ```
198
197
 
199
198
  I use this concern:
200
199
  ```ruby
201
- # app/models/concerns/jsonb_querable
202
- module JsonbQuerable
200
+ # app/models/concerns/jsonb_queryable.rb
201
+ module JsonbQueryable
203
202
  extend ActiveSupport::Concern
204
203
 
205
204
  included do
206
205
  # 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)
206
+ scope :where_jsonb_value, ->(attribute, value) {
207
+ column = connection.quote_table_name("#{table_name}.#{attribute}")
208
+ where("EXISTS (SELECT 1 FROM jsonb_each_text(#{column}) j WHERE j.value = ?)", value)
210
209
  }
211
210
 
212
- scope :where_jsonb_value_like, -> (attribute, value, case_sens = false) {
213
- ta = sanitize("#{table_name}.#{attribute}")[1..-2]
211
+ scope :where_jsonb_value_like, ->(attribute, value, case_sens = false) {
212
+ column = connection.quote_table_name("#{table_name}.#{attribute}")
213
+ pattern = "%#{sanitize_sql_like(value)}%"
214
214
  if case_sens
215
- where("EXISTS (SELECT 1 FROM jsonb_each_text(#{ta}) j WHERE j.value LIKE ?)", "%#{value}%")
215
+ where("EXISTS (SELECT 1 FROM jsonb_each_text(#{column}) j WHERE j.value LIKE ?)", pattern)
216
216
  else
217
- where("EXISTS (SELECT 1 FROM jsonb_each_text(#{ta}) j WHERE lower(j.value) LIKE lower(?))", "%#{value}%")
217
+ where("EXISTS (SELECT 1 FROM jsonb_each_text(#{column}) j WHERE lower(j.value) LIKE lower(?))", pattern)
218
218
  end
219
219
  }
220
220
  end
221
221
  end
222
222
  ```
223
223
 
224
- Refer to the [Postgres documentation](http://www.postgresql.org/docs/9.4/static/functions-json.html).
224
+ Refer to the [Postgres documentation](https://www.postgresql.org/docs/current/functions-json.html).
225
225
 
226
226
  ### Troubleshooting
227
227
 
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`.
228
+ 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
229
 
230
230
  ## TODO
231
231
 
@@ -236,12 +236,14 @@ If you're having problems with `translateable_sanity_check` due to non-existing
236
236
 
237
237
  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
238
 
239
+ 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`.
240
+
239
241
  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
242
 
241
243
  ## Contributing
242
244
 
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.
245
+ 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
246
 
245
247
  ## License
246
248
 
247
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
249
+ 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.0'.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,7 @@ 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)
70
78
  end
71
79
  end
72
80
  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.0
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: []