actionview_attribute_builders 0.1.0 → 0.1.1

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: 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