nd-enum 0.1.0 → 0.2.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: 14980decd9ccdc766c2e27fc1c224a4dc94aee5bce41069fa574a58abe29f60f
4
- data.tar.gz: 908d743ac4486cd835370ec06034661c15fc1b30dc4cf196cf02bdeb03f234ea
3
+ metadata.gz: 1319016da965b32314532d641038f67316c07087b03f8a0a195786d783307c08
4
+ data.tar.gz: e88397852614473313ba633c1d3029de0cad1466b9ddfa0787b54e5cc7133eb8
5
5
  SHA512:
6
- metadata.gz: f02d420631940aec368decfa880ba8f956b7675a40d2ce9a5f7704b838c912f62056b271ad53afb9584c6e9539f17b89371b3992f785d123fc6c4d1f16261417
7
- data.tar.gz: 6f194a9f4b8396a46b8198af254648d7bf50c5291245e42afbbfdd06729dc31d621b3b602015a1c338809fc539cc15b62b1ecdbae6466e50691b93b031c370f1
6
+ metadata.gz: 65596f68728490a34c575fac8191438d85b4490268a5938c725f5ed7c0fbe3e0616679b332971dd8991f0989c34bd41c51b89bdaf1153f822f6446a0714613fc
7
+ data.tar.gz: c583f3f1e8a655bcf9ba604ef3851cbb27dba1acc7743ea5c158a763c33792e3cd2dc826659b7e53c1ae3380ac045aef60e53e6fd1bff3b9d8e83b315bd37b5f
data/.rspec ADDED
@@ -0,0 +1,6 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
4
+ --require mocha
5
+ --require active_record
6
+ --require pry
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.2.0] - 2022-08-10
4
+
5
+ - Add initializer
6
+ - Validate translations
7
+
8
+ ## [0.1.2] - 2022-08-08
9
+
10
+ - Add specs
11
+
12
+ ## [0.1.1] - 2022-08-07
13
+
14
+ - Fix bugs
15
+
16
+ ## [0.1.0] - 2022-08-07
17
+
18
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in nd-enum.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+ gem 'rspec', '~> 3.0'
10
+ gem 'mocha', '~> 1.13'
11
+ gem 'pry', '~> 0.13'
12
+ gem 'guard', '~> 2.18'
13
+ gem 'guard-rspec', '~> 4.7', require: false
data/Gemfile.lock ADDED
@@ -0,0 +1,91 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nd-enum (0.2.0)
5
+ activerecord (>= 6.0.0)
6
+ activesupport (>= 6.0.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (7.0.3.1)
12
+ activesupport (= 7.0.3.1)
13
+ activerecord (7.0.3.1)
14
+ activemodel (= 7.0.3.1)
15
+ activesupport (= 7.0.3.1)
16
+ activesupport (7.0.3.1)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ coderay (1.1.3)
22
+ concurrent-ruby (1.1.10)
23
+ diff-lcs (1.5.0)
24
+ ffi (1.15.5)
25
+ formatador (1.1.0)
26
+ guard (2.18.0)
27
+ formatador (>= 0.2.4)
28
+ listen (>= 2.7, < 4.0)
29
+ lumberjack (>= 1.0.12, < 2.0)
30
+ nenv (~> 0.1)
31
+ notiffany (~> 0.0)
32
+ pry (>= 0.13.0)
33
+ shellany (~> 0.0)
34
+ thor (>= 0.18.1)
35
+ guard-compat (1.2.1)
36
+ guard-rspec (4.7.3)
37
+ guard (~> 2.1)
38
+ guard-compat (~> 1.1)
39
+ rspec (>= 2.99.0, < 4.0)
40
+ i18n (1.12.0)
41
+ concurrent-ruby (~> 1.0)
42
+ listen (3.7.1)
43
+ rb-fsevent (~> 0.10, >= 0.10.3)
44
+ rb-inotify (~> 0.9, >= 0.9.10)
45
+ lumberjack (1.2.8)
46
+ method_source (1.0.0)
47
+ minitest (5.16.2)
48
+ mocha (1.14.0)
49
+ nenv (0.3.0)
50
+ notiffany (0.1.3)
51
+ nenv (~> 0.1)
52
+ shellany (~> 0.0)
53
+ pry (0.14.1)
54
+ coderay (~> 1.1)
55
+ method_source (~> 1.0)
56
+ rake (13.0.6)
57
+ rb-fsevent (0.11.1)
58
+ rb-inotify (0.10.1)
59
+ ffi (~> 1.0)
60
+ rspec (3.11.0)
61
+ rspec-core (~> 3.11.0)
62
+ rspec-expectations (~> 3.11.0)
63
+ rspec-mocks (~> 3.11.0)
64
+ rspec-core (3.11.0)
65
+ rspec-support (~> 3.11.0)
66
+ rspec-expectations (3.11.0)
67
+ diff-lcs (>= 1.2.0, < 2.0)
68
+ rspec-support (~> 3.11.0)
69
+ rspec-mocks (3.11.1)
70
+ diff-lcs (>= 1.2.0, < 2.0)
71
+ rspec-support (~> 3.11.0)
72
+ rspec-support (3.11.0)
73
+ shellany (0.0.1)
74
+ thor (1.2.1)
75
+ tzinfo (2.0.5)
76
+ concurrent-ruby (~> 1.0)
77
+
78
+ PLATFORMS
79
+ x86_64-darwin-21
80
+
81
+ DEPENDENCIES
82
+ guard (~> 2.18)
83
+ guard-rspec (~> 4.7)
84
+ mocha (~> 1.13)
85
+ nd-enum!
86
+ pry (~> 0.13)
87
+ rake (~> 13.0)
88
+ rspec (~> 3.0)
89
+
90
+ BUNDLED WITH
91
+ 2.3.12
data/Guardfile ADDED
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.call("routing/#{m[1]}_routing"),
51
+ rspec.spec.call("controllers/#{m[1]}_controller"),
52
+ rspec.spec.call("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Romain Clavel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # ND::Enum
2
+
3
+ This gem allows you to create and use enums easily and quickly in your Rails project.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add nd-enum
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install nd-enum
14
+
15
+ ## Usage
16
+
17
+ - [Basic usage](#basic-usage)
18
+ - [Configuration](#configuration)
19
+ - [I18n](#i18n)
20
+ - [Validate translations presence](#validate-translations-presence)
21
+ - [Enforce translations presence](#enforce-translations-presence)
22
+ - [ActiveRecord Enum](#activerecord-enum)
23
+
24
+ ### Basic usage
25
+
26
+ Define your enum in an ActiveRecord model:
27
+
28
+ ```ruby
29
+ class User < ApplicationRecord
30
+ nd_enum(role: %i(user admin))
31
+ end
32
+ ```
33
+
34
+ It creates a module for your enum that contains one constant per enum value. Say goodbye to [magic strings](https://en.wikipedia.org/wiki/Magic_string)!
35
+
36
+ In our example, the module is `User::Role`, and the constants are `User::Role::USER` and `User::Role::ADMIN`.
37
+
38
+ ```ruby
39
+ irb(main)> User::Role::USER
40
+ => "user"
41
+
42
+ irb(main)> User::Role::ADMIN
43
+ => "admin"
44
+
45
+ irb(main)> User::Role.all
46
+ => ["user", "admin"]
47
+
48
+ irb(main)> User::Role.length
49
+ => 2
50
+
51
+ irb(main)> User::Role[1]
52
+ => "admin"
53
+
54
+ irb(main)> User::Role[:user]
55
+ => "user"
56
+
57
+ irb(main)> User::Role.include?('foobar')
58
+ => false
59
+
60
+ irb(main)> User::Role.include?('user')
61
+ => true
62
+ ```
63
+
64
+ ND::Enum inheritates from [`Enumerable`](https://ruby-doc.org/core-3.1.2/Enumerable.html), so it is possible to use all `Enumerable` methods on the enum module: `each`, `map`, `find`...
65
+
66
+ ### Configuration
67
+
68
+ Fine-tune `ND::Enum` behaviour by creating an initializer (for example: `config/initializers/nd_enum.rb`).
69
+ The values shown in the example below are the default values.
70
+
71
+ ```ruby
72
+ ND::Enum.configure do |c|
73
+ c.default_i18n_scope = :base
74
+ c.default_i18n_validation_mode = :ignore # Allowed values: ignore, log, enforce
75
+ end
76
+ ```
77
+
78
+ ### I18n
79
+
80
+ Allows to translate your enum values.
81
+ Add to your locale files:
82
+
83
+ ```yaml
84
+ en:
85
+ users: # Model.table_name
86
+ role: # attribute
87
+ base: # default scope
88
+ user: User
89
+ admin: Admin
90
+ foobar: # custom scope
91
+ user: The user
92
+ admin: The admin
93
+ ```
94
+
95
+ Then call `t` (or `translate`) method:
96
+
97
+ ```ruby
98
+ irb(main)> User::Role.t(:user) # Or `translate` method (alias)
99
+ => "translation missing: en.users.role.base.user"
100
+ ```
101
+
102
+ Use a different scope to have several translations for a single value, depending on context:
103
+
104
+ ```ruby
105
+ irb(main)> User::Role.t(:user, :foobar)
106
+ => "translation missing: en.users.role.foobar.user"
107
+ ```
108
+
109
+ Please note that the default scope (`base`) can be configured using the `default_i18n_scope` initializer option.
110
+
111
+ #### Validate translations presence
112
+
113
+ Validate that your enum are translated. By default, this feature is disabled.
114
+
115
+ ```ruby
116
+ class User < ApplicationRecord
117
+ nd_enum(role: %i(user admin), i18n: { mode: :log })
118
+ end
119
+ ```
120
+
121
+ It will log the missing translations for each scope & locale. For example:
122
+
123
+ ```
124
+ I, [2022-08-10T21:17:53.931669 #67401] INFO -- : ND::Enum: User#role scopes=[:base, :short]
125
+ I, [2022-08-10T21:17:53.931669 #67401] INFO -- : ND::Enum: User#role locale=en missing_keys=[]
126
+ I, [2022-08-10T21:17:53.931669 #67401] INFO -- : ND::Enum: User#role locale=nl missing_keys=["users.role.base.user", "users.role.short.user"]
127
+ ```
128
+
129
+ This mode can be used as default with the `c.default_i18n_validation_mode = :log` initializer option.
130
+
131
+ #### Enforce translations presence
132
+
133
+ Raise an exception when some translations are missing.
134
+
135
+ ```ruby
136
+ class User < ApplicationRecord
137
+ nd_enum(role: %i(user admin), i18n: { mode: :enforce })
138
+ end
139
+ ```
140
+
141
+ ```
142
+ (irb):1:in `<main>': One or several translations are missing (ND::Enum::MissingTranslationError)
143
+ from bin/console:15:in `<main>'
144
+ ```
145
+
146
+ This mode can be used as default with the `c.default_i18n_validation_mode = :enforce` initializer option.
147
+
148
+ ### `ActiveRecord` Enum
149
+
150
+ Add a wrapper to [`ActiveRecord` Enum](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html) by specifying the `db: true` option.
151
+
152
+ ```ruby
153
+ class User < ApplicationRecord
154
+ nd_enum(role: %i(user admin), db: true)
155
+ end
156
+
157
+ # It does exactly the same thing than below, but shorter:
158
+
159
+ class User < ApplicationRecord
160
+ nd_enum(role: %i(user admin))
161
+ enum(role: User::Role.to_h) # Or `enum(role: { user: 'user', admin: 'admin '})`
162
+ end
163
+ ```
164
+
165
+ It allows to use these methods:
166
+
167
+ ```ruby
168
+ user.admin!
169
+ user.admin? # => true
170
+ user.role # => "admin"
171
+ ```
172
+
173
+ And these scopes:
174
+
175
+ ```ruby
176
+ User.admin
177
+ User.not_admin
178
+
179
+ User.user
180
+ User.not_user
181
+
182
+ # ...
183
+ ```
184
+
185
+ Disable scope definition by setting `scopes: false` to your enum:
186
+
187
+ ```ruby
188
+ class User < ApplicationRecord
189
+ nd_enum(role: %i(user admin), db: { scopes: false })
190
+ end
191
+ ```
192
+
193
+ Set the default enum:
194
+
195
+ ```ruby
196
+ class User < ApplicationRecord
197
+ nd_enum(role: %i(user admin), db: { default: :admin })
198
+ end
199
+ ```
200
+
201
+ Add a `prefix` or `suffix` option when you need to define multiple enums with same values. If the passed value is true, the methods are prefixed/suffixed with the name of the enum. It is also possible to supply a custom value:
202
+
203
+ ```ruby
204
+ class User < ApplicationRecord
205
+ nd_enum(role: %i(user admin), db: { prefix: true })
206
+
207
+ # Scopes: `User.role_admin`, `User.role_user` ...
208
+ # Methods: `User.role_admin!`, `User.role_user!` ...
209
+
210
+ nd_enum(role: %i(user admin), db: { suffix: true })
211
+
212
+ # Scopes: `User.admin_role`, `User.user_role` ...
213
+ # Methods: `User.admin_role!`, `User.user_role!` ...
214
+
215
+ nd_enum(role: %i(user admin), db: { prefix: 'foobar' })
216
+
217
+ # Scopes: `User.foobar_admin`, `User.foobar_user` ...
218
+ # Methods: `User.foobar_admin!`, `User.foobar_user!` ...
219
+ end
220
+ ```
221
+
222
+ ## Development
223
+
224
+ 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.
225
+
226
+ Guard is also installed: `bundle exec guard`.
227
+
228
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, run `gem bump` (or manually 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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
229
+
230
+ ## Contributing
231
+
232
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rclavel/nd-enum.
233
+
234
+ ## License
235
+
236
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'active_support'
5
+
6
+ module ND::Enum::Base
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class << self
11
+ include Enumerable
12
+ extend Forwardable
13
+
14
+ def_delegators :all, :size, :length, :[], :empty?, :last, :index
15
+
16
+ def each(&block)
17
+ all.each(&block)
18
+ end
19
+
20
+ def to_h
21
+ all.map { |value| [value.to_sym, value] }.to_h
22
+ end
23
+
24
+ def [](value)
25
+ value.is_a?(Integer) ? all[value] : to_h[value.to_sym]
26
+ end
27
+
28
+ def t(value, scope = nil)
29
+ scope ||= configuration.default_i18n_scope
30
+ ::I18n.t(value, scope: "#{model.table_name}.#{attribute}.#{scope}")
31
+ end
32
+ alias_method :translate, :t
33
+
34
+ private
35
+
36
+ def configuration
37
+ ND::Enum.configuration
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ND::Enum::Configuration
4
+ Configuration = Struct.new(:default_i18n_validation_mode, :default_i18n_scope)
5
+ DEFAULT_CONFIGURATION = {
6
+ default_i18n_validation_mode: :ignore,
7
+ default_i18n_scope: :base,
8
+ }
9
+
10
+ def configuration
11
+ @_configuration ||= Configuration.new(*DEFAULT_CONFIGURATION.values_at(*Configuration.members))
12
+ end
13
+
14
+ def configure
15
+ yield(configuration)
16
+ end
17
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ND::Enum::I18n
4
+ class << self
5
+ # TODO: Transform into a nd_enum
6
+ module Mode
7
+ LOG = 'log'
8
+ ENFORCE = 'enforce'
9
+ IGNORE = 'ignore'
10
+
11
+ def self.all
12
+ [LOG, ENFORCE, IGNORE]
13
+ end
14
+ end
15
+
16
+ def validate!(options)
17
+ mode = get_mode_from_options(options)
18
+ return if mode == Mode::IGNORE
19
+
20
+ i18n_scope = build_i18n_scope(options)
21
+ scopes = get_scopes_from_translations(i18n_scope)
22
+ missing_keys_by_locale = get_missing_keys_by_locale(options, i18n_scope, scopes)
23
+
24
+ log_missing_keys(options, scopes, missing_keys_by_locale)
25
+
26
+ if mode == Mode::ENFORCE && missing_keys_by_locale.values.any?(&:present?)
27
+ raise ND::Enum::MissingTranslationError
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def get_mode_from_options(options)
34
+ case options.dig(:i18n, :validate)
35
+ when 'log', :log then Mode::LOG
36
+ when 'enforce', :enforce then Mode::ENFORCE
37
+ else
38
+ default_mode = configuration.default_i18n_validation_mode&.to_s
39
+ Mode.all.include?(default_mode) ? default_mode : Mode::IGNORE
40
+ end
41
+ end
42
+
43
+ def build_i18n_scope(options)
44
+ "#{options[:model].table_name}.#{options[:attribute]}"
45
+ end
46
+
47
+ def get_scopes_from_translations(i18n_scope)
48
+ scopes = %i(base)
49
+
50
+ I18n.available_locales.each do |locale|
51
+ configuration = I18n.t(i18n_scope, locale: locale, default: {})
52
+ configuration.each_key do |scope|
53
+ scopes << scope.to_sym unless scopes.include?(scope.to_sym)
54
+ end
55
+ end
56
+
57
+ scopes
58
+ end
59
+
60
+ def get_missing_keys_by_locale(options, i18n_scope, scopes)
61
+ I18n.available_locales.each_with_object({}) do |locale, missing_keys_by_locale|
62
+ missing_keys = []
63
+
64
+ scopes.each do |scope|
65
+ options[:values].each do |value|
66
+ value_i18n_scope = "#{i18n_scope}.#{scope}.#{value}"
67
+ next if I18n.exists?(value_i18n_scope, locale: locale)
68
+
69
+ missing_keys << value_i18n_scope
70
+ end
71
+ end
72
+
73
+ missing_keys_by_locale[locale] = missing_keys
74
+ end
75
+ end
76
+
77
+ def log_missing_keys(options, scopes, missing_keys_by_locale)
78
+ prefix = "ND::Enum: #{options[:model_name]}##{options[:attribute]}"
79
+ logger.info("#{prefix} scopes=#{scopes}")
80
+
81
+ missing_keys_by_locale.each do |locale, missing_keys|
82
+ logger.info("#{prefix} locale=#{locale} missing_keys=#{missing_keys}")
83
+ end
84
+ end
85
+
86
+ def logger
87
+ @_logger ||= Logger.new(STDOUT)
88
+ end
89
+
90
+ def configuration
91
+ ND::Enum.configuration
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ND
4
+ module Enum
5
+ VERSION = '0.2.0'
6
+ end
7
+ end
data/lib/nd/enum.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require 'active_record/enum.rb'
5
+
6
+ require 'active_support'
7
+ require 'active_support/core_ext/string/inflections.rb'
8
+
9
+ require_relative 'enum/version'
10
+ require_relative 'enum/base'
11
+ require_relative 'enum/configuration'
12
+ require_relative 'enum/i18n'
13
+
14
+ module ND::Enum
15
+ extend ActiveSupport::Concern
16
+
17
+ included do
18
+ def self.nd_enum(db: false, i18n: {}, model: self, model_name: nil, **configuration)
19
+ options = ND::Enum.set_options(binding, model, model_name)
20
+ enum_module = ND::Enum.define_module(options)
21
+
22
+ ND::Enum.define_db_enum(options, enum_module) if options[:db]
23
+ ND::Enum::I18n.validate!(options)
24
+
25
+ const_set(options[:attribute].to_s.camelize, enum_module)
26
+ end
27
+ end
28
+
29
+ class Error < StandardError; end
30
+ class MissingTranslationError < Error
31
+ def message
32
+ 'One or several translations are missing'
33
+ end
34
+ end
35
+
36
+ class << self
37
+ include ND::Enum::Configuration
38
+
39
+ def set_options(caller_binding, caller_class, caller_class_name)
40
+ options = caller_class.method(:nd_enum).parameters.each_with_object({}) do |(_, name), options|
41
+ options[name.to_sym] = caller_binding.local_variable_get(name)
42
+ end
43
+ options[:attribute], options[:values] = options.delete(:configuration).to_a.first
44
+ options[:model] = caller_class
45
+ options[:model_name] = caller_class_name
46
+
47
+ options
48
+ end
49
+
50
+ def define_module(options)
51
+ Module.new do
52
+ include ND::Enum::Base
53
+
54
+ # Public methods
55
+
56
+ define_singleton_method(:all) { options[:values] }
57
+
58
+ # Private methods
59
+
60
+ define_singleton_method(:options) { options }
61
+ options.each_key do |name|
62
+ define_singleton_method(name) { options[name] }
63
+ end
64
+
65
+ [:options, *options.keys].each do |method_name|
66
+ singleton_class.class_eval { private method_name.to_sym }
67
+ end
68
+
69
+ # Constants
70
+
71
+ options[:values].map do |value|
72
+ const_set(value.to_s.upcase, value.to_s)
73
+ end
74
+ end
75
+ end
76
+
77
+ def define_db_enum(options, enum_module)
78
+ enum_options = options[:db].is_a?(Hash) ? options[:db] : {}
79
+
80
+ enum_options[:_prefix] = enum_options.delete(:prefix) if enum_options.key?(:prefix)
81
+ enum_options[:_suffix] = enum_options.delete(:suffix) if enum_options.key?(:suffix)
82
+
83
+ options[:model].enum(options[:attribute] => enum_module.to_h, **enum_options)
84
+ end
85
+ end
86
+ end
87
+
88
+ class ActiveRecord::Base
89
+ include ND::Enum
90
+ end
data/nd-enum.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/nd/enum/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'nd-enum'
7
+ spec.version = ND::Enum::VERSION
8
+ spec.authors = ['Romain Clavel']
9
+ spec.email = ['romain@clavel.io']
10
+
11
+ spec.summary = 'Powerful enum for Rails models.'
12
+ spec.homepage = 'https://github.com/rclavel/nd-enum'
13
+ spec.license = 'MIT'
14
+ spec.required_ruby_version = '>= 2.6.0'
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/rclavel/nd-enum'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/rclavel/nd-enum/blob/master/CHANGELOG.md'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ # Uncomment to register a new dependency of your gem
32
+ spec.add_dependency 'activesupport', '>= 6.0.0'
33
+ spec.add_dependency 'activerecord', '>= 6.0.0'
34
+
35
+ # For more information and examples about making a new gem, check out our
36
+ # guide at: https://bundler.io/guides/creating_gem.html
37
+ end
data/sig/nd/enum.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module ND
2
+ module Enum
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata CHANGED
@@ -1,22 +1,65 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nd-enum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Romain Clavel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-08 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 6.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 6.0.0
13
41
  description:
14
42
  email:
15
43
  - romain@clavel.io
16
44
  executables: []
17
45
  extensions: []
18
46
  extra_rdoc_files: []
19
- files: []
47
+ files:
48
+ - ".rspec"
49
+ - CHANGELOG.md
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - Guardfile
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - lib/nd/enum.rb
57
+ - lib/nd/enum/base.rb
58
+ - lib/nd/enum/configuration.rb
59
+ - lib/nd/enum/i18n.rb
60
+ - lib/nd/enum/version.rb
61
+ - nd-enum.gemspec
62
+ - sig/nd/enum.rbs
20
63
  homepage: https://github.com/rclavel/nd-enum
21
64
  licenses:
22
65
  - MIT