formify 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -1
- data/README.md +175 -1
- data/formify.gemspec +1 -0
- data/lib/formify/version.rb +1 -1
- data/lib/generators/form/form_generator.rb +60 -4
- data/lib/generators/form/templates/form.rb.tt +1 -1
- data/lib/generators/form/templates/form_spec.rb.tt +6 -8
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '09b226440478b6b89edbee6d137934281da2c347fd48681e5b205eb4039e0582'
|
4
|
+
data.tar.gz: 85399692540ed00fe2f26f88273f300a9a7bdaa1a7c82c95102f3a59b89d8aa2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6646b9167f6ab241864284b64e88b3ec851e4fa7a4c00f08597e53e32bfbdc1a50a084ef65152bfa1ed9796c51f74f276652da618195f4189797fe92784de282
|
7
|
+
data.tar.gz: 3af2ea3e9492d8cd839b2198bb96153fa18b71ce4890e40407cfea4b1a07b3741d3a8d0130e4c94599db6868e502f433adc9350f93fdf4d9f95c0b626f28a626
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
formify (0.
|
4
|
+
formify (0.16.0)
|
5
5
|
rails
|
6
6
|
resonad
|
7
|
+
with_advisory_lock
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
@@ -130,6 +131,8 @@ GEM
|
|
130
131
|
websocket-driver (0.7.0)
|
131
132
|
websocket-extensions (>= 0.1.0)
|
132
133
|
websocket-extensions (0.1.3)
|
134
|
+
with_advisory_lock (4.0.0)
|
135
|
+
activerecord (>= 4.2)
|
133
136
|
|
134
137
|
PLATFORMS
|
135
138
|
ruby
|
data/README.md
CHANGED
@@ -20,7 +20,181 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
Once installed, use `include Formify::Form` into any class to give it the form structure.
|
23
|
+
Once installed, use `include Formify::Form` into any class to give it the form structure. In rspec tests, you can `require 'formify/spec_helpers` and then `include Formify::SpecHelpers`. This gives you access to [a number of helper methods](lib/formify/spec_helpers.rb).
|
24
|
+
|
25
|
+
## Generators
|
26
|
+
|
27
|
+
Formify ships with a powerful generator, giving you some default scaffolding. It's designed to generate everything for you, letting you know what's optional and can be removed. You can generate forms and their respective spec files using the following:
|
28
|
+
|
29
|
+
`rails generate form ATTRIBUTES`
|
30
|
+
|
31
|
+
`ATTRIBUTES` is a list of form attributes and their delegates. Formify takes advantage of existing objects (e.g. models and other forms) and their attribute reading and writing functionality. For example, if you were to do the following:
|
32
|
+
|
33
|
+
`rails generate form widgets/create foo:bar,baz owner:owner_attribute created_by`
|
34
|
+
|
35
|
+
You would get the following files:
|
36
|
+
|
37
|
+
- `app/lib/forms/widgets/create.rb`
|
38
|
+
- `spec/lib/forms/widgets/create_spec.rb`
|
39
|
+
|
40
|
+
### Generated Form
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# frozen_string_literal: true
|
44
|
+
|
45
|
+
module Forms
|
46
|
+
module Widgets
|
47
|
+
class Create
|
48
|
+
include Formify::Form
|
49
|
+
|
50
|
+
attr_accessor :created_by,
|
51
|
+
:foo,
|
52
|
+
:owner
|
53
|
+
|
54
|
+
delegate_accessor :bar,
|
55
|
+
:baz,
|
56
|
+
to: :foo
|
57
|
+
delegate_accessor :owner_attribute,
|
58
|
+
to: :owner
|
59
|
+
|
60
|
+
validates_presence_of :bar,
|
61
|
+
:baz,
|
62
|
+
:created_by,
|
63
|
+
:foo,
|
64
|
+
:owner,
|
65
|
+
:owner_attribute
|
66
|
+
|
67
|
+
# validate :validate_something
|
68
|
+
|
69
|
+
initialize_with :created_by, :foo, :owner do |attributes|
|
70
|
+
puts attributes
|
71
|
+
end
|
72
|
+
|
73
|
+
def save
|
74
|
+
raise NotImplementedError
|
75
|
+
|
76
|
+
with_advisory_lock_transaction(:foo) do
|
77
|
+
validate_or_fail
|
78
|
+
.and_then { create_widget }
|
79
|
+
.and_then { success(created_by) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def create_widget
|
86
|
+
end
|
87
|
+
|
88
|
+
# def validate_something
|
89
|
+
# end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
### Generated Spec
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
# frozen_string_literal: true
|
99
|
+
|
100
|
+
require 'rails_helper'
|
101
|
+
require 'formify/spec_helpers'
|
102
|
+
|
103
|
+
describe Forms::Widgets::Create, type: :form do
|
104
|
+
include Formify::SpecHelpers
|
105
|
+
|
106
|
+
# :attributes is used to initialize the form.
|
107
|
+
# These values should result in a valid form.
|
108
|
+
# You can override these in blocks or use let(:attributes_override) { { foo: bar } }
|
109
|
+
let(:attributes) do
|
110
|
+
{
|
111
|
+
bar: BAR_VALUE,
|
112
|
+
baz: BAZ_VALUE,
|
113
|
+
created_by: CREATED_BY_VALUE,
|
114
|
+
foo: FOO_VALUE,
|
115
|
+
owner: OWNER_VALUE,
|
116
|
+
owner_attribute: OWNER_ATTRIBUTE_VALUE
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
it { expect_valid } # Expect the form to be valid
|
121
|
+
it { expect(result).to be_success }
|
122
|
+
it { expect(value).to be_a(Widget) } # Model name inferred
|
123
|
+
|
124
|
+
context '#bar' do
|
125
|
+
# Attribute: :bar
|
126
|
+
it { expect_error_with_missing_attribute(:bar) }
|
127
|
+
xit { expect_error_with_attribute_value(:bar, BAR_BAD_VALUE, message: nil) } # :message is optional
|
128
|
+
xit { expect_valid_with_attribute_value(:bar, BAR_GOOD_VALUE) }
|
129
|
+
end
|
130
|
+
|
131
|
+
context '#baz' do
|
132
|
+
# Attribute: :baz
|
133
|
+
it { expect_error_with_missing_attribute(:baz) }
|
134
|
+
xit { expect_error_with_attribute_value(:baz, BAZ_BAD_VALUE, message: nil) } # :message is optional
|
135
|
+
xit { expect_valid_with_attribute_value(:baz, BAZ_GOOD_VALUE) }
|
136
|
+
end
|
137
|
+
|
138
|
+
context '#created_by' do
|
139
|
+
# Attribute: :created_by
|
140
|
+
it { expect_error_with_missing_attribute(:created_by) }
|
141
|
+
xit { expect_error_with_attribute_value(:created_by, CREATED_BY_BAD_VALUE, message: nil) } # :message is optional
|
142
|
+
xit { expect_valid_with_attribute_value(:created_by, CREATED_BY_GOOD_VALUE) }
|
143
|
+
end
|
144
|
+
|
145
|
+
context '#foo' do
|
146
|
+
# Attribute: :foo
|
147
|
+
it { expect_error_with_missing_attribute(:foo) }
|
148
|
+
xit { expect_error_with_attribute_value(:foo, FOO_BAD_VALUE, message: nil) } # :message is optional
|
149
|
+
xit { expect_valid_with_attribute_value(:foo, FOO_GOOD_VALUE) }
|
150
|
+
end
|
151
|
+
|
152
|
+
context '#owner' do
|
153
|
+
# Attribute: :owner
|
154
|
+
it { expect_error_with_missing_attribute(:owner) }
|
155
|
+
xit { expect_error_with_attribute_value(:owner, OWNER_BAD_VALUE, message: nil) } # :message is optional
|
156
|
+
xit { expect_valid_with_attribute_value(:owner, OWNER_GOOD_VALUE) }
|
157
|
+
end
|
158
|
+
|
159
|
+
context '#owner_attribute' do
|
160
|
+
# Attribute: :owner_attribute
|
161
|
+
it { expect_error_with_missing_attribute(:owner_attribute) }
|
162
|
+
xit { expect_error_with_attribute_value(:owner_attribute, OWNER_ATTRIBUTE_BAD_VALUE, message: nil) } # :message is optional
|
163
|
+
xit { expect_valid_with_attribute_value(:owner_attribute, OWNER_ATTRIBUTE_GOOD_VALUE) }
|
164
|
+
end
|
165
|
+
|
166
|
+
# Other Expectation Helpers
|
167
|
+
# xit { expect_error_message(message) }
|
168
|
+
# xit { expect_error_with_attribute(attribute) }
|
169
|
+
# xit { expect_not_valid(attribute: nil, message: nil) } # :attribute and :message are optional
|
170
|
+
end
|
171
|
+
|
172
|
+
```
|
173
|
+
|
174
|
+
### Options
|
175
|
+
|
176
|
+
- `pluralize_collection` (default: `true`) - Pluralize the collection as per naming conventions below.
|
177
|
+
|
178
|
+
## Transactions
|
179
|
+
|
180
|
+
Formify works with ClosureTree/with_advisory_lock to offer easy and intuitive locking, with or without transactions.
|
181
|
+
|
182
|
+
## Naming Conventions
|
183
|
+
|
184
|
+
### Form Name
|
185
|
+
Formify assumes that every form is an action. As such, it's best to use verbs like `create`, `update`, `destroy`, `upsert`, `find`, `process`, etc.
|
186
|
+
|
187
|
+
### Form Collection
|
188
|
+
The parent folder of a form should be the plural of the object it operates on. For example, if you had a `User` model, you would have `users/create.rb`. The collection does not need to be a model. It can be any noun in the plural form, like `sessions/create.rb`
|
189
|
+
|
190
|
+
### Collection Scopes
|
191
|
+
|
192
|
+
Any folder before the collection is considered a scope and serves to help you group collections and forms. Consider an application that has a end-user and an admin, and accounts need to be approved. You would probably consider the following actions to need separate functionality:
|
193
|
+
|
194
|
+
- `/forms/admin/accounts/create.rb` - This one may take an `approved_by` attribute.
|
195
|
+
- `/forms/accounts/create.rb` - This one would result in account pending approval
|
196
|
+
|
197
|
+
Scoping helps you keep your
|
24
198
|
|
25
199
|
## Development
|
26
200
|
|
data/formify.gemspec
CHANGED
data/lib/formify/version.rb
CHANGED
@@ -11,6 +11,20 @@ class FormGenerator < Rails::Generators::NamedBase
|
|
11
11
|
required: true,
|
12
12
|
description: 'A list of attributes and their delegates: foo:bar,baz'
|
13
13
|
|
14
|
+
class_option :pluralize_collection,
|
15
|
+
default: true,
|
16
|
+
description: 'Disable naming convention transformations like forcing plural collections',
|
17
|
+
type: :boolean
|
18
|
+
|
19
|
+
def transform_naming
|
20
|
+
return unless pluralize_collection?
|
21
|
+
|
22
|
+
self.name = begin
|
23
|
+
names = name.split('/')
|
24
|
+
names[0..-3].append(names[-2].pluralize).append(names[-1]).join('/')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
14
28
|
def validate_fixed_attrs
|
15
29
|
return unless duplicate_attributes.any?
|
16
30
|
|
@@ -18,11 +32,11 @@ class FormGenerator < Rails::Generators::NamedBase
|
|
18
32
|
end
|
19
33
|
|
20
34
|
def generate_form
|
21
|
-
template 'form.rb', File.join('app/lib/forms',
|
35
|
+
template 'form.rb', File.join('app/lib/forms', transformed_class_path, "#{file_name}.rb")
|
22
36
|
end
|
23
37
|
|
24
38
|
def generate_form_spec
|
25
|
-
template 'form_spec.rb', File.join('spec/lib/forms',
|
39
|
+
template 'form_spec.rb', File.join('spec/lib/forms', transformed_class_path, "#{file_name}_spec.rb")
|
26
40
|
end
|
27
41
|
|
28
42
|
private
|
@@ -47,6 +61,14 @@ class FormGenerator < Rails::Generators::NamedBase
|
|
47
61
|
]
|
48
62
|
end
|
49
63
|
|
64
|
+
def collection
|
65
|
+
@collection ||= split_name[-2].pluralize.underscore
|
66
|
+
end
|
67
|
+
|
68
|
+
def collection_name
|
69
|
+
@collection_name ||= collection.camelcase
|
70
|
+
end
|
71
|
+
|
50
72
|
def delegated_attributes
|
51
73
|
@delegated_attributes ||= attributes_and_delegates
|
52
74
|
.select { |_k, v| v.sort!.present? }
|
@@ -59,8 +81,16 @@ class FormGenerator < Rails::Generators::NamedBase
|
|
59
81
|
.map(&:first)
|
60
82
|
end
|
61
83
|
|
84
|
+
def form
|
85
|
+
@form ||= name.split('/').last.to_s.underscore
|
86
|
+
end
|
87
|
+
|
88
|
+
def form_name
|
89
|
+
@form_name ||= form.camelcase
|
90
|
+
end
|
91
|
+
|
62
92
|
def inferred_model_name
|
63
|
-
@inferred_model_name ||=
|
93
|
+
@inferred_model_name ||= collection.singularize.camelcase
|
64
94
|
end
|
65
95
|
|
66
96
|
def module_namespacing(&block)
|
@@ -72,7 +102,33 @@ class FormGenerator < Rails::Generators::NamedBase
|
|
72
102
|
end
|
73
103
|
|
74
104
|
def modules
|
75
|
-
@modules ||= ['Forms'] +
|
105
|
+
@modules ||= ['Forms'] + split_name[0..-2].map(&:to_s).map(&:camelcase)
|
106
|
+
end
|
107
|
+
|
108
|
+
def pluralize_collection?
|
109
|
+
@pluralize_collection ||= options[:pluralize_collection]
|
110
|
+
end
|
111
|
+
|
112
|
+
def scopes
|
113
|
+
@scopes ||= split_name[0..-3].map(&:underscore)
|
114
|
+
end
|
115
|
+
|
116
|
+
def scope_names
|
117
|
+
@scope_names ||= scopes.map(&:camelcase)
|
118
|
+
end
|
119
|
+
|
120
|
+
def split_name
|
121
|
+
@split_name ||= name.split('/')
|
122
|
+
end
|
123
|
+
|
124
|
+
def transformed_class_path
|
125
|
+
@transformed_class_path ||= begin
|
126
|
+
if pluralize_collection?
|
127
|
+
class_path[0..-2].append(class_path[-1].pluralize)
|
128
|
+
else
|
129
|
+
class_path
|
130
|
+
end
|
131
|
+
end
|
76
132
|
end
|
77
133
|
|
78
134
|
def variablefy(val)
|
@@ -21,17 +21,15 @@ describe Forms::<%= class_name %>, type: :form do
|
|
21
21
|
it { expect(result).to be_success }
|
22
22
|
it { expect(value).to be_a(<%= inferred_model_name %>) } # Model name inferred
|
23
23
|
|
24
|
-
### START: Attribute Expectations
|
25
|
-
|
26
24
|
<% all_attributes.each do |attr| -%>
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
context '#<%= attr %>' do
|
26
|
+
# Attribute: :<%= attr %>
|
27
|
+
it { expect_error_with_missing_attribute(:<%= attr %>) }
|
28
|
+
xit { expect_error_with_attribute_value(:<%= attr %>, <%= attr.upcase %>_BAD_VALUE, message: nil) } # :message is optional
|
29
|
+
xit { expect_valid_with_attribute_value(:<%= attr %>, <%= attr.upcase %>_GOOD_VALUE) }
|
30
|
+
end
|
31
31
|
|
32
32
|
<% end -%>
|
33
|
-
### END: Attribute Expectations
|
34
|
-
|
35
33
|
# Other Expectation Helpers
|
36
34
|
# xit { expect_error_message(message) }
|
37
35
|
# xit { expect_error_with_attribute(attribute) }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: formify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Jackson
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: with_advisory_lock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description: Formify acts as an abstract class, allowing you to easily create robust
|
84
98
|
form objects and test them using rspec.
|
85
99
|
email:
|