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 +4 -4
- data/.DS_Store +0 -0
- data/Gemfile.lock +19 -1
- data/README.md +82 -168
- data/abyme.gemspec +4 -0
- data/lib/abyme.rb +1 -0
- data/lib/abyme/abyme_builder.rb +5 -10
- data/lib/abyme/controller.rb +15 -0
- data/lib/abyme/engine.rb +3 -0
- data/lib/abyme/model.rb +79 -5
- data/lib/abyme/version.rb +1 -1
- data/lib/abyme/view_helpers.rb +22 -11
- data/package.json +2 -2
- metadata +45 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb0f45e02d147c46725a521f0e789998a6c99172333daa039eb5cd0544805c52
|
4
|
+
data.tar.gz: f849cda4209753325ecbb737683b510c19f52fb22ac2aa72e095e67c56d5b24f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
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
|
-
#
|
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
|
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
|
-
<%=
|
12
|
-
<%= f.
|
13
|
-
<%= f.
|
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
|
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
|
-
##
|
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
|
-
|
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
|
-
|
81
|
+
### Model
|
76
82
|
|
77
|
-
|
83
|
+
💡 Don't forget to `include Abyme::Model` in your parent model
|
78
84
|
|
79
|
-
|
80
|
-
|
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
|
-
|
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
|
-
|
102
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
177
|
-
|
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
|
-
|
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
|
-
###
|
125
|
+
### Views
|
189
126
|
|
190
|
-
|
191
|
-
|
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
|
-
|
130
|
+
💡 Please note an id is automatically added to this element, which value is : `abyme--association_name`.
|
194
131
|
|
195
|
-
|
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
|
-
|
198
|
-
|
199
|
-
<%=
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
data/lib/abyme/abyme_builder.rb
CHANGED
@@ -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:,
|
11
|
+
def initialize(association:, form:, context:, partial:, &block)
|
12
12
|
@association = association
|
13
13
|
@form = form
|
14
|
-
@
|
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,
|
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,
|
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
data/lib/abyme/model.rb
CHANGED
@@ -1,11 +1,85 @@
|
|
1
1
|
module Abyme
|
2
2
|
module Model
|
3
|
-
|
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
|
-
|
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
data/lib/abyme/view_helpers.rb
CHANGED
@@ -3,7 +3,7 @@ require_relative "abyme_builder"
|
|
3
3
|
module Abyme
|
4
4
|
module ViewHelpers
|
5
5
|
|
6
|
-
#
|
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 #
|
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
|
-
# <%=
|
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,
|
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
|
-
# <%=
|
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 #
|
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
|
-
# <%=
|
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 #
|
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) :
|
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
|
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
|
+
"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": "^
|
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
|
+
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
|
+
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
|