attr_translate 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d9a44a5c74b80f11fc6c36cc4153bec6f019bb3
4
+ data.tar.gz: 4716ef9f800a4786ed938a84432ad1c729aba82e
5
+ SHA512:
6
+ metadata.gz: c3a9a0bb4c6b23f8e8571de91eeeb416f5df2b059b5147f617d94f8d0a07cb113b007d78853fbd0a268d51f1ca1a34b4e74e9d82974df639a6b9b3352c1c9d19
7
+ data.tar.gz: daf05ddcc2a0d73f6e7161b851b9e071a2f88a982cffeb3e5cd81dc9ce401401dd4c4f9be13468763bcde5cbaab12f3b686fbb31be35d62c2011b73ecb491d24
@@ -0,0 +1,4 @@
1
+ # Change Log
2
+
3
+ ## v1.0.0
4
+ - Initial release.
@@ -0,0 +1,83 @@
1
+ # AttrTranslate
2
+
3
+ Rails concern for ActiveRecord attribute translation using PostgreSQL's JSONB datatype.
4
+
5
+ AttrTranslate is extracted from the Brightcommerce platform and is used in a number of other software projects.
6
+
7
+ ## Installation
8
+
9
+ To install add the line to your Gemfile:
10
+
11
+ ``` ruby
12
+ gem 'attr_translate'
13
+ ```
14
+
15
+ And `bundle install`.
16
+
17
+ ## How To Use
18
+
19
+ To add AttrTranslate to a model, include the concern and class method:
20
+
21
+ ``` ruby
22
+ class Post < ActiveRecord::Base
23
+ include AttrTranslate
24
+
25
+ attr_translate :title, :body
26
+ end
27
+ ```
28
+
29
+ For convenience the `attr_translate` class method is aliased internally as `attr_translates`.
30
+
31
+ To autoload AttrTranslate for all models, add the following to an initializer:
32
+
33
+ ``` ruby
34
+ require 'attr_translate/active_record'
35
+ ```
36
+
37
+ You then don't need to `include AttrTranslate` in any model, but you still need to add the `attr_translate` class method.
38
+
39
+ ### Setup
40
+
41
+ Each attribute that will have translations will need to be setup appropriately in your model's migration. Append `_translations` to the column name, and set the column type to `:jsonb`. To aid in search, setup an index using the `gin` index type. Each translation will be stored using the locale as a key in a hash and converted to JSON.
42
+
43
+ ``` ruby
44
+ class CreatePosts < ActiveRecord::Migration[5.2]
45
+ def change
46
+ create_table :posts do |table|
47
+ table.column :title_translations, :jsonb, default: {}, index: {using: 'gin'}
48
+ table.column :body_translations, :jsonb, default: {}, index: {using: 'gin'}
49
+ table.timestamps
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
55
+ ## Dependencies
56
+
57
+ AttrTranslate gem has the following runtime dependencies:
58
+ - activerecord >= 5.1.4
59
+ - activesupport >= 5.1.4
60
+
61
+ ## Compatibility
62
+
63
+ Tested with MRI 2.4.2 against Rails 5.2.2.
64
+
65
+ ## Contributing
66
+
67
+ 1. Fork it
68
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
69
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
70
+ 4. Push to the branch (`git push origin my-new-feature`)
71
+ 5. Create new Pull Request
72
+
73
+ ## Credit
74
+
75
+ This gem was written and is maintained by [Jurgen Jocubeit](https://github.com/JurgenJocubeit), CEO and President Brightcommerce, Inc.
76
+
77
+ ## License
78
+
79
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
80
+
81
+ ## Copyright
82
+
83
+ Copyright 2018 Brightcommerce, Inc.
@@ -0,0 +1,2 @@
1
+ require 'attr_translate/attr_translate'
2
+ require 'attr_translate/version'
@@ -0,0 +1 @@
1
+ ActiveRecord::Base.send :include, AttrTranslate
@@ -0,0 +1,145 @@
1
+ require 'active_support/concern'
2
+ require_relative './translation_attribute'
3
+
4
+ module AttrTranslate
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def attr_translate(*attrs)
9
+ class_attribute :translated_attribute_names, :permitted_translated_attributes
10
+
11
+ self.translated_attribute_names = attrs
12
+ self.permitted_translated_attributes = [
13
+ *self.ancestors
14
+ .select { |klass| klass.respond_to?(:permitted_translated_attributes) }
15
+ .map(&:permitted_translated_attributes),
16
+ *attrs.product(I18n.available_locales)
17
+ .map { |attribute, locale| :"#{attribute}_#{locale}" }
18
+ ].flatten.compact
19
+
20
+ attrs.each do |attr_name|
21
+ define_method attr_name do |**params|
22
+ read_json_translation(attr_name, params)
23
+ end
24
+
25
+ define_method "#{attr_name}=" do |value|
26
+ write_json_translation(attr_name, value)
27
+ end
28
+
29
+ define_singleton_method "with_#{attr_name}_translation" do |value, locale = I18n.locale|
30
+ quoted_translation_store = connection.quote_column_name("#{attr_name}_translations")
31
+ translation_hash = { "#{locale}" => value }
32
+ where("#{quoted_translation_store} @> :translation::jsonb", translation: translation_hash.to_json)
33
+ end
34
+ end
35
+
36
+ send(:prepend, TranslationAttribute)
37
+ end
38
+ alias_method :attr_translates, :attr_translate
39
+
40
+ def translates?
41
+ true
42
+ end
43
+
44
+ end
45
+
46
+ def disable_fallback
47
+ toggle_fallback(false)
48
+ end
49
+
50
+ def enable_fallback
51
+ toggle_fallback(true)
52
+ end
53
+
54
+ protected
55
+
56
+ attr_reader :enabled_fallback
57
+
58
+ def json_translate_fallback_locales(locale)
59
+ return locale if enabled_fallback == false || !::I18n.respond_to?(:fallbacks)
60
+ ::I18n.fallbacks[locale]
61
+ end
62
+
63
+ def read_json_translation(attr_name, locale = I18n.locale, **params)
64
+ translations = public_send("#{attr_name}_translations") || {}
65
+
66
+ available = Array(json_translate_fallback_locales(locale)).detect do |available_locale|
67
+ translations[available_locale.to_s].present?
68
+ end
69
+
70
+ translation = translations[available.to_s]
71
+ # Rescue from MissingInterpolationArgument
72
+ # so the default behaviour doesn't change.
73
+ begin
74
+ ::I18n.interpolate(translation, params) if translation
75
+ rescue ::I18n::MissingInterpolationArgument
76
+ translation
77
+ end
78
+ end
79
+
80
+ def write_json_translation(attr_name, value, locale = I18n.locale)
81
+ translation_store = "#{attr_name}_translations"
82
+ translations = public_send(translation_store) || {}
83
+ public_send("#{translation_store}_will_change!") unless translations[locale.to_s] == value
84
+ translations[locale.to_s] = value
85
+ public_send("#{translation_store}=", translations)
86
+ value
87
+ end
88
+
89
+ def respond_to_with_translates?(symbol, include_all = false)
90
+ return true if parse_translated_attribute_accessor(symbol)
91
+ respond_to_without_translates?(symbol, include_all)
92
+ end
93
+
94
+ def method_missing_with_translates(method_name, *args)
95
+ translated_attr_name, locale, assigning = parse_translated_attribute_accessor(method_name)
96
+
97
+ return method_missing_without_translates(method_name, *args) unless translated_attr_name
98
+
99
+ if assigning
100
+ write_json_translation(translated_attr_name, args.first, locale)
101
+ else
102
+ read_json_translation(translated_attr_name, locale)
103
+ end
104
+ end
105
+
106
+ # Internal: Parse a translated convenience accessor name.
107
+ #
108
+ # method_name - The accessor name.
109
+ #
110
+ # Examples
111
+ #
112
+ # parse_translated_attribute_accessor("title_en=")
113
+ # # => [:title, :en, true]
114
+ #
115
+ # parse_translated_attribute_accessor("title_fr")
116
+ # # => [:title, :fr, false]
117
+ #
118
+ # Returns the attribute name Symbol, locale Symbol, and a Boolean
119
+ # indicating whether or not the caller is attempting to assign a value.
120
+ def parse_translated_attribute_accessor(method_name)
121
+ return unless /\A(?<attribute>[a-z0-9_]+)_(?<locale>[a-z]{2})(?<assignment>=?)\z/ =~ method_name
122
+
123
+ translated_attr_name = attribute.to_sym
124
+ return unless translated_attribute_names.include?(translated_attr_name)
125
+
126
+ locale = locale.to_sym
127
+ assigning = assignment.present?
128
+
129
+ [translated_attr_name, locale, assigning]
130
+ end
131
+
132
+ def toggle_fallback(enabled)
133
+ if block_given?
134
+ old_value = @enabled_fallback
135
+ begin
136
+ @enabled_fallback = enabled
137
+ yield
138
+ ensure
139
+ @enabled_fallback = old_value
140
+ end
141
+ else
142
+ @enabled_fallback = enabled
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,18 @@
1
+ module TranslationAttribute
2
+ def respond_to?(symbol, include_all = false)
3
+ return true if parse_translated_attribute_accessor(symbol)
4
+ super(symbol, include_all)
5
+ end
6
+
7
+ def method_missing(method_name, *args, **params)
8
+ translated_attr_name, locale, assigning = parse_translated_attribute_accessor(method_name)
9
+
10
+ return super(method_name, *args, **params) unless translated_attr_name
11
+
12
+ if assigning
13
+ write_json_translation(translated_attr_name, args.first, locale)
14
+ else
15
+ read_json_translation(translated_attr_name, locale, **params)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module AttrTranslate
2
+ module Version
3
+ Major = 1
4
+ Minor = 0
5
+ Revision = 0
6
+ Prerelease = nil
7
+ Compact = [Major, Minor, Revision, Prerelease].compact.join('.')
8
+ Summary = "AttrTranslate v#{Compact}"
9
+ Description = "Provides scopes for finding published, unpublished and returning recent or upcoming items."
10
+ Author = "Jurgen Jocubeit"
11
+ Email = "support@brightcommerce.com"
12
+ Homepage = "https://github.com/brightcommerce/attr_translate"
13
+ Metadata = {'copyright' => 'Copyright 2018 Brightcommerce, Inc. All Rights Reserved.'}
14
+ License = "MIT"
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attr_translate
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jurgen Jocubeit
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-12-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.1.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.1.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.1.4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 5.1.4
41
+ description: Provides scopes for finding published, unpublished and returning recent
42
+ or upcoming items.
43
+ email: support@brightcommerce.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - README.md
50
+ - lib/attr_translate.rb
51
+ - lib/attr_translate/active_record.rb
52
+ - lib/attr_translate/attr_translate.rb
53
+ - lib/attr_translate/translation_attribute.rb
54
+ - lib/attr_translate/version.rb
55
+ homepage: https://github.com/brightcommerce/attr_translate
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ copyright: Copyright 2018 Brightcommerce, Inc. All Rights Reserved.
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '2.3'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.6.13
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: AttrTranslate v1.0.0
80
+ test_files: []