abyme 0.4.0 → 0.5.0

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: 627dd83f44a40bcdf8ebd3355761ff9e1201c9d9c11e526e00acee11c685e479
4
- data.tar.gz: 476215a4ae3e062e79104f0351da75a090c74c25cbde16cdd26016e17cea08cb
3
+ metadata.gz: fb0f45e02d147c46725a521f0e789998a6c99172333daa039eb5cd0544805c52
4
+ data.tar.gz: f849cda4209753325ecbb737683b510c19f52fb22ac2aa72e095e67c56d5b24f
5
5
  SHA512:
6
- metadata.gz: bd5fa9ecb5bae8fdab1272017769649c4ca5945789f47a5c792c6240a2aa45366a335c231128e04feb57448e9275433a303df023ced40c5f26eaafefd03baf78
7
- data.tar.gz: 8d5728bd54f6e9292f36f9336e7b8157b1354e17e6348ce1fa07ad611c31c8d5f4d2550f494e4e4d00dfd136c5daf621a1cf6d1ba999c5f2625fc8e1110841b2
6
+ metadata.gz: a091fd6a3a798accdcc60a119e7b435d72ce553aea6896e041583d6df65d64157e1511031bad28a8d5b9042c2df3c3501400819cf3791eda7c2ae53618f776e5
7
+ data.tar.gz: f92d9395baad0cfa1a561a9c7936c9856b4b5049c5821040fbdb73aa55c9d444c9de2d5b7bdd59d70ce90e665967782311c9ba85250204f41d78bed644a24753
data/.DS_Store CHANGED
Binary file
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- abyme (0.4.0)
4
+ abyme (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -63,6 +63,7 @@ GEM
63
63
  zeitwerk (~> 2.2, >= 2.2.2)
64
64
  addressable (2.7.0)
65
65
  public_suffix (>= 2.0.2, < 5.0)
66
+ bindex (0.8.1)
66
67
  builder (3.2.4)
67
68
  capybara (3.33.0)
68
69
  addressable
@@ -73,6 +74,7 @@ GEM
73
74
  regexp_parser (~> 1.5)
74
75
  xpath (~> 3.2)
75
76
  childprocess (3.0.0)
77
+ coderay (1.1.3)
76
78
  concurrent-ruby (1.1.8)
77
79
  crass (1.0.6)
78
80
  database_cleaner (1.8.5)
@@ -82,6 +84,9 @@ GEM
82
84
  diff-lcs (1.4.4)
83
85
  docile (1.3.2)
84
86
  erubi (1.10.0)
87
+ generator_spec (0.9.4)
88
+ activesupport (>= 3.0.0)
89
+ railties (>= 3.0.0)
85
90
  globalid (0.4.2)
86
91
  activesupport (>= 4.2.0)
87
92
  i18n (1.8.8)
@@ -102,6 +107,11 @@ GEM
102
107
  nokogiri (1.11.1)
103
108
  mini_portile2 (~> 2.5.0)
104
109
  racc (~> 1.4)
110
+ pry (0.13.1)
111
+ coderay (~> 1.1)
112
+ method_source (~> 1.0)
113
+ pry-rails (0.3.9)
114
+ pry (>= 0.10.4)
105
115
  public_suffix (4.0.6)
106
116
  puma (5.0.4)
107
117
  nio4r (~> 2.0)
@@ -179,6 +189,11 @@ GEM
179
189
  thread_safe (0.3.6)
180
190
  tzinfo (1.2.9)
181
191
  thread_safe (~> 0.1)
192
+ web-console (4.1.0)
193
+ actionview (>= 6.0.0)
194
+ activemodel (>= 6.0.0)
195
+ bindex (>= 0.4.0)
196
+ railties (>= 6.0.0)
182
197
  webdrivers (4.4.1)
183
198
  nokogiri (~> 1.6)
184
199
  rubyzip (>= 1.3.0)
@@ -198,6 +213,8 @@ DEPENDENCIES
198
213
  bundler (~> 2.0)
199
214
  capybara
200
215
  database_cleaner-active_record
216
+ generator_spec
217
+ pry-rails
201
218
  puma
202
219
  rails
203
220
  rails-controller-testing
@@ -206,6 +223,7 @@ DEPENDENCIES
206
223
  simplecov
207
224
  simplecov-lcov
208
225
  sqlite3
226
+ web-console
209
227
  webdrivers
210
228
 
211
229
  BUNDLED WITH
data/README.md CHANGED
@@ -1,33 +1,27 @@
1
- # Abyme 🕳
1
+ # abyme 🕳
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/abyme.svg)](https://badge.fury.io/rb/abyme)
4
4
  ![build](https://github.com/bear-in-mind/abyme/workflows/build/badge.svg)
5
5
  [![Maintainability](https://api.codeclimate.com/v1/badges/f591a9e00f7cf5188ad5/maintainability)](https://codeclimate.com/github/bear-in-mind/abyme/maintainability)
6
6
  [![Coverage Status](https://coveralls.io/repos/github/bear-in-mind/abyme/badge.svg)](https://coveralls.io/github/bear-in-mind/abyme?branch=master)
7
7
 
8
- abyme makes it easy to handle nested attributes in Rails, using [stimulus](https://stimulusjs.org/handbook/introduction) under the hood. Here's an example :
8
+ abyme is an easy and framework-agnostic way to handle nested attributes in Rails, using [stimulus](https://stimulusjs.org/handbook/introduction) under the hood. Here's an example :
9
9
  ```ruby
10
10
  # views/projects/_form.html.erb
11
- <%= simple_form_for @project do |f| %>
12
- <%= f.input :title %>
13
- <%= f.input :description %>
11
+ <%= form_for @project do |f| %>
12
+ <%= f.text_field :title %>
13
+ <%= f.text_area :description %>
14
14
  <%= f.submit 'Save' %>
15
15
 
16
16
  <%= f.abyme_for(:tasks) %>
17
17
  <% end %>
18
18
  ```
19
- Supposing you have a partial located in `views/abyme/_task_fields` containing your fields for `tasks`, the `abyme_for` command will generate and display 3 elements in this order :
20
- - A div containing all task fields for `@project.tasks` (either persisted or already built instances of `tasks`)
21
- - A div which will contain all additional tasks about to be created (added through the `Add task` button below)
22
- - A button to generate fields for new instances of tasks
19
+ Supposing you have a `Project` that `has_many :tasks` and a partial located in `views/abyme/_task_fields` containing your form fields for `tasks`, the `abyme_for` command will generate and display 3 elements in this order :
20
+ - A `div` containing all task fields for `@project.tasks` (either persisted or already built instances of `tasks`)
21
+ - A `div` which will contain all additional tasks about to be created (added through the `Add task` button below)
22
+ - A `button` to generate fields for new instances of tasks
23
23
 
24
- Have a look below to learn more about the different options and needed configuration.
25
-
26
-
27
- ## Disclaimer
28
- This project is still a work in progress and subject to change. We would advise not to use it in production code just yet.
29
-
30
- Any enhancement proposition or bug report welcome !
24
+ Have a look below to learn more about configuration and all its different options.
31
25
 
32
26
  ## Demo app
33
27
 
@@ -37,6 +31,17 @@ Check out our demo app here : https://abyme-demo.herokuapp.com/
37
31
 
38
32
  Source code is right here : https://github.com/bear-in-mind/abyme_demo
39
33
 
34
+ ## Breaking changes
35
+ Careful ! As of February 12th, we changed quite a few methods name :
36
+ In model:
37
+ - `abyme_for` became `abymize`
38
+ In views:
39
+ - `abymize(:association, f)` became `f.abyme_for(:association)`
40
+ - `add_association` became `add_associated_record`
41
+ - `remove_association` became `remove_associated_record`
42
+
43
+ If you update, don't forget to change those ! All changes are reflected in the README below.
44
+
40
45
  ## Installation
41
46
 
42
47
  Add this line to your application's Gemfile:
@@ -66,154 +71,96 @@ application.load(definitionsFromContext(context))
66
71
  application.register('abyme', AbymeController)
67
72
  ```
68
73
 
69
- ## What are nested forms and why a new gem ?
74
+ ## Get started
75
+
76
+ To learn more about the *why* of this gem, check out our [wiki](https://github.com/bear-in-mind/abyme/wiki/What-are-nested-forms-and-why-a-new-gem-%3F)
70
77
 
71
- Nested forms (or more accurately *nested fields* or *nested attributes*) are forms that deal with associated models. Let's picture a `Project` model that `has_many :tasks`. A nested form will allow you to create a project along with one or several tasks **within a single form**. If `Tasks` were to have associations on their own, like `:comments`, you could also, still in the same form, instantiate comments along with their parent models.
78
+ You may also check out our [step by step tutorial](https://github.com/bear-in-mind/abyme/wiki/Step-by-step-Tutorial)
72
79
 
73
- Rails provides [its own helper](https://api.rubyonrails.org/v6.0.1/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for) to handle nested attributes. **abyme** is basically a smart wrapper around it, offering easier syntax along with some fancy additions. To work properly, some configuration will be required in both models and controllers (see below).
74
80
 
75
- What Rails doesn't provide natively is the possibility to **dynamically add new associations on the fly**, which requires Javascript implementation. What this means it that you would normally have to know in advance how many fields you'd like to display (1, 2 or any number of `:tasks`), which isn't very usable in this day and age. This is what the [cocoon gem](https://github.com/nathanvda/cocoon) has been helping with for the past 7 years. This gem still being implemented in JQuery (which [Rails dropped as a dependency](https://github.com/rails/rails/issues/25208)), we wanted to propose a more plug'n'play approach, using Basecamp's [Stimulus](https://stimulusjs.org/) instead.
81
+ ### Model
76
82
 
77
- ## Basic Configuration
83
+ 💡 Don't forget to `include Abyme::Model` in your parent model
78
84
 
79
- ### Models
80
- Let's consider a to-do application with Projects having many Taks, themselves having many Comments.
85
+ #### #abymize(:association, permit: nil, reject: nil, options = {})
86
+ In models, the `abyme_for :association` acts as an alias for this command :
81
87
  ```ruby
82
- # models/project.rb
83
- class Project < ApplicationRecord
84
- has_many :tasks
85
- validates :title, :description, presence: true
86
- end
87
-
88
- # models/task.rb
89
- class Task < ApplicationRecord
90
- belongs_to :project
91
- has_many :comments
92
- validates :title, :description, presence: true
93
- end
94
-
95
- # models/comment.rb
96
- class Comment < ApplicationRecord
97
- belongs_to :task
98
- validates :content, presence: true
99
- end
88
+ accepts_nested_attributes_for :association, reject_if: :all_blank, :allow_destroy: true
100
89
  ```
101
- The end-goal here is to be able to create a project along with different tasks, and immediately add comments to some of these tasks ; all within a single form.
102
- What we'll have is a 2-level nested form. Thus, we'll need to configure our `Project` and `Task` models like so :
90
+
91
+ * `permit: []` : allows you to generate a hash of attributes that can be easily called on the controller side through the `::abyme_attributes` class method (see details below).
103
92
  ```ruby
104
- # models/project.rb
105
- class Project < ApplicationRecord
106
- include Abyme::Model
107
- has_many :tasks, inverse_of: :project
108
- # ...
109
- abymize :tasks
110
- end
111
-
112
- # models/task.rb
113
- class Task < ApplicationRecord
114
- include Abyme::Model
115
- has_many :comments, inverse_of: :task
116
- # ...
117
- abymize :comments
118
- end
93
+ abymize :association, permit: [:name, :description]
94
+
95
+ # You may also permit all attributes like so :
96
+ abymize :association, permit: :all_attributes
119
97
  ```
120
- Note the use of the `inverse_of` option. It is needed for Rails to effectively associate children to their yet unsaved parent. Have a peek to the bottom of [this page](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for) for more info.
121
-
122
- ### Controller
123
- Since we're dealing with one form, we're only concerned with one controller : the one the form routes to. In our example, this would be the `ProjectsController`.
124
- The only configuration needed here will concern our strong params. Nested attributes require a very specific syntax to white-list the permitted attributes. It looks like this :
125
98
 
99
+ * `reject: []` : allows you to add all attributes to `::abyme_attributes`, excepted the ones specified.
126
100
  ```ruby
127
- def project_params
128
- params.require(:project).permit(
129
- :title, :description, tasks_attributes: [
130
- :id, :title, :description, :_destroy, comments_attributes: [
131
- :id, :content, :_destroy
132
- ]
133
- ]
134
- )
135
- end
101
+ abymize :association, reject: [:password]
136
102
  ```
137
- A few explanations here.
138
-
139
- * To permit a nested model attributes in your params, you'll need to pass the `association_attributes: [...]` hash at the end of your resource attributes. Key will always be `association_name` followed by `_attributes`, while the value will be an array of symbolized attributes, just like usual.
140
-
141
- > **Note**: if your association is a singular one (`has_one` or `belongs_to`) the association will be singular ; if a Project `has_one :owner`, you would then need to pass `owner_attributes: [...]`)
142
-
143
- * You may have remarked the presence of `id` and `_destroy` among those params. These are necessary for edit actions : if you want to allow your users to destroy or update existing records, these are **mandatory**. Otherwise, Rails won't be able to recognize these records as existing ones, and will just create new ones. More info [here](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html).
144
-
145
- ## Basic Usage
146
-
147
- Dealing with nested attributes means you'll generally have to handle a few things inside your form:
148
- * Display fields for the **persisted records** (here, already existing `:tasks`)
149
- * Display fields for the **new records** (future `:tasks` not yet persisted)
150
- * A button to **trigger the addition** of fields for a new resource (an `Add a new task` button)
151
- * A button to **remove fields** for a given resource (`Remove task`)
152
-
153
- abyme provides helper methods for all these. Here's how our form for `Project` looks like when using default values:
154
103
 
104
+ * `options: {}` : [the same options] you may pass to the `accepts_nested_attributes` method (see [this link](https://api.rubyonrails.org/v6.1.0/classes/ActiveRecord/NestedAttributes/ClassMethods.html) for details)
155
105
  ```ruby
156
- # views/projects/_form.html.erb
157
- <%= simple_form_for @project do |f| %>
158
- <%= f.input :title %>
159
- <%= f.input :description %>
160
- <%= f.submit 'Save' %>
161
-
162
- <%= f.abyme_for(:tasks) do |abyme| %>
163
- <%= abyme.records %>
164
- <%= abyme.new_records %>
165
- <%= add_associated_record %>
166
- <% end %>
167
- <% end %>
106
+ abyme_for :association, limit: 3, allow_destroy: false
168
107
  ```
169
108
 
170
- `abyme.records` will contain the persisted associations fields, while `abyme.new_records` will contain fields for the new associations. `add_associated_record` will by default generate a button with a text of type "Add `resource_name`". To work properly, this method **has** to be called **inside the block** passed to the `abyme_for` method.
171
-
172
- Now where's the code for these fields ? abyme will assume a **partial** to be present in the directory `/views/abyme` with a *name respecting this naming convention* (just like with [cocoon](https://github.com/nathanvda/cocoon#basic-usage)): `_singular_association_name_fields.html.erb`.
173
-
174
- This partial might look like this:
109
+ #### ::abyme_attributes
110
+ Returns a hash to the right format to be included in the `strong params` on the controller side. For a `Project` model with nested `:tasks` :
175
111
  ```ruby
176
- # views/abyme/_task_fields.html.erb
177
- <%= f.input :title %>
178
- <%= f.input :description %>
179
- <%= f.hidden_field :_destroy %>
180
-
181
- <%= remove_associated_record(tag: :div) do %>
182
- <i class="fas fa-trash"></i>
183
- <% end %>
112
+ Project.abyme_attributes
113
+ # => {tasks_attributes: [:title, :description, :id, :_destroy]}
184
114
  ```
185
115
 
186
- Note the presence of the `remove_associated_record` button. Here, we pass it an option to make it a `<div>`, as well as a block to customize its content. Don't forget the `_destroy` attribute, needed to mark items for destruction.
116
+ ### Controller
117
+ #### #abyme_attributes
118
+ Infers the name of the resource from the controller name, and calls the `::abyme_attributes` method on it. Hence, in your `ProjectsController` :
119
+ ```ruby
120
+ def project_params
121
+ params.require(:project).permit(:title, :description, abyme_attributes)
122
+ end
123
+ ```
187
124
 
188
- ### What about the controller ?
125
+ ### Views
189
126
 
190
- What about it ? Well, not much. That's the actual magical thing about `nested_attributes`: once your model is aware of its acceptance of those for a given association, and your strong params are correctly configured, there's nothing else to do.
191
- `@project.create(project_params)` is all you'll need to save a project along with its descendants 👨‍👧‍👧
127
+ #### #abyme_for(:association, options = {}, &block)
128
+ This is the container for all your nested fields. It takes the symbolized association as a parameter, along with options, and an optional block to specify any layout you may wish for the different parts of the `abyme` builder.
192
129
 
193
- ### Auto mode
130
+ 💡 Please note an id is automatically added to this element, which value is : `abyme--association_name`.
194
131
 
195
- Let's now take care of our comments fields. We'll add these using our neat *automatic mode*: just stick this line at the end of the partial :
132
+ 💡 If you don't pass a block, `records`, `new_records` and `add_association` will be called and will appear in this order in your layout.
133
+ * `partial: ` : allows you to indicate a custom partial path for both `records` and `new_records`
196
134
  ```ruby
197
- # views/abyme/_task_fields.html.erb
198
- # ... rest of the partial above
199
- <%= f.abyme_for(:comments) %>
135
+ <%= f.abyme_for(:tasks, partial: 'projects/task_fields') do |abyme| %>
136
+ <%= abyme.records %>
137
+ <%= abyme.new_records %>
138
+ <%= add_association %>
139
+ <% end %>
200
140
  ```
201
- Where's the rest of the code ? Well, if the default configuration you saw above in the `_form.html.erb` suits you, and the order in which the different resources appear feels right (persisted first, new fields second, and the 'Add' button last), then you can just spare the block, and it will be taken care of for you. We'll just write our `_comment_fields.html.erb` partial in the `views/abyme` directory and we'll be all set.
202
-
203
- ## Advanced usage
204
- ### Models
205
- In models, the `abyme_for :association` acts as an alias for this command :
206
-
141
+ * `limit: ` : allows you to limit the number of new fields that can be created through JS. If you need to limit the number of associations in database, you will need to add validations. You can also pass an option [in your model as well](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for).
207
142
  ```ruby
208
- accepts_nested_attributes_for :association, reject_if: :all_blank, :allow_destroy: true
143
+ <%= f.abyme_for(:tasks, limit: 5) do |abyme| %>
144
+ # Beyond 5 tasks, the add button won't add any more fields. See events section below to see how to handle the 'abyme:limit-reached' event
145
+ <%= abyme.records %>
146
+ <%= abyme.new_records %>
147
+ <%= add_association %>
148
+ <% end %>
209
149
  ```
210
-
211
- Which is the way you would configure `nested_attributes` 90% of the time. Should you want to pass [any available options](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for) to this method or change those, you may just pass them as with the original method :
150
+ * `min_count: ` by default, there won't be any blank fields added on page load. By passing a `min_count` option, you can set how many empty fields should appear in the form.
212
151
  ```ruby
213
- abyme_for :association, limit: 3, allow_destroy: false
152
+ <%= f.abyme_for(:tasks, min_count: 1) do |abyme| %>
153
+ # 1 blank task will automatically be added to the form.
154
+ <%= abyme.records %>
155
+ <%= abyme.new_records %>
156
+ <%= add_association %>
157
+ <% end %>
214
158
  ```
215
159
 
216
- ### Views
160
+ *If you're not passing a block*, the `abyme_for` method can take a few additional options:
161
+ * `button_text: ` this will set the `add_association` button text to the string of your choice.
162
+
163
+ 💡 All options that should be passed to either `records` or `new_records` below can be passed here and will be passed down.
217
164
 
218
165
  #### #records
219
166
  A few options can be passed to `abyme.records`:
@@ -250,7 +197,7 @@ A few options can be passed to `abyme.records`:
250
197
  <%= add_associated_record %>
251
198
  <% end %>
252
199
  ```
253
- * `wrapper_html:` : gives you the possibility to add any HTML attribute you may want to the wrapper containing all fields.
200
+ * `wrapper_html:` : gives you the possibility to add any HTML attribute you may want to the wrapper containing all persisted fields.
254
201
  ```ruby
255
202
  <%= f.abyme_for(:tasks) do |abyme| %>
256
203
  <%= abyme.records(wrapper_html: { class: "persisted-records" }) %>
@@ -296,39 +243,6 @@ As you may have seen above, you can also pass a block to the method to give it w
296
243
  <% end %>
297
244
  ```
298
245
 
299
- #### #abyme_for(:association, form_object)
300
- This is the container for all your nested fields. It takes two parameters (the symbolized association and the `form_builder`), and some optional ones. Please note an id is automatically added to this element, which value is : `abyme--association`.
301
- * `partial:` : allows you to indicate a custom partial path for both `records` and `new_records`
302
- ```ruby
303
- <%= f.abyme_for(:tasks, partial: 'projects/task_fields') do |abyme| %>
304
- <%= abyme.records %>
305
- <%= abyme.new_records %>
306
- <%= add_associated_record %>
307
- <% end %>
308
- ```
309
- * `limit:` : allows you to limit the number of new fields that can be created through JS. If you need to limit the number of associations in database, you will need to add validations. You can also pass an option [in your model as well](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for).
310
- ```ruby
311
- <%= f.abyme_for(:tasks, limit: 5) do |abyme| %>
312
- # Beyond 5 tasks, the add button won't add any more fields. See events section below to see how to handle the 'abyme:limit-reached' event
313
- <%= abyme.records %>
314
- <%= abyme.new_records %>
315
- <%= add_associated_record %>
316
- <% end %>
317
- ```
318
- * `min_count` : by default, there won't be any blank fields added on page load. By passing a `min_count` option, you can set how many empty fields should appear in the form.
319
- ```ruby
320
- <%= f.abyme_for(:tasks, min_count: 1) do |abyme| %>
321
- # 1 blank task will automatically be added to the form.
322
- <%= abyme.records %>
323
- <%= abyme.new_records %>
324
- <%= add_associated_record %>
325
- <% end %>
326
- ```
327
-
328
- *When in auto mode*, the abyme_for method can take a few options:
329
- * `button_text:` : this will set the `add_associated_record` button text to the string of your choice.
330
- * All options that should be passed to either `records` or `new_records` can be passed here and will be passed down.
331
-
332
246
  ## Events
333
247
  This part is still a work in progress and subject to change. We're providing some basic self-explanatory events to attach to. These are emitted by the main container (created by the `abyme_for` method).
334
248
 
data/abyme.gemspec CHANGED
@@ -34,10 +34,14 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency 'database_cleaner-active_record'
35
35
  spec.add_development_dependency 'capybara'
36
36
  spec.add_development_dependency 'webdrivers'
37
+ spec.add_development_dependency "generator_spec"
37
38
 
38
39
  # Dummy app
39
40
  spec.add_development_dependency "sqlite3"
40
41
  spec.add_development_dependency 'rails'
42
+ spec.add_development_dependency 'pry-rails'
43
+ spec.add_development_dependency 'web-console'
44
+
41
45
  spec.add_development_dependency 'puma'
42
46
  spec.add_development_dependency 'simplecov'
43
47
  spec.add_development_dependency 'simplecov-lcov'
data/lib/abyme.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "abyme/version"
2
2
  require 'abyme/view_helpers'
3
3
  require 'abyme/engine'
4
+ require 'abyme/controller'
4
5
  require 'abyme/action_view_extensions/builder'
5
6
 
6
7
  module Abyme
@@ -8,10 +8,11 @@ module Abyme
8
8
  # the form object, lookup_context optionaly a partial path
9
9
  # then yield itself to the block
10
10
 
11
- def initialize(association:, form:, lookup_context:, partial:, &block)
11
+ def initialize(association:, form:, context:, partial:, &block)
12
12
  @association = association
13
13
  @form = form
14
- @lookup_context = lookup_context
14
+ @context = context
15
+ @lookup_context = context.lookup_context
15
16
  @partial = partial
16
17
  yield(self) if block_given?
17
18
  end
@@ -23,7 +24,7 @@ module Abyme
23
24
 
24
25
  def records(options = {})
25
26
  persisted_records_for(@association, @form, options) do |fields_for_association|
26
- render_association_partial(fields_for_association, options)
27
+ render_association_partial(@association, fields_for_association, @partial, @context)
27
28
  end
28
29
  end
29
30
 
@@ -34,15 +35,9 @@ module Abyme
34
35
 
35
36
  def new_records(options = {}, &block)
36
37
  new_records_for(@association, @form, options) do |fields_for_association|
37
- render_association_partial(fields_for_association, options)
38
+ render_association_partial(@association, fields_for_association, @partial, @context)
38
39
  end
39
40
  end
40
41
 
41
- private
42
-
43
- def render_association_partial(fields, options)
44
- partial = @partial || options[:partial] || "abyme/#{@association.to_s.singularize}_fields"
45
- ActionController::Base.render(partial: partial, locals: { f: fields })
46
- end
47
42
  end
48
43
  end
@@ -0,0 +1,15 @@
1
+ module Abyme
2
+ module Controller
3
+ def abyme_attributes
4
+ return [] if resource_class.nil?
5
+
6
+ resource_class.abyme_attributes
7
+ end
8
+
9
+ private
10
+
11
+ def resource_class
12
+ self.class.name.match(/(.*)(Controller)/)[1].singularize.safe_constantize
13
+ end
14
+ end
15
+ end
data/lib/abyme/engine.rb CHANGED
@@ -6,6 +6,9 @@ module Abyme
6
6
  ActiveSupport.on_load :action_view do
7
7
  include Abyme::ViewHelpers
8
8
  end
9
+ ActiveSupport.on_load :action_controller do
10
+ include Abyme::Controller
11
+ end
9
12
  end
10
13
  end
11
14
  end
data/lib/abyme/model.rb CHANGED
@@ -1,11 +1,85 @@
1
1
  module Abyme
2
2
  module Model
3
- extend ActiveSupport::Concern
4
-
5
- class_methods do
6
- def abymize(association, options = {})
3
+ module ClassMethods
4
+ def abymize(association, permit: nil, reject: nil, **options)
7
5
  default_options = {reject_if: :all_blank, allow_destroy: true}
8
- accepts_nested_attributes_for association, default_options.merge(options)
6
+ nested_attributes_options = default_options.merge(options)
7
+ accepts_nested_attributes_for association, nested_attributes_options
8
+ # Save allow_destroy value for this model/association for later
9
+ save_destroy_option(association, nested_attributes_options[:allow_destroy])
10
+ Abyme::Model.permit_attributes(self.name, association, permit || reject, permit.present?) if permit.present? || reject.present?
11
+ end
12
+
13
+ def abyme_attributes
14
+ Abyme::Model.instance_variable_get(:@permitted_attributes)[self.name]
15
+ end
16
+
17
+ private
18
+
19
+ def save_destroy_option(association, value)
20
+ Abyme::Model.instance_variable_get(:@allow_destroy)[self.name][association] = value
21
+ end
22
+ end
23
+
24
+ @permitted_attributes ||= {}
25
+ @allow_destroy ||= {}
26
+
27
+ attr_accessor :allow_destroy
28
+ attr_reader :permitted_attributes
29
+
30
+ def self.permit_attributes(class_name, association, attributes, permit)
31
+ @permitted_attributes[class_name]["#{association}_attributes".to_sym] = AttributesBuilder.new(class_name, association, attributes, permit)
32
+ .build_attributes
33
+ end
34
+
35
+ def self.included(klass)
36
+ @permitted_attributes[klass.name] ||= {}
37
+ @allow_destroy[klass.name] ||= {}
38
+ klass.extend ClassMethods
39
+ end
40
+
41
+ class AttributesBuilder
42
+ def initialize(model, association, attributes, permit = true)
43
+ @model = model
44
+ @association = association
45
+ @attributes_list = attributes
46
+ @permit = permit
47
+ @association_class = @association.to_s.classify.constantize
48
+ end
49
+
50
+ def build_attributes
51
+ nested_attributes = @association_class.abyme_attributes if @association_class.respond_to? :abyme_attributes
52
+ authorized_attributes = build_default_attributes
53
+ if @permit && @attributes_list == :all_attributes
54
+ authorized_attributes = build_all_attributes(authorized_attributes, nested_attributes)
55
+ elsif @permit
56
+ @attributes_list << nested_attributes unless (nested_attributes.blank? || @attributes_list.include?(nested_attributes))
57
+ authorized_attributes += @attributes_list
58
+ else
59
+ authorized_attributes = build_all_attributes(authorized_attributes, nested_attributes)
60
+ authorized_attributes -= @attributes_list
61
+ end
62
+ authorized_attributes
63
+ end
64
+
65
+ def destroy_allowed?
66
+ Abyme::Model.instance_variable_get(:@allow_destroy).dig(@model, @association)
67
+ end
68
+
69
+ def add_all_attributes
70
+ @association_class.column_names.map(&:to_sym).reject { |attr| [:id, :created_at, :updated_at].include?(attr) }
71
+ end
72
+
73
+ def build_all_attributes(authorized_attributes, nested_attributes)
74
+ authorized_attributes += add_all_attributes
75
+ authorized_attributes << nested_attributes unless (nested_attributes.blank? || authorized_attributes.include?(nested_attributes))
76
+ authorized_attributes
77
+ end
78
+
79
+ def build_default_attributes
80
+ attributes = [:id]
81
+ attributes << :_destroy if destroy_allowed?
82
+ attributes
9
83
  end
10
84
  end
11
85
  end
data/lib/abyme/version.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  module Abyme
3
3
  module VERSION
4
4
  MAJOR = 0
5
- MINOR = 4
5
+ MINOR = 5
6
6
  PATCH = 0
7
7
 
8
8
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -3,7 +3,7 @@ require_relative "abyme_builder"
3
3
  module Abyme
4
4
  module ViewHelpers
5
5
 
6
- # ABYMIZE
6
+ # ABYME_FOR
7
7
 
8
8
  # this helper will generate the top level wrapper markup
9
9
  # with the bare minimum html attributes (data-controller="abyme")
@@ -23,12 +23,12 @@ module Abyme
23
23
  # set the default number of blank fields to display
24
24
 
25
25
  # - partial (String)
26
- # to customize the partial path by default #abymize will expect
26
+ # to customize the partial path by default #abyme_for will expect
27
27
  # a partial to bbe present in views/abyme
28
28
 
29
29
  # - Exemple
30
30
 
31
- # <%= abymize(:tasks, f, limit: 3) do |abyme| %>
31
+ # <%= abyme_for(:tasks, f, limit: 3) do |abyme| %>
32
32
  # ...
33
33
  # <% end %>
34
34
 
@@ -42,7 +42,7 @@ module Abyme
42
42
  content_tag(:div, data: { controller: 'abyme', limit: options[:limit], min_count: options[:min_count] }, id: "abyme--#{association}") do
43
43
  if block_given?
44
44
  yield(Abyme::AbymeBuilder.new(
45
- association: association, form: form, lookup_context: self.lookup_context, partial: options[:partial]
45
+ association: association, form: form, context: self, partial: options[:partial]
46
46
  )
47
47
  )
48
48
  else
@@ -62,7 +62,7 @@ module Abyme
62
62
  # then a hash of options.
63
63
 
64
64
  # - Exemple
65
- # <%= abymize(:tasks, f) do |abyme| %>
65
+ # <%= abyme_for(:tasks, f) do |abyme| %>
66
66
  # <%= abyme.new_records %>
67
67
  # ...
68
68
  # <% end %>
@@ -85,7 +85,7 @@ module Abyme
85
85
  # :end is the default value
86
86
 
87
87
  # - partial (String)
88
- # to customize the partial path by default #abymize will expect
88
+ # to customize the partial path by default #abyme_for will expect
89
89
  # a partial to bbe present in views/abyme
90
90
 
91
91
  # - fields_html (Hash)
@@ -113,7 +113,8 @@ module Abyme
113
113
  form.fields_for association, association.to_s.classify.constantize.new, child_index: 'NEW_RECORD' do |f|
114
114
  content_tag(:div, build_attributes(fields_default, basic_fields_markup(options[:fields_html], association))) do
115
115
  # Here, if a block is passed, we're passing the association fields to it, rather than the form itself
116
- block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
116
+ # block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
117
+ block_given? ? yield(f) : render_association_partial(association, f, options[:partial])
117
118
  end
118
119
  end
119
120
  end
@@ -128,7 +129,7 @@ module Abyme
128
129
  # then a hash of options.
129
130
 
130
131
  # - Exemple
131
- # <%= abymize(:tasks, f) do |abyme| %>
132
+ # <%= abyme_for(:tasks, f) do |abyme| %>
132
133
  # <%= abyme.records %>
133
134
  # ...
134
135
  # <% end %>
@@ -151,7 +152,7 @@ module Abyme
151
152
  # ex: order: { created_at: :desc }
152
153
 
153
154
  # - partial (String)
154
- # to customize the partial path by default #abymize will expect
155
+ # to customize the partial path by default #abyme_for will expect
155
156
  # a partial to bbe present in views/abyme
156
157
 
157
158
  # - fields_html (Hash)
@@ -178,7 +179,7 @@ module Abyme
178
179
  content_tag(:div, options[:wrapper_html]) do
179
180
  form.fields_for(association, records) do |f|
180
181
  content_tag(:div, build_attributes(fields_default, basic_fields_markup(options[:fields_html], association))) do
181
- block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
182
+ block_given? ? yield(f) : render_association_partial(association, f, options[:partial])
182
183
  end
183
184
  end
184
185
  end
@@ -262,9 +263,19 @@ module Abyme
262
263
  # Add new data attributes (keys & values)
263
264
  default[:data] = default[:data].merge(attr[:data].reject { |key, _| default[:data][key] })
264
265
  end
265
- # Merge data attributes to the hash ok html attributes
266
+ # Merge data attributes to the hash of html attributes
266
267
  default.merge(attr.reject { |key, _| key == :data })
267
268
  end
268
269
 
270
+ # RENDER PARTIAL
271
+
272
+ # renders a partial based on the passed path, or will expect a partial to be found in the views/abyme directory.
273
+
274
+ def render_association_partial(association, form, partial = nil, context = nil)
275
+ partial_path = partial ||"abyme/#{association.to_s.singularize}_fields"
276
+ context ||= self
277
+ context.render(partial: partial_path, locals: {f: form})
278
+ end
279
+
269
280
  end
270
281
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abyme",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "JS companion to abyme gem",
5
5
  "main": "javascript/index.js",
6
6
  "files": [
@@ -20,6 +20,6 @@
20
20
  "homepage": "https://github.com/bear-in-mind/abyme",
21
21
  "bugs": "https://github.com/bear-in-mind/abyme/issues",
22
22
  "dependencies": {
23
- "stimulus": "^1.1.1"
23
+ "stimulus": "^2.0.0"
24
24
  }
25
25
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abyme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Romain Sanson
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-02-12 00:00:00.000000000 Z
12
+ date: 2021-02-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -109,6 +109,20 @@ dependencies:
109
109
  - - ">="
110
110
  - !ruby/object:Gem::Version
111
111
  version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: generator_spec
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
112
126
  - !ruby/object:Gem::Dependency
113
127
  name: sqlite3
114
128
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +151,34 @@ dependencies:
137
151
  - - ">="
138
152
  - !ruby/object:Gem::Version
139
153
  version: '0'
154
+ - !ruby/object:Gem::Dependency
155
+ name: pry-rails
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: web-console
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
140
182
  - !ruby/object:Gem::Dependency
141
183
  name: puma
142
184
  requirement: !ruby/object:Gem::Requirement
@@ -207,6 +249,7 @@ files:
207
249
  - lib/abyme.rb
208
250
  - lib/abyme/abyme_builder.rb
209
251
  - lib/abyme/action_view_extensions/builder.rb
252
+ - lib/abyme/controller.rb
210
253
  - lib/abyme/engine.rb
211
254
  - lib/abyme/model.rb
212
255
  - lib/abyme/version.rb