actionview_attribute_builders 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b86da009d7cc1776185d530b5a36d0b987c45ae6decbe56e6417d16d491462bb
4
- data.tar.gz: 53e398656df6d9e343c4efa880ebb49b8ae2db108dc5467cb77b6abb8467970f
3
+ metadata.gz: 43274cf57e396d3787e0edc0c015784d9cf2a5f2eadc0ca10c2a64cd3ab8f162
4
+ data.tar.gz: 4e1dccf8cb53ba229f2a3285338831a01632f2fbc5a2f313404684fe2179c819
5
5
  SHA512:
6
- metadata.gz: 599cc0f4d55bddea629a960f26d0f4a681af8510e4c37b82db6085dedc8ca5791acb4024dc7b151347bd8a63aa738b775895b10c2b59fc2cc19b2e869b116fc7
7
- data.tar.gz: 38594b7e1a5ef99db5ce279ecabc97957803cf099dd1c13183c44ceacd4ccbe8bfe07a522b4125ce3508ea5f5b56f84e6248106fce40c0c67a9e9b6d711954bd
6
+ metadata.gz: 6115d771857a6ea74ddf94c104fa0b9cdedbfeb67599b57a1c4c21576ad8639234b0d9b130d0349a29c11e63d2d324ad4d17e913655b3fa5c9f97a73ae8c5046
7
+ data.tar.gz: fe14aa3aefbd5a87f86fbbcf9ba68dd077f22d39006022d8f25d32e98589d5f92286e12f0f004c514b5a3f22458ac91fda1c27207c555bbb2dd0eaa1c861a530
data/README.md CHANGED
@@ -1,28 +1,185 @@
1
- # ActionviewAttributeBuilders
2
- Short description and motivation.
1
+ # Actionview::AttributeBuilders
2
+ > 🧪 ☣️ This gem is an experiment. Please do not use it for anything other than to explore the new concepts it brings into Rails.
3
3
 
4
- ## Usage
5
- How to use my plugin.
4
+ **New!**
6
5
 
7
- ## Installation
8
- Add this line to your application's Gemfile:
6
+ Visit the AttributeBuilders playground https://attributebuilders.julianpinzon.com to see all of this in action!
7
+
8
+ ## 🧑🏽‍💻 Installation
9
+
10
+ Install the gem in your Rails application. Use:
11
+ ```bash
12
+ $ bundle add actionview_attribute_builders
13
+ ```
14
+
15
+ Or add it directly in your Gemfile
9
16
 
10
17
  ```ruby
11
- gem "actionview_attribute_builders"
18
+ gem 'actionview_attribute_builders', '~> 0.1.0'
12
19
  ```
13
20
 
14
21
  And then execute:
15
22
  ```bash
16
23
  $ bundle
17
24
  ```
25
+ ## 🧑🏽‍🎨 Usage
18
26
 
19
- Or install it yourself as:
20
- ```bash
21
- $ gem install actionview_attribute_builders
27
+ **1. Create a custom form builder**
28
+
29
+ ```ruby
30
+ # app/form_builders/example_form_builder.rb
31
+
32
+ class ExampleFormBuilder < ActionView::Helpers::FormBuilder
33
+ end
34
+ ```
35
+
36
+ **2. Add a `text_field` method, overriding the one inherited from its parent (Rails' default)**
37
+
38
+ ```ruby
39
+ # app/form_builders/example_form_builder.rb
40
+
41
+ class ExampleFormBuilder < ActionView::Helpers::FormBuilder
42
+ def text_field(method, options = {})
43
+ # ...
44
+ end
45
+ end
22
46
  ```
23
47
 
48
+ **3. Use the appropriate `<helper>_attribute_builder` method for the type of field you're creating**
49
+
50
+ In this case `text_field_attribute_builder`
51
+
52
+ ```ruby
53
+ # app/form_builders/example_form_builder.rb
54
+
55
+ class ExampleFormBuilder < ActionView::Helpers::FormBuilder
56
+ def text_field(method, options = {})
57
+ # 1. Retreive an instance of the `AttributeBuilders::TextField` class via the helper method
58
+ attribute_builder = text_field_attribute_builder(method, options)
59
+ # 2. Use the `html_attributes` method to retrieve the computed attributes. Commonly, `id`, `name`, `value` etc.
60
+ html_attributes = attribute_builder.html_attributes
61
+ # => { id: "user_name", name: "user[name]", value: "Julian" }
62
+ end
63
+ end
64
+ ```
65
+
66
+ **4. Render the HTML form element**
67
+
68
+ ```ruby
69
+ # app/form_builders/example_form_builder.rb
70
+
71
+ class ExampleFormBuilder < ActionView::Helpers::FormBuilder
72
+ def text_field(method, options = {})
73
+ # ...
74
+ # 3. Use the attributes to create the markup.
75
+
76
+ # 3.1 For example, use a web component
77
+ @template.content_tag("example-text-field", nil, html_attributes.merge!(options))
78
+
79
+ # 3.1 Or a ViewComponent
80
+ @template.render(ExampleFieldComponent.new(html_attributes, options))
81
+ end
82
+ end
83
+ ```
84
+
85
+ **5. Use the new form builder in a template**
86
+
87
+ ```erb
88
+ <%= form_with model: @user, builder: ExampleFormBuilder do |form| %>
89
+ <%= form.text_field :name, required: true, label: "Name" %>
90
+ <% end %>
91
+ ```
92
+
93
+ **6. Check the output in your browser's inspector to verify the new markup**
94
+
95
+ Note how, just like in a plain Rails application, the field includes the conventional `name`, `type` and `id` attributes based on the model used.
96
+
97
+ ```html
98
+ <form action="/users" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="3EgNNhL-HUI-2gxV_-9T_cEaT8p6b4CWtVbQMCeHlaKlfgd_p9sFuuLVKaDkUt3gEQKhc_d7YdR-TFzp-LiAuA" autocomplete="off">
99
+ <example-text-field required="" type="text" name="user[name]" id="user_name"></example-text-field>
100
+ </form>
101
+
102
+ ```
103
+
104
+ **6. Do this for \*every form helper**
105
+
106
+ `number_field`, `password_field`, `checkbox` etc.
107
+
108
+ <small>* _every helper that's currently supported. Not all of them are done_<small>
109
+
110
+ ## 🏞️ Concrete examples
111
+
112
+ ### Playground
113
+ Visit the Attribute Builders playground https://attributebuilders.julianpinzon.com for an interactive preview of this gem in action.
114
+
115
+ ### Implementation guides
116
+
117
+ > 👀 Images inside!
118
+
119
+ Visit the [examples](/examples) folder and follow the guides on how to use this with real components using Shoelace and Material Design Web Components.
120
+
121
+ ## 🔧 API
122
+
123
+ This library exposes helpers that use _the exact same mechanics to create form input element attributes_ that Rails uses internally to create input elements that are compliant with `ActiveModel` and `ActiveRecord`conventions.
124
+
125
+ Check out the [lib/actionview_attribute_builders/attribute_builders_helper](AttributeBuildersHelper) for a list of all currently supported builders.
126
+
127
+ ## ☢️ Status
128
+
129
+ This library is **not finished**. There are several things missing. For example:
130
+ 1. Not all attribute builders have been extracted from Rails. There are currently 4 missing input fields and some extra missing helpers (like buttons and input submit fields)
131
+ 2. Some helpers are far more complex than others and probably can't be used yet in a real scenario. For example, `<select>` tags (and similar elements) are harder because they are composed of not one but multiple html elements (`<select>` and `<option>`). This problem is yet to be solved.
132
+ 3. The way the gem is loaded is a bit hacky and has a lot of garbage comments.
133
+
134
+ ### ⚙️ Current AttributeBuilders compatibility with ActionView Tags
135
+ | Attributer Builder | Available? |
136
+ |---------------------------|------------|
137
+ | check_box | ✅ |
138
+ | collection_check_boxes | ◻️ |
139
+ | collection_radio_buttons | ◻️ |
140
+ | collection_select | ✅ |
141
+ | color_field | ✅ |
142
+ | date_field | ✅ |
143
+ | date_select | ◻️ |
144
+ | datetime_field | ✅ |
145
+ | datetime_select | ◻️ |
146
+ | email_field | ✅ |
147
+ | file_field | ✅ |
148
+ | grouped_collection_select | ✅ |
149
+ | hidden_field | ✅ |
150
+ | label | ✅ |
151
+ | month_field | ✅ |
152
+ | number_field | ✅ |
153
+ | password_field | ✅ |
154
+ | radio_button | ✅ |
155
+ | range_field | ✅ |
156
+ | search_field | ✅ |
157
+ | select | ✅ |
158
+ | tel_field | ✅ |
159
+ | text_area | ✅ |
160
+ | text_field | ✅ |
161
+ | time_field | ✅ |
162
+ | time_select | ◻️ |
163
+ | time_zone_select | ✅ |
164
+ | url_field | ✅ |
165
+ | week_field | ✅ |
166
+ | weekday_select | ✅ |
167
+ | submit | ❌ |
168
+ | button | ❌ |
169
+
170
+ ## 💎 What problem is this solving?
171
+ The short version is that Rails has a lot of conventions for form fields to work seamlessly with `ActiveModel` and `ActiveRecord`. However, these are not exposed to developers; they are deeply nested and coupled to the rendering of the actual markup. This makes creating new `FormBuilders` notoriously hard or even impossible in some cases. This forces developers to abandon Rails' conventions which is not desirable.
172
+
173
+ This gem's objective is to separate those responsibilities so developers can leverage Rails' conventions to build custom form elements and keep enjoying the advantages of [convention over configuraiton](https://rubyonrails.org/doctrine#convention-over-configuration).
174
+
175
+ I have written extensively about this problem. If you're interested, please read the following:
176
+ 1. https://dev.julianpinzon.com/series/exploring-rails-forms
177
+ 2. https://github.com/ViewComponent/view_component/discussions/420#discussioncomment-867525
178
+
24
179
  ## Contributing
25
- Contribution directions go here.
180
+ The best way to contribute to this gem in its current state is:
181
+ 1. to experiment with it and build custom `FormBuilder` classes.
182
+ 2. start a discussion or issue on this repo
26
183
 
27
184
  ## License
28
185
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require "action_view/helpers/attribute_builders/submittable"
3
+
4
+ module ActionView
5
+ module Helpers
6
+ module AttributeBuilders # :nodoc:
7
+ class Button # :nodoc:
8
+ include Submittable
9
+
10
+ attr_reader :value
11
+
12
+ def initialize(value = nil, options = {}, object, object_name, template_object)
13
+ @object_name = object_name
14
+ @object = object
15
+ @template_object = template_object
16
+ @value = value
17
+ @options = options
18
+
19
+ case @value
20
+ when Hash
21
+ @options = @value
22
+ @value = nil
23
+ when Symbol
24
+ @options = { name: @template_object.field_name(@value), id: @template_object.field_id(@value) }.merge!(@options.to_h)
25
+ @value = nil
26
+ end
27
+
28
+ @value ||= submit_default_value
29
+
30
+ formmethod = @options[:formmethod]
31
+ if formmethod.present? && !/post|get/i.match?(formmethod) && !@options.key?(:name) && !@options.key?(:value)
32
+ @options.merge! formmethod: :post, name: "_method", value: formmethod
33
+ end
34
+ end
35
+
36
+ def html_attributes
37
+ @options
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require "action_view/helpers/attribute_builders/submittable"
3
+
4
+ module ActionView
5
+ module Helpers
6
+ module AttributeBuilders # :nodoc:
7
+ class Submit # :nodoc:
8
+ include Submittable
9
+
10
+ attr_reader :value
11
+
12
+ def initialize(value = nil, options = {}, object, object_name)
13
+ @object_name = object_name
14
+ @object = object
15
+ @value = value
16
+ @options = options
17
+
18
+ @value, @options = nil, @value if @value.is_a?(Hash)
19
+ @value ||= submit_default_value
20
+ end
21
+
22
+ def html_attributes
23
+ @options
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module AttributeBuilders # :nodoc:
6
+ module Submittable # :nodoc:
7
+ include ModelNaming
8
+
9
+ private
10
+ def submit_default_value
11
+ object = convert_to_model(@object)
12
+ key = object ? (object.persisted? ? :update : :create) : :submit
13
+
14
+ model = if object.respond_to?(:model_name)
15
+ object.model_name.human
16
+ else
17
+ @object_name.to_s.humanize
18
+ end
19
+
20
+ defaults = []
21
+ # Object is a model and it is not overwritten by as and scope option.
22
+ if object.respond_to?(:model_name) && @object_name.to_s == model.downcase
23
+ defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
24
+ else
25
+ defaults << :"helpers.submit.#{@object_name}.#{key}"
26
+ end
27
+ defaults << :"helpers.submit.#{key}"
28
+ defaults << "#{key.to_s.humanize} #{model}"
29
+
30
+ I18n.t(defaults.shift, model: model, default: defaults)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -26,36 +26,12 @@ require_relative "attribute_builders/range_field"
26
26
  require_relative "attribute_builders/url_field"
27
27
  require_relative "attribute_builders/week_field"
28
28
 
29
+ require_relative "attribute_builders/button"
30
+ require_relative "attribute_builders/submit"
31
+
29
32
  module ActionView
30
33
  module Helpers # :nodoc:
31
34
  module AttributeBuilders # :nodoc:
32
- # extend ActiveSupport::Autoload
33
- #
34
- # eager_autoload do
35
- # autoload :Base
36
- # autoload :CheckBox
37
- # autoload :ColorField
38
- # autoload :DateField
39
- # autoload :DatetimeField
40
- # autoload :DatetimeLocalField
41
- # autoload :EmailField
42
- # autoload :FileField
43
- # autoload :HiddenField
44
- # autoload :Label
45
- # autoload :MonthField
46
- # autoload :NumberField
47
- # autoload :RadioButton
48
- # autoload :SearchField
49
- # autoload :Select
50
- # autoload :TelField
51
- # autoload :TextArea
52
- # autoload :TextField
53
- # autoload :TimeField
54
- # autoload :PasswordField
55
- # autoload :RangeField
56
- # autoload :UrlField
57
- # autoload :WeekField
58
- # end
59
35
  end
60
36
  end
61
37
  end
@@ -1,39 +1,127 @@
1
1
  module ActionviewAttributeBuilders
2
2
  module AttributeBuildersHelper
3
+
4
+ def checkbox_attribute_builder(method, options, checked_value)
5
+ ActionView::Helpers::AttributeBuilders::CheckBox.new(object_name, method, @template, checked_value, options)
6
+ end
7
+
8
+ def collection_check_boxes_attribute_builder
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def collection_radio_buttons_attribute_builder
13
+ raise NotImplementedError
14
+ end
15
+
3
16
  def color_field_attribute_builder(method, options)
4
17
  ActionView::Helpers::AttributeBuilders::ColorField.new(object_name, method, @template, options)
5
18
  end
6
19
 
20
+ def date_field_attribute_builder(method, options)
21
+ ActionView::Helpers::AttributeBuilders::DateField.new(object_name, method, @template, options)
22
+ end
23
+
24
+ def date_select_attribute_builder
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def datetime_field_attribute_builder(method, options)
29
+ ActionView::Helpers::AttributeBuilders::DatetimeField.new(object_name, method, @template, options)
30
+ end
31
+
32
+ def datetime_select_attribute_builder
33
+ raise NotImplementedError
34
+ end
35
+
7
36
  def email_field_attribute_builder(method, options)
8
37
  ActionView::Helpers::AttributeBuilders::EmailField.new(object_name, method, @template, options)
9
38
  end
10
39
 
40
+ def file_field_attribute_builder(method, options)
41
+ ActionView::Helpers::AttributeBuilders::FileField.new(object_name, method, @template, options)
42
+ end
43
+
44
+ # grouped_collection_select
45
+
46
+ def hidden_field_attribute_builder(method, options)
47
+ ActionView::Helpers::AttributeBuilders::HiddenField.new(object_name, method, @template, options)
48
+ end
49
+
50
+ def label_attribute_builder(method, content_or_options, options)
51
+ ActionView::Helpers::AttributeBuilders::Label.new(object_name, method, @template, content_or_options, options)
52
+ end
53
+
54
+ def month_field_attribute_builder(method, options)
55
+ ActionView::Helpers::AttributeBuilders::MonthField.new(object_name, method, @template, options)
56
+ end
57
+
11
58
  def number_field_attribute_builder(method, options)
12
59
  ActionView::Helpers::AttributeBuilders::NumberField.new(object_name, method, @template, options)
13
60
  end
14
61
 
62
+ def password_field_attribute_builder(method, options)
63
+ ActionView::Helpers::AttributeBuilders::PasswordField.new(object_name, method, @template, options)
64
+ end
65
+
66
+ def radio_button_attribute_builder(method, tag_value, options)
67
+ ActionView::Helpers::AttributeBuilders::RadioButton.new(object_name, method, @template, tag_value, options)
68
+ end
69
+
70
+ def range_field_attribute_builder(method, options)
71
+ ActionView::Helpers::AttributeBuilders::RangeField.new(object_name, method, @template, options)
72
+ end
73
+
15
74
  def search_field_attribute_builder(method, options)
16
75
  ActionView::Helpers::AttributeBuilders::SearchField.new(object_name, method, @template, options)
17
76
  end
18
77
 
78
+ def select_attribute_builder(method, options, html_options)
79
+ ActionView::AttributeBuilders::AttributeBuilders::Select.new(object, method, @template, options, html_options)
80
+ end
81
+
19
82
  def tel_field_attribute_builder(method, options)
20
83
  ActionView::Helpers::AttributeBuilders::TelField.new(object_name, method, @template, options)
21
84
  end
22
85
 
86
+ def text_area_attribute_builder(method, options)
87
+ ActionView::Helpers::AttributeBuilders::TextArea.new(object_name, method, @template, options)
88
+ end
89
+
23
90
  def text_field_attribute_builder(method, options)
24
91
  ActionView::Helpers::AttributeBuilders::TextField.new(object_name, method, @template, options)
25
92
  end
26
93
 
27
- def password_field_attribute_builder(method, options)
28
- ActionView::Helpers::AttributeBuilders::PasswordField.new(object_name, method, @template, options)
94
+ def time_field_attribute_builder(method, options)
95
+ ActionView::Helpers::AttributeBuilders::TimeField.new(object_name, method, @template, options)
96
+ end
97
+
98
+ def time_select_attribute_builder
99
+ raise NotImplementedError
100
+ end
101
+
102
+ def time_zone_select_attribute_builder(method, options, html_options)
103
+ ActionView::Helpers::AttributeBuilders::Select.new(object_name, method, @template, options, html_options)
29
104
  end
30
105
 
31
106
  def url_field_attribute_builder(method, options)
32
107
  ActionView::Helpers::AttributeBuilders::UrlField.new(object_name, method, @template, options)
33
108
  end
34
109
 
35
- def checkbox_attribute_builder(method, options, checked_value)
36
- ActionView::Helpers::AttributeBuilders::CheckBox.new(object_name, method, @template, checked_value, options)
110
+ def week_field_attribute_builder(method, options)
111
+ ActionView::Helpers::AttributeBuilders::WeekField.new(object_name, method, @template, options)
37
112
  end
113
+
114
+ def weekday_select_attribute_builder(method, options, html_options)
115
+ ActionView::Helpers::AttributeBuilders::Select.new(object_name, method, @template, options, html_options)
116
+ end
117
+
118
+ def button_attribute_builder(value, options)
119
+ ActionView::Helpers::AttributeBuilders::Button.new(value, options, object_name, @template)
120
+ end
121
+
122
+ def submit_attribute_builder(value, options)
123
+ ActionView::Helpers::AttributeBuilders::Submit.new(value, options, object_name)
124
+ end
125
+
38
126
  end
39
127
  end
@@ -1,3 +1,3 @@
1
1
  module ActionviewAttributeBuilders
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionview_attribute_builders
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julián Pinzón Eslava
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-20 00:00:00.000000000 Z
11
+ date: 2023-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -38,6 +38,7 @@ files:
38
38
  - lib/actionview_attribute_builders.rb
39
39
  - lib/actionview_attribute_builders/attribute_builders.rb
40
40
  - lib/actionview_attribute_builders/attribute_builders/base.rb
41
+ - lib/actionview_attribute_builders/attribute_builders/button.rb
41
42
  - lib/actionview_attribute_builders/attribute_builders/check_box.rb
42
43
  - lib/actionview_attribute_builders/attribute_builders/checkable.rb
43
44
  - lib/actionview_attribute_builders/attribute_builders/color_field.rb
@@ -55,6 +56,8 @@ files:
55
56
  - lib/actionview_attribute_builders/attribute_builders/range_field.rb
56
57
  - lib/actionview_attribute_builders/attribute_builders/search_field.rb
57
58
  - lib/actionview_attribute_builders/attribute_builders/select.rb
59
+ - lib/actionview_attribute_builders/attribute_builders/submit.rb
60
+ - lib/actionview_attribute_builders/attribute_builders/submittable.rb
58
61
  - lib/actionview_attribute_builders/attribute_builders/tel_field.rb
59
62
  - lib/actionview_attribute_builders/attribute_builders/text_area.rb
60
63
  - lib/actionview_attribute_builders/attribute_builders/text_field.rb