formtastic_tristate_radio 0.1.0 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c29b06aadeb4938a06be81d4b33c23c904d7033011aec8346d43b765f54d262
4
- data.tar.gz: 471d66686e0aa7db2526642214602aa49ede7ab6183100877e9bda5954bab37d
3
+ metadata.gz: 8fdf4d000b01b98679b7b2e4eb9bbb8ac43e2bdec9dfe79aba6e07ed580f0cbf
4
+ data.tar.gz: 7734d39327b809b599b058362e091df2e6153749ff6cfad5a7cb7ac91756e1a2
5
5
  SHA512:
6
- metadata.gz: 7292702606d47a360a308d89c322bb3dda27adc62866c4bc0fa45e6939ebb841a30760684b8b299b7312838b83ebc800d1d5b83624a6d73b37d2f21f2ea33cc5
7
- data.tar.gz: 97251242621b60cc2d1c9f381f29a58f408eddd44c202ba9f57ddf6c7075c54212733c70c9b7ef83c317e8fde8991b7aaaa9f79439e7d060f8f111fc6d0f6c11
6
+ metadata.gz: e67a6dfe2440683e483d9d2bb677b66a87d335625a1ca7622888e641b3b0c36d8602ef502708d2098e78b4d3f15985ca59b6105d17f661adb4e72ea5661f07a1
7
+ data.tar.gz: 9fcd6fe99ddfba81237d17305abcc6e1de6febcc05765c442c265bdb5293adaea88bd9364affac0c374929a4ebc1113cba8c98ad7eb82dd11440cde462fa7606
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## [0.2.2] - 2021-11-05
4
+
5
+ - Make the gem configurable
6
+ - Pull the key used for “unset” choice value into configuration
7
+
8
+ ## [0.2.1] - 2021-11-04
9
+
10
+ - Updates docs URL in gemspec
11
+ - Updates change-log URL in gemspec
12
+ - Removes development gems
13
+ - Adds roadmap items
14
+
15
+ ## [0.2.0] - 2021-11-04
16
+
17
+ - Custom translation override from form via options
18
+ - Custom error class
19
+ - YARD documentation for everything
20
+ - Inherits from `Formtastic::Inputs::RadioInput` and patches only necessary methods
21
+ - Error YAML example for ActiveAdmin included only if ActiveAdmin is detected
22
+
23
+ ## [0.1.0] - 2021-11-01
24
+
25
+ Initial release
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec # Specify your gem's dependencies in .gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,162 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ formtastic_tristate_radio (0.2.3)
5
+ formtastic (>= 3, < 5)
6
+ rails (>= 4, < 7)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ actioncable (6.1.4.1)
12
+ actionpack (= 6.1.4.1)
13
+ activesupport (= 6.1.4.1)
14
+ nio4r (~> 2.0)
15
+ websocket-driver (>= 0.6.1)
16
+ actionmailbox (6.1.4.1)
17
+ actionpack (= 6.1.4.1)
18
+ activejob (= 6.1.4.1)
19
+ activerecord (= 6.1.4.1)
20
+ activestorage (= 6.1.4.1)
21
+ activesupport (= 6.1.4.1)
22
+ mail (>= 2.7.1)
23
+ actionmailer (6.1.4.1)
24
+ actionpack (= 6.1.4.1)
25
+ actionview (= 6.1.4.1)
26
+ activejob (= 6.1.4.1)
27
+ activesupport (= 6.1.4.1)
28
+ mail (~> 2.5, >= 2.5.4)
29
+ rails-dom-testing (~> 2.0)
30
+ actionpack (6.1.4.1)
31
+ actionview (= 6.1.4.1)
32
+ activesupport (= 6.1.4.1)
33
+ rack (~> 2.0, >= 2.0.9)
34
+ rack-test (>= 0.6.3)
35
+ rails-dom-testing (~> 2.0)
36
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
37
+ actiontext (6.1.4.1)
38
+ actionpack (= 6.1.4.1)
39
+ activerecord (= 6.1.4.1)
40
+ activestorage (= 6.1.4.1)
41
+ activesupport (= 6.1.4.1)
42
+ nokogiri (>= 1.8.5)
43
+ actionview (6.1.4.1)
44
+ activesupport (= 6.1.4.1)
45
+ builder (~> 3.1)
46
+ erubi (~> 1.4)
47
+ rails-dom-testing (~> 2.0)
48
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
49
+ activejob (6.1.4.1)
50
+ activesupport (= 6.1.4.1)
51
+ globalid (>= 0.3.6)
52
+ activemodel (6.1.4.1)
53
+ activesupport (= 6.1.4.1)
54
+ activerecord (6.1.4.1)
55
+ activemodel (= 6.1.4.1)
56
+ activesupport (= 6.1.4.1)
57
+ activestorage (6.1.4.1)
58
+ actionpack (= 6.1.4.1)
59
+ activejob (= 6.1.4.1)
60
+ activerecord (= 6.1.4.1)
61
+ activesupport (= 6.1.4.1)
62
+ marcel (~> 1.0.0)
63
+ mini_mime (>= 1.1.0)
64
+ activesupport (6.1.4.1)
65
+ concurrent-ruby (~> 1.0, >= 1.0.2)
66
+ i18n (>= 1.6, < 2)
67
+ minitest (>= 5.1)
68
+ tzinfo (~> 2.0)
69
+ zeitwerk (~> 2.3)
70
+ builder (3.2.4)
71
+ concurrent-ruby (1.1.9)
72
+ crass (1.0.6)
73
+ diff-lcs (1.4.4)
74
+ erubi (1.10.0)
75
+ formtastic (4.0.0)
76
+ actionpack (>= 5.2.0)
77
+ globalid (0.5.2)
78
+ activesupport (>= 5.0)
79
+ i18n (1.8.11)
80
+ concurrent-ruby (~> 1.0)
81
+ loofah (2.12.0)
82
+ crass (~> 1.0.2)
83
+ nokogiri (>= 1.5.9)
84
+ mail (2.7.1)
85
+ mini_mime (>= 0.1.1)
86
+ marcel (1.0.2)
87
+ method_source (1.0.0)
88
+ mini_mime (1.1.2)
89
+ minitest (5.14.4)
90
+ nio4r (2.5.8)
91
+ nokogiri (1.12.5-x86_64-darwin)
92
+ racc (~> 1.4)
93
+ racc (1.6.0)
94
+ rack (2.2.3)
95
+ rack-test (1.1.0)
96
+ rack (>= 1.0, < 3)
97
+ rails (6.1.4.1)
98
+ actioncable (= 6.1.4.1)
99
+ actionmailbox (= 6.1.4.1)
100
+ actionmailer (= 6.1.4.1)
101
+ actionpack (= 6.1.4.1)
102
+ actiontext (= 6.1.4.1)
103
+ actionview (= 6.1.4.1)
104
+ activejob (= 6.1.4.1)
105
+ activemodel (= 6.1.4.1)
106
+ activerecord (= 6.1.4.1)
107
+ activestorage (= 6.1.4.1)
108
+ activesupport (= 6.1.4.1)
109
+ bundler (>= 1.15.0)
110
+ railties (= 6.1.4.1)
111
+ sprockets-rails (>= 2.0.0)
112
+ rails-dom-testing (2.0.3)
113
+ activesupport (>= 4.2.0)
114
+ nokogiri (>= 1.6)
115
+ rails-html-sanitizer (1.4.2)
116
+ loofah (~> 2.3)
117
+ railties (6.1.4.1)
118
+ actionpack (= 6.1.4.1)
119
+ activesupport (= 6.1.4.1)
120
+ method_source
121
+ rake (>= 0.13)
122
+ thor (~> 1.0)
123
+ rake (13.0.6)
124
+ rspec (3.10.0)
125
+ rspec-core (~> 3.10.0)
126
+ rspec-expectations (~> 3.10.0)
127
+ rspec-mocks (~> 3.10.0)
128
+ rspec-core (3.10.1)
129
+ rspec-support (~> 3.10.0)
130
+ rspec-expectations (3.10.1)
131
+ diff-lcs (>= 1.2.0, < 2.0)
132
+ rspec-support (~> 3.10.0)
133
+ rspec-mocks (3.10.2)
134
+ diff-lcs (>= 1.2.0, < 2.0)
135
+ rspec-support (~> 3.10.0)
136
+ rspec-support (3.10.3)
137
+ sprockets (4.0.2)
138
+ concurrent-ruby (~> 1.0)
139
+ rack (> 1, < 3)
140
+ sprockets-rails (3.2.2)
141
+ actionpack (>= 4.0)
142
+ activesupport (>= 4.0)
143
+ sprockets (>= 3.0.0)
144
+ thor (1.1.0)
145
+ tzinfo (2.0.4)
146
+ concurrent-ruby (~> 1.0)
147
+ websocket-driver (0.7.5)
148
+ websocket-extensions (>= 0.1.0)
149
+ websocket-extensions (0.1.5)
150
+ yard (0.9.26)
151
+ zeitwerk (2.5.1)
152
+
153
+ PLATFORMS
154
+ x86_64-darwin-17
155
+
156
+ DEPENDENCIES
157
+ formtastic_tristate_radio!
158
+ rspec (~> 3)
159
+ yard (>= 0.9.20, < 1)
160
+
161
+ BUNDLED WITH
162
+ 2.2.27
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Sergey Pedan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,50 +1,57 @@
1
1
  # Formtastic tri-state radio
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/formtastic_tristate_radio.svg)](https://badge.fury.io/rb/formtastic_tristate_radio)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/c2508d7f23238fb2b87f/maintainability)](https://codeclimate.com/github/sergeypedan/formtastic-tristate-radio/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/c2508d7f23238fb2b87f/test_coverage)](https://codeclimate.com/github/sergeypedan/formtastic-tristate-radio/test_coverage)
6
+
3
7
  ## What is “tri-state”?
4
8
 
5
- — that which has 3 states.
9
+ — that which has 3 states.
6
10
 
7
- By defenition Boolean values have 2 states: True | False.
11
+ By defenition Boolean values have 2 states: True & False.
8
12
 
9
- However, if you store a Boolean value in a database column with no `NOT FULL` restriction, it aquires a 3d possible state: `null`.
13
+ However, if you store a Boolean value in a database column with no `NOT NULL` restriction, it aquires a 3<sup>d</sup> possible state: `null`.
10
14
 
11
- Some may say this is a questionable practice — I don’t think so. In real life you always have a case when the answer to your question may be only “yes” or “no”, but you don’t know the answer yet. Using a string type column, storing there `"yes"`, `"no"` and `"unset"` + using a state machine + validations — feels overkill to me.
15
+ Some may consider this practice questionable — I don’t think so. In real life you always have a case when the answer to your question may be only “yes” or “no”, but you don’t know the answer yet. Using a string type column, storing there `"yes"`, `"no"` and `"unset"` + using a state machine + validations — feels overkill to me.
12
16
 
13
17
 
14
18
  ## What the gem does
15
19
 
16
- 1. Provides a custom Formtastic input type `:tristate_radio` which renders 3 radios (“Yes”, “No”, “Unset”) instead of a checkbox (only where you put it).
17
- 1. Teaches Rails recognize `"null"` and `"nil"` param values as `nil`. See “[How it works](#how-it-works)” ☟ section for technical details on this.
18
- 1. Encourages you to add translations for ActiveAdmin “status tag” so that `nil` be correctly translated as “Unset” instead of “False”.
20
+ 1. Provides a custom Formtastic input type `:tristate_radio` which renders 3 radios (“Yes”, “No”, “Unset”) instead of a checkbox (only where you put it).
21
+ 1. Teaches Rails recognize `"null"` and `"nil"` param values as `nil`. See “[How it works](#how-it-works)” ☟ section for technical details on this.
22
+ 1. Encourages you to add translations for ActiveAdmin “status tag” so that `nil` be correctly translated as “Unset” instead of “False”.
19
23
 
20
24
 
21
25
  ## Usage
22
26
 
23
- For a Boolean column with 3 possible states:
27
+ For a Boolean column with 3 possible states:
24
28
 
25
29
  ```ruby
26
- f.input :column_name, as: :tristate_radio
30
+ f.input :am_i_awake, as: :tristate_radio
27
31
  ```
28
32
 
29
- You get (HTML is simplified):
33
+ You get (HTML is simplified, actually there are more classes etc.):
30
34
 
31
35
  ```html
32
36
  <fieldset>
33
- <input name="column_name" type="radio" value="true"> <label>Yes</label>
34
- <input name="column_name" type="radio" value="false"> <label>No</label>
35
- <input name="column_name" type="radio" value="null"> <label>Unset</label>
37
+ <legend>Am i awake?</legend>
38
+ <input name="am_i_awake" type="radio" value="true"> <label>Yes</label>
39
+ <input name="am_i_awake" type="radio" value="false"> <label>No</label>
40
+ <input name="am_i_awake" type="radio" value="null"> <label>Unset</label>
36
41
  </fieldset>
37
42
  ```
38
43
 
39
- In the future `:tristate_radio` will be registered for Boolean columns with `null` by default. Until then you have to assign it manually.
40
-
41
44
 
42
45
  ## Installation
43
46
 
47
+ ### Gem
48
+
44
49
  ```ruby
45
50
  gem "formtastic_tristate_radio"
46
51
  ```
47
52
 
53
+ ### Translations
54
+
48
55
  Add translation for the new “unset” option:
49
56
 
50
57
  ```yaml
@@ -55,7 +62,15 @@ ru:
55
62
  null: Неизвестно # <- this you must provide youself
56
63
  ```
57
64
 
58
- ActiveAdmin will automatically translate `nil` as “No”, so if you use ActiveAdmin, add translation like so:
65
+ As noted in [Usage](#usage), you can override individual translations like so:
66
+
67
+ ```ruby
68
+ f.input :attribute, as: :tristate_radio, null: "Your text"
69
+ ```
70
+
71
+ ### ActiveAdmin translations
72
+
73
+ ActiveAdmin will automatically translate `nil` as “No”, so if you use ActiveAdmin, add translation like so:
59
74
 
60
75
  ```yaml
61
76
  ru:
@@ -63,23 +78,47 @@ ru:
63
78
  status_tag:
64
79
  :yes: Да
65
80
  :no: Нет
66
- null: Неизвестно
81
+ unset: Неизвестно
67
82
  ```
68
83
 
84
+ Notice that the key ActiveAdmin uses is “unset”, not “null”.
85
+
69
86
 
70
87
  ## Configuration
71
88
 
72
- Nothing is configurable yet. I think of making configurable which values are regognized as `nil`.
89
+ It’s difficult to come up with a reasonable use case for that, but you can configure what will be used as inputs value:
90
+
91
+ ```ruby
92
+ # config/initializers/formtastic.rb
93
+ FormtasticTristateRadio.configure do |config|
94
+ config.unset_key = "__unset" # default is :null
95
+ end
96
+ ```
97
+
98
+ which will result in:
99
+
100
+ ```html
101
+ <input type="radio" name="am_i_awake" value="true">
102
+ <input type="radio" name="am_i_awake" value="false">
103
+ <input type="radio" name="am_i_awake" value="__unset">
104
+ ```
105
+
106
+ Mind that for your custom value to work, you also need to configure `ActiveModel` to recognize that value as `nil`. Currently that is done [like so](https://github.com/sergeypedan/formtastic-tristate-radio/blob/master/config/initializers/activemodel_type_boolean.rb#L9).
107
+
108
+
109
+ ## Documentation
110
+
111
+ Low-level methods are properly documented in RubyDoc [here](https://www.rubydoc.info/gems/formtastic_tristate_radio/TristateRadioInput).
73
112
 
74
113
 
75
114
  ## Dependencies
76
115
 
77
- Now the gem depends on [Formtastic](https://github.com/formtastic/formtastic) (naturally) and Rails. Frankly I am not sure whether I will have time to make it work with other frameworks.
116
+ Now the gem depends on [Formtastic](https://github.com/formtastic/formtastic) (naturally) and Rails. Frankly I am not sure whether I will have time to make it work with other frameworks.
78
117
 
79
118
 
80
119
  ## How it works
81
120
 
82
- In Ruby any String is cast to `true`:
121
+ In Ruby any String is cast to `true`:
83
122
 
84
123
  ```ruby
85
124
  !!"" #=> true
@@ -89,11 +128,11 @@ In Ruby any String is cast to `true`:
89
128
  !!"null" #=> true
90
129
  ```
91
130
 
92
- Web form params are passed as plain text and are interpreted as String by Rack.
131
+ Web form params are passed as plain text and are interpreted as String by Rack.
93
132
 
94
- So how Boolean values are transfered as strings if a `"no"` or `"0"` and even `""` is truthy in Ruby?
133
+ So how are Boolean values transfered as strings if a `"no"` or `"0"` and even `""` is truthy in Ruby?
95
134
 
96
- Frameworks just have a list of string values to be recognized and mapped to Boolean values:
135
+ Frameworks just have a list of string values to be recognized and mapped to Boolean values:
97
136
 
98
137
  ```ruby
99
138
  ActiveModel::Type::Boolean::FALSE_VALUES
@@ -115,7 +154,7 @@ ActiveModel::Type::Boolean.new.cast("off") #=> false
115
154
  # etc
116
155
  ```
117
156
 
118
- So what [I do in this gem](https://github.com/sergeypedan/formtastic_tristate_radio/blob/master/config/initializers/activemodel_type_boolean.rb) is extend `ActiveModel::Type::Boolean` in a consistent way to teach it recognize null-ish values as `nil`:
157
+ So what [I do in this gem](https://github.com/sergeypedan/formtastic_tristate_radio/blob/master/config/initializers/activemodel_type_boolean.rb) is extend `ActiveModel::Type::Boolean` in a consistent way to teach it recognize null-ish values as `nil`:
119
158
 
120
159
  ```ruby
121
160
  module ActiveModel
@@ -143,9 +182,21 @@ ActiveModel::Type::Boolean.new.cast("nil") #=> nil
143
182
  ActiveModel::Type::Boolean.new.cast(:nil) #=> nil
144
183
  ```
145
184
 
146
- **Warning**: as you might have noticed, default Rails behavior is changed. If you rely on Rails’ automatic conversion of strings with value `"null"` into `true`, this gem might not be for you (and you are definitely doing something weird).
185
+ **Warning**: as you might have noticed, default Rails behavior is changed. If you rely on Rails’ automatic conversion of strings with value `"null"` into `true`, this gem might not be for you (and you are definitely doing something weird).
186
+
187
+
188
+ ## Roadmap
189
+
190
+ - [ ] Remove `require_relative "../app/models/active_record/base"` from main file
191
+ - [x] Make the gem configurable
192
+ - [x] Pull the key used for “unset” choice value into configuration
193
+ - [ ] Load translations from gem
194
+ - [ ] Add translations into most popular languages
195
+ - [ ] Rgister `:tristate_radio` for Boolean columns with `null`
196
+ - [ ] Decouple `ActiveModel::Type::Boolean` thing from Formtastic things, maybe into a separate gem
197
+ - [ ] Decouple from Rails
147
198
 
148
199
 
149
200
  ## License
150
201
 
151
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
202
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -3,168 +3,110 @@
3
3
  require "formtastic"
4
4
 
5
5
  # It may also be appropriate to put this file in `app/inputs`
6
- class TristateRadioInput
6
+ class TristateRadioInput < Formtastic::Inputs::RadioInput
7
7
 
8
- include Formtastic::Inputs::Base
9
- include Formtastic::Inputs::Base::Collections
10
- include Formtastic::Inputs::Base::Choices
11
-
12
-
13
- # UNSET_KEY = :null
14
- UNSET_KEY = ActiveModel::Type::Boolean::NULL_VALUES.reject(&:blank?).first
8
+ # No equals `:null`.
9
+ # Should equal one of `ActiveModel::Type::Boolean::NULL_VALUES`
15
10
  #
16
- # Mind ActiveAdmin status resolving logic:
17
- # https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/views/components/status_tag.rb#L51
18
- # In status tag builder the value is lowercased before casting into boolean, and the keyword for nil is "unset", so
19
- # if we have lowercase "unset", translations from `ru.formtastic.unset` will be overriden by `ru.active_admin.status_tag.unset`.
20
-
21
- MISSING_TRANSLATION_ERROR_MSG = <<~HEREDOC
22
-
11
+ # Mind ActiveAdmin [status resolving logic](https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/views/components/status_tag.rb#L51):
12
+ # in status tag builder the value is lowercased before casting into Boolean, and the keyword for nil is `"unset"`.
13
+ # So if we have lowercase `"unset"`, translations from `ru.formtastic.unset` will be overriden by `ru.active_admin.status_tag.unset`.
14
+ #
15
+ UNSET_KEY = FormtasticTristateRadio.config.unset_key
23
16
 
24
- For ActiveAdmin status tags in index & view tables:
17
+ I18N_EXAMPLE_ACTIVEADMIN = <<~YAML.chomp
25
18
  ru:
26
19
  active_admin:
27
20
  status_tag:
28
21
  :yes: Да
29
22
  :no: Нет
30
23
  :#{UNSET_KEY}: Неизвестно
24
+ YAML
31
25
 
32
- For radiobutton labels in forms:
26
+ I18N_EXAMPLE_FORMTASTIC = <<~YAML.chomp
33
27
  ru:
34
28
  formtastic:
35
29
  :yes: Да
36
30
  :no: Нет
37
31
  :#{UNSET_KEY}: Неизвестно
38
-
39
- Note: “yes”, “no”, “null” and some other words are reserved, converted into Boolean values in YAML, so you need to quote or symbolize them.
40
- HEREDOC
32
+ YAML
41
33
 
42
34
 
43
- # template => an instance of ActionView::Base
35
+ # @note In you have ActiveAdmin installed, it will give you YAML example for ActiveAdmin as well, otherwise only for Formtastic
44
36
  #
45
- # @param choice [Array], ["Completed", "completed"]
37
+ # @return [String] error message with YAML examples for the “unset” label translation lookup error
46
38
  #
47
- def choice_html(choice)
48
- template.content_tag(:label, tag_content(choice), tag_options(choice))
39
+ def self.missing_i18n_error_msg
40
+ msg = []
41
+ msg << "Add translations for the “unset” radio label"
42
+ msg << ["For radiobutton labels in forms:", I18N_EXAMPLE_FORMTASTIC].join("\n")
43
+ msg << "Note: “yes”, “no” and some other reserved words are converted into Boolean values in YAML, so you need to quote or symbolize them."
44
+ msg << ["For ActiveAdmin status tags in index & view tables:", I18N_EXAMPLE_ACTIVEADMIN].join("\n") if !!defined?(ActiveAdmin)
45
+ msg.join("\n\n")
49
46
  end
50
47
 
51
48
 
52
- # collection => [["Completed", "completed"], ["In progress", "in_progress"], ["Unknown", "unset"]]
49
+ # @see https://github.com/formtastic/formtastic/blob/35dc806964403cb2bb0a6074b951ceef906c8581/lib/formtastic/inputs/base/choices.rb#L59 Original Formtastic method
53
50
  #
54
- # @return [Array]
51
+ # @return [Hash] HTML options for the `<input type="radio" />` tag
55
52
  #
56
- def collection_with_unset
57
- collection + [[unset_label_translation, UNSET_KEY]]
53
+ # Adds `{ selected: true }` to the original options Hash if the choice value equals attribute value (to ultimately set for `checked="checked"`)
54
+ #
55
+ def choice_html_options(choice)
56
+ super.merge({ checked: selected?(choice) })
58
57
  end
59
58
 
60
59
 
61
- # Override to remove the for attribute since this isn't associated with any element, as it's nested inside the legend
62
- # @return [Hash]
60
+ # @example Original method
61
+ # def collection_for_boolean
62
+ # true_text = options[:true] || Formtastic::I18n.t(:yes)
63
+ # false_text = options[:false] || Formtastic::I18n.t(:no)
64
+ # [ [true_text, true], [false_text, false] ]
65
+ # end
63
66
  #
64
- # @example
65
- # { for: nil, class: ["label"] }
67
+ # collection_for_boolean #=> [["Да", true], ["Нет", false]]
66
68
  #
67
- def label_html_options
68
- super.merge({ for: nil })
69
- end
70
-
71
-
72
- # choice_value(choice) => true | false | UNSET_KEY <- in our version
73
- # choice_value(choice) => true | false | ? <- in regular radio-buttons version
74
- # method => :status
75
- # object => ActiveRecord::Base model subclass, `User`
69
+ # @example This patched method
70
+ # collection_for_boolean #=> [["Да", true], ["Нет", false], ["Неизвестно", :null]]
76
71
  #
77
- # @param choice [Array], ["Completed", "completed"]
72
+ # @return [Array<[String, (Boolean|String|Symbol)]>] an array of “choices”, each presented as an array with 2 items: HTML label text and HTML input value
78
73
  #
79
- # For this to work, ActiveModel::Type::Boolean must be patched to resolve `UNSET_KEY` as nil
74
+ # @see https://github.com/formtastic/formtastic/blob/e34baba470d2fda75bf9748cff8898ee0ed29075/lib/formtastic/inputs/base/collections.rb#L131 Original Formtastic method
80
75
  #
81
- def selected?(choice)
82
- ActiveModel::Type::Boolean.new.cast(choice_value(choice)) == object.public_send(method)
76
+ def collection_for_boolean
77
+ super + [[label_text_for_unset, UNSET_KEY]]
83
78
  end
84
79
 
85
80
 
86
- # @returns [String]
87
- # "<input ...> Text..."
88
- #
89
- # @param choice [Array], ["Completed", "completed"]
81
+ # Checks translation passed as option, then checks in locale
90
82
  #
91
- # input_html_options => { id: "task_status", required: false, autofocus: false, readonly: false}
92
- #
93
- # input_html_options.merge(choice_html_options(choice)).merge({ required: false })
94
- # => { id: "task_status_completed", required: false, autofocus: false, readonly: false }
95
- #
96
- # builder => an instance of ActiveAdmin::FormBuilder
97
- # choice_label(choice) => "Completed"
98
- # choice_html_options(choice) => { id: "task_status_completed" }
99
- # choice_value(choice) => "completed"
100
- # input_name => :status
83
+ # @example
84
+ # label_text_for_unset #=> "Неизвестно"
101
85
  #
102
- def tag_content(choice)
103
- builder.radio_button(
104
- input_name,
105
- choice_value(choice),
106
- input_html_options.merge(choice_html_options(choice)).merge({ required: false, checked: selected?(choice) })
107
- ) << choice_label(choice)
108
- end
109
-
110
-
111
- # choice_input_dom_id(choice) => "task_status_completed"
112
- # label_html_options => { for: nil, class: ["label"] }
86
+ # @return [String] Label of the radio that stands for the unknown choice
113
87
  #
114
- # @param choice [Array], ["Completed", "completed"]
88
+ # @raise [StandardError] if the translation could not be found
89
+ # @see missing_i18n_error_msg
115
90
  #
116
- def tag_options(choice)
117
- label_html_options.merge({ for: choice_input_dom_id(choice), class: nil })
91
+ def label_text_for_unset
92
+ options.fetch(:null, Formtastic::I18n.t(UNSET_KEY)).presence or \
93
+ fail FormtasticTristateRadio::MissingTranslationError.new(self.class.missing_i18n_error_msg)
118
94
  end
119
95
 
120
96
 
121
- # choice_wrapping_html_options(choice) #=> { class: "choice" }
97
+ # @example For each item of `collection` it runs:
98
+ # selected?(["Да", true]) #=> false
99
+ # selected?(["Нет", false]) #=> false
100
+ # selected?(["Неизвестно", :null]) #=> true
122
101
  #
123
- # legend_html => "<legend class="label">
124
- # <label>Status</label>
125
- # </legend>"
102
+ # @param choice [Array<[String, (Boolean|String|Symbol)]>]
126
103
  #
127
- # choice_html(choice) => "<label for="task_status_completed">
128
- # <input type="radio" value="completed" name="task[status]" /> Completed
129
- # </label>"
104
+ # @return [Boolean] answer to the question “Is the passed option selected?”
130
105
  #
131
- # collection.map do |choice|
132
- # choice_wrapping({ class: "choice" }) do
133
- # choice_html(choice)
134
- # end
135
- # end
136
- # => ["<li class="choice">
137
- # <label for="task_status_completed">
138
- # <input type="radio" value="completed" name="task[status]" /> Completed
139
- # </label>
140
- # </li>",
141
- # "<li class="choice">
142
- # ...
143
- # ]
144
- #
145
- # This method relies on ActiveAdmin
146
- #
147
- def to_html
148
- choices = collection_with_unset #=> [["Completed", "completed"], ["In progress", "in_progress"], ["Unknown", "unset"]]
149
-
150
- input_wrapping do
151
- choices_wrapping do
152
- legend_html << choices_group_wrapping do
153
- choices.map { |choice|
154
- choice_wrapping(choice_wrapping_html_options(choice)) do
155
- choice_html(choice)
156
- end
157
- }.join("\n").html_safe
158
- end
159
- end
160
- end
161
- end
162
-
163
-
164
- # @return [String] Label of the radio that stands for the unknown choice
106
+ # @note For this to work, `ActiveModel::Type::Boolean` must be patched to resolve `UNSET_KEY` as `nil`.
165
107
  #
166
- def unset_label_translation
167
- Formtastic::I18n.t(UNSET_KEY).presence or fail StandardError.new(MISSING_TRANSLATION_ERROR_MSG)
108
+ def selected?(choice)
109
+ ActiveModel::Type::Boolean.new.cast(choice_value(choice)) == object.public_send(method)
168
110
  end
169
111
 
170
112
  end
@@ -2,7 +2,11 @@
2
2
 
3
3
  class ActiveRecord::Base
4
4
 
5
- # @return [Array] of symbols — names of Boolean columns, which can be `NULL`
5
+ # @return [Array<Symbol>] names of Boolean columns which can store `NULL` values
6
+ # @example
7
+ # Company.tristate_column_names
8
+ # #=> [:is_profitable, :is_run_by_psychopaths, :evades_taxation, ...]
9
+ #
6
10
  def self.tristate_column_names
7
11
  columns.select { |col| col.type == :boolean && col.null }.map(&:name)
8
12
  end
@@ -4,7 +4,7 @@ module ActiveModel
4
4
  module Type
5
5
  class Boolean < Value
6
6
 
7
- NULL_VALUES = [nil, "", "null", :null, "nil", :nil].to_set.freeze
7
+ NULL_VALUES = [nil, "", :null, "null", :nil, "nil"].to_set.freeze
8
8
 
9
9
  private def cast_value(value)
10
10
  NULL_VALUES.include?(value) ? nil : !FALSE_VALUES.include?(value)
@@ -0,0 +1,60 @@
1
+ ---
2
+ ca:
3
+ active_admin:
4
+ status_tag:
5
+ :null: No establert
6
+ ---
7
+ de:
8
+ active_admin:
9
+ status_tag:
10
+ :null: Nicht eingestellt
11
+ ---
12
+ fr:
13
+ active_admin:
14
+ status_tag:
15
+ :null: Pas défini
16
+ ---
17
+ it:
18
+ active_admin:
19
+ status_tag:
20
+ :null: Sconosciuto
21
+ ---
22
+ ja:
23
+ active_admin:
24
+ status_tag:
25
+ :null: 不明
26
+ ---
27
+ en:
28
+ active_admin:
29
+ status_tag:
30
+ :null: Unset
31
+ ---
32
+ es:
33
+ active_admin:
34
+ status_tag:
35
+ :null: No establecido
36
+ ---
37
+ pl:
38
+ active_admin:
39
+ status_tag:
40
+ :null: Nie ustawiony
41
+ ---
42
+ pt-BR:
43
+ active_admin:
44
+ status_tag:
45
+ :null: Não configurado
46
+ ---
47
+ pt:
48
+ active_admin:
49
+ status_tag:
50
+ :null: Não configurado
51
+ ---
52
+ ru:
53
+ active_admin:
54
+ status_tag:
55
+ :null: Неизвестно
56
+ ---
57
+ tr:
58
+ active_admin:
59
+ status_tag:
60
+ :null: Ayarlanmadı
@@ -0,0 +1,48 @@
1
+ ---
2
+ ca:
3
+ formtastic:
4
+ :null: No establert
5
+ ---
6
+ de:
7
+ formtastic:
8
+ :null: Nicht eingestellt
9
+ ---
10
+ fr:
11
+ formtastic:
12
+ :null: Pas défini
13
+ ---
14
+ it:
15
+ formtastic:
16
+ :null: Sconosciuto
17
+ ---
18
+ ja:
19
+ formtastic:
20
+ :null: 不明
21
+ ---
22
+ en:
23
+ formtastic:
24
+ :null: Unset
25
+ ---
26
+ es:
27
+ formtastic:
28
+ :null: No establecido
29
+ ---
30
+ pl:
31
+ formtastic:
32
+ :null: Nie ustawiony
33
+ ---
34
+ pt-BR:
35
+ formtastic:
36
+ :null: Não configurado
37
+ ---
38
+ pt:
39
+ formtastic:
40
+ :null: Não configurado
41
+ ---
42
+ ru:
43
+ formtastic:
44
+ :null: Неизвестно
45
+ ---
46
+ tr:
47
+ formtastic:
48
+ :null: Ayarlanmadı
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://guides.rubygems.org/name-your-gem/
4
+ # https://bundler.io/guides/creating_gem.html
5
+ # https://guides.rubyonrails.org/engines.html
6
+ # https://guides.rubyonrails.org/plugins.html
7
+
8
+ require_relative "lib/formtastic_tristate_radio/version"
9
+
10
+ Gem::Specification.new do |spec|
11
+ spec.name = "formtastic_tristate_radio"
12
+ spec.version = FormtasticTristateRadio::VERSION
13
+ spec.authors = ["Sergey Pedan"]
14
+ spec.email = ["sergey.pedan@gmail.com"]
15
+ spec.license = "MIT"
16
+
17
+ spec.summary = "Have 3-state radiobuttons instead of a 2-state checkbox for your Boolean columns which can store NULL"
18
+ spec.description = <<~HEREDOC
19
+ #{spec.summary}.
20
+
21
+ What the gem does?
22
+
23
+ 1. Provides a custom Formtastic input type `:tristate_radio` which renders 3 radios (“Yes”, “No”, “Unset”) instead of a checkbox (only where you put it).
24
+ 1. Teaches Rails recognize `"null"` and `"nil"` param values as `nil`. See “[How it works](#how-it-works)” ☟ section for technical details on this.
25
+ 1. Encourages you to add translations for ActiveAdmin “status tag” so that `nil` be correctly translated as “Unset” instead of “False”.
26
+
27
+ Does not change controls, you need to turn it on via `as: :tristate_radio` option.
28
+
29
+ By defenition Boolean values have 2 states: True & False.
30
+
31
+ However, if you store a Boolean value in a database column with no `NOT NULL` restriction, it aquires a 3<sup>d</sup> possible state: `null`.
32
+
33
+ Some may consider this practice questionable — I don’t think so. In real life you always have a case when the answer to your question may be only “yes” or “no”, but you don’t know the answer yet. Using a string type column, storing there `"yes"`, `"no"` and `"unset"` + using a state machine + validations — feels overkill to me.
34
+ HEREDOC
35
+
36
+ spec.homepage = "https://github.com/sergeypedan/formtastic-tristate-radio"
37
+ spec.extra_rdoc_files = ["README.md", "CHANGELOG.md"]
38
+ spec.rdoc_options = ["--charset=UTF-8"]
39
+ spec.metadata = { "changelog_uri" => "#{spec.homepage}/blob/master/CHANGELOG.md",
40
+ "documentation_uri" => "https://www.rubydoc.info/gems/#{spec.name}",
41
+ "homepage_uri" => spec.homepage,
42
+ "source_code_uri" => spec.homepage }
43
+
44
+ spec.require_paths = ["app/inputs", "app/models/active_record", "config/initializers", "config/locales", "lib"]
45
+ spec.bindir = "exe"
46
+ spec.executables = []
47
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
48
+ `git ls-files`.split("\n")
49
+ .reject { |f| %w[bin spec test].any? { |dir| f.start_with? dir } }
50
+ .reject { |f| f.start_with? "." }
51
+ end
52
+
53
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
54
+
55
+ spec.add_dependency "formtastic", ">= 3", "< 5"
56
+ spec.add_dependency "rails", ">= 4", "< 7"
57
+
58
+ spec.add_development_dependency "rspec", "~> 3"
59
+ spec.add_development_dependency "yard", ">= 0.9.20", "< 1"
60
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormtasticTristateRadio
4
+
5
+ class << self
6
+ attr_writer :config
7
+ end
8
+
9
+ def self.config
10
+ @config ||= Configuration.new
11
+ end
12
+
13
+ def self.configure
14
+ yield(config)
15
+ end
16
+
17
+ class Configuration
18
+ attr_accessor :unset_key
19
+
20
+ def initialize
21
+ @unset_key = :null
22
+ end
23
+ end
24
+
25
+ end
@@ -1,4 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FormtasticTristateRadio
4
+
5
+ # This is standard Rails way to autoload gem’s contents dynamically as an “engine”
6
+ # https://guides.rubyonrails.org/engines.html
7
+ #
2
8
  class Engine < ::Rails::Engine
3
9
  end
10
+
4
11
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormtasticTristateRadio
4
+ class MissingTranslationError < I18n::MissingTranslationData
5
+ end
6
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FormtasticTristateRadio
2
- VERSION = '0.1.0'
4
+ VERSION = '0.2.3'
3
5
  end
@@ -1,8 +1,9 @@
1
- require "formtastic_tristate_radio/version"
2
- require "formtastic_tristate_radio/engine"
1
+ require_relative "formtastic_tristate_radio/version"
2
+ require_relative "formtastic_tristate_radio/missing_translation_error"
3
+ require_relative "formtastic_tristate_radio/configuration"
4
+ require_relative "formtastic_tristate_radio/engine"
3
5
 
4
6
  require_relative "../app/models/active_record/base"
5
- # require_relative "../app/inputs/tristate_radio_input"
6
7
 
7
8
  module FormtasticTristateRadio
8
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formtastic_tristate_radio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Pedan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-03 00:00:00.000000000 Z
11
+ date: 2021-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: formtastic
@@ -51,102 +51,98 @@ dependencies:
51
51
  - !ruby/object:Gem::Version
52
52
  version: '7'
53
53
  - !ruby/object:Gem::Dependency
54
- name: coveralls
54
+ name: rspec
55
55
  requirement: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: '0.2'
59
+ version: '3'
60
60
  type: :development
61
61
  prerelease: false
62
62
  version_requirements: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: '0.2'
66
+ version: '3'
67
67
  - !ruby/object:Gem::Dependency
68
- name: pry
68
+ name: yard
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
- - - "~>"
72
- - !ruby/object:Gem::Version
73
- version: '0.14'
74
- type: :development
75
- prerelease: false
76
- version_requirements: !ruby/object:Gem::Requirement
77
- requirements:
78
- - - "~>"
71
+ - - ">="
79
72
  - !ruby/object:Gem::Version
80
- version: '0.14'
81
- - !ruby/object:Gem::Dependency
82
- name: rake
83
- requirement: !ruby/object:Gem::Requirement
84
- requirements:
85
- - - "~>"
73
+ version: 0.9.20
74
+ - - "<"
86
75
  - !ruby/object:Gem::Version
87
- version: '13'
76
+ version: '1'
88
77
  type: :development
89
78
  prerelease: false
90
79
  version_requirements: !ruby/object:Gem::Requirement
91
80
  requirements:
92
- - - "~>"
93
- - !ruby/object:Gem::Version
94
- version: '13'
95
- - !ruby/object:Gem::Dependency
96
- name: yard
97
- requirement: !ruby/object:Gem::Requirement
98
- requirements:
99
- - - "~>"
81
+ - - ">="
100
82
  - !ruby/object:Gem::Version
101
- version: '0.9'
102
- type: :development
103
- prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
105
- requirements:
106
- - - "~>"
83
+ version: 0.9.20
84
+ - - "<"
107
85
  - !ruby/object:Gem::Version
108
- version: '0.9'
109
- description: 'Have 3-state radiobuttons instead of a 2-state checkbox for your Boolean
110
- columns which can store NULL. Does not change controls, you need to turn it on via
111
- `as: :tristate_radio` option.'
86
+ version: '1'
87
+ description: |
88
+ Have 3-state radiobuttons instead of a 2-state checkbox for your Boolean columns which can store NULL.
89
+
90
+ What the gem does?
91
+
92
+ 1. Provides a custom Formtastic input type `:tristate_radio` which renders 3 radios (“Yes”, “No”, “Unset”) instead of a checkbox (only where you put it).
93
+ 1. Teaches Rails recognize `"null"` and `"nil"` param values as `nil`. See “[How it works](#how-it-works)” ☟ section for technical details on this.
94
+ 1. Encourages you to add translations for ActiveAdmin “status tag” so that `nil` be correctly translated as “Unset” instead of “False”.
95
+
96
+ Does not change controls, you need to turn it on via `as: :tristate_radio` option.
97
+
98
+ By defenition Boolean values have 2 states: True & False.
99
+
100
+ However, if you store a Boolean value in a database column with no `NOT NULL` restriction, it aquires a 3<sup>d</sup> possible state: `null`.
101
+
102
+ Some may consider this practice questionable — I don’t think so. In real life you always have a case when the answer to your question may be only “yes” or “no”, but you don’t know the answer yet. Using a string type column, storing there `"yes"`, `"no"` and `"unset"` + using a state machine + validations — feels overkill to me.
112
103
  email:
113
104
  - sergey.pedan@gmail.com
114
105
  executables: []
115
106
  extensions: []
116
107
  extra_rdoc_files:
117
108
  - README.md
109
+ - CHANGELOG.md
118
110
  files:
111
+ - CHANGELOG.md
112
+ - Gemfile
113
+ - Gemfile.lock
114
+ - MIT-LICENSE
119
115
  - README.md
120
116
  - Rakefile
121
117
  - app/inputs/tristate_radio_input.rb
122
118
  - app/models/active_record/base.rb
123
119
  - config/initializers/activemodel_type_boolean.rb
124
- - config/locales/active_admin.en.yml
125
- - config/locales/active_admin.ru.yml
126
- - config/locales/formtastic.en.yml
127
- - config/locales/formtastic.ru.yml
120
+ - config/locales/active_admin.yml
121
+ - config/locales/formtastic.yml
128
122
  - config/routes.rb
123
+ - formtastic_tristate_radio.gemspec
129
124
  - lib/formtastic_tristate_radio.rb
125
+ - lib/formtastic_tristate_radio/configuration.rb
130
126
  - lib/formtastic_tristate_radio/engine.rb
127
+ - lib/formtastic_tristate_radio/missing_translation_error.rb
131
128
  - lib/formtastic_tristate_radio/version.rb
132
- - lib/tasks/formtastic_tristate_radio_tasks.rake
133
129
  homepage: https://github.com/sergeypedan/formtastic-tristate-radio
134
130
  licenses:
135
131
  - MIT
136
132
  metadata:
137
- changelog_uri: https://github.com/sergeypedan/formtastic-tristate-radio/blob/master/Changelog.md
138
- documentation_uri: https://github.com/sergeypedan/formtastic-tristate-radio#usage
133
+ changelog_uri: https://github.com/sergeypedan/formtastic-tristate-radio/blob/master/CHANGELOG.md
134
+ documentation_uri: https://www.rubydoc.info/gems/formtastic_tristate_radio
139
135
  homepage_uri: https://github.com/sergeypedan/formtastic-tristate-radio
140
136
  source_code_uri: https://github.com/sergeypedan/formtastic-tristate-radio
141
137
  post_install_message:
142
138
  rdoc_options:
143
139
  - "--charset=UTF-8"
144
140
  require_paths:
145
- - lib
146
141
  - app/inputs
147
142
  - app/models/active_record
148
143
  - config/initializers
149
144
  - config/locales
145
+ - lib
150
146
  required_ruby_version: !ruby/object:Gem::Requirement
151
147
  requirements:
152
148
  - - ">="
@@ -1,5 +0,0 @@
1
- ---
2
- en:
3
- active_admin:
4
- status_tag:
5
- unset: Unset
@@ -1,7 +0,0 @@
1
- ---
2
- ru:
3
- active_admin:
4
- status_tag:
5
- :no: Нет
6
- :yes: Да
7
- unset: Неизвестно
@@ -1,4 +0,0 @@
1
- ---
2
- en:
3
- formtastic:
4
- :null: Unset
@@ -1,6 +0,0 @@
1
- ---
2
- ru:
3
- formtastic:
4
- :no: Нет
5
- :null: Неизвестно
6
- :yes: Да
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :formtastic_tristate_radio do
3
- # # Task goes here
4
- # end