gourami 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +83 -10
- data/lib/gourami.rb +8 -2
- data/lib/gourami/attributes.rb +29 -26
- data/lib/gourami/configuration_error.rb +4 -0
- data/lib/gourami/error.rb +4 -0
- data/lib/gourami/extensions/changes.rb +69 -0
- data/lib/gourami/not_watching_changes_error.rb +4 -0
- data/lib/gourami/required_attribute_error.rb +1 -1
- data/lib/gourami/validation_error.rb +1 -1
- data/lib/gourami/version.rb +1 -1
- data/lib/gourami/watch_changes_error.rb +4 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f97edb63e52af97e6e63f41a74a824a04da00c21
|
4
|
+
data.tar.gz: 74bff6e2f3563c96205fc155f0b21db04df37169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2ea15a122976499b06265949d859767170c66d7647bac35861a9bea9e8b95f2090da92bd8f6ee99f7677c59eb5ce47ab59d25e21922a2a6a2dd4698cd279d4e
|
7
|
+
data.tar.gz: a38f7c552c0939c3122171945e855e4edff4482206e8c4e1a5b82d9100a92d1de2ce462bffbee147804fdb763ecc042dd550f441cee909f26b54922e2cd3e78c
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Gourami
|
2
2
|
|
3
|
-
Keep your Routes, Controllers and Models thin.
|
3
|
+
Keep your Routes, Controllers and Models thin with Plain Old Ruby Objects (PORO).
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -22,9 +22,9 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
### A Typical Gourami::Form will
|
24
24
|
|
25
|
-
-
|
26
|
-
-
|
27
|
-
-
|
25
|
+
- Define some attributes
|
26
|
+
- Validate user input
|
27
|
+
- Perform an action
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
class TypicalForm < Gourami::Form
|
@@ -32,7 +32,7 @@ class TypicalForm < Gourami::Form
|
|
32
32
|
attribute(:typical_attribute)
|
33
33
|
|
34
34
|
def validate
|
35
|
-
# Define
|
35
|
+
# Define your validation rules here
|
36
36
|
end
|
37
37
|
|
38
38
|
def perform
|
@@ -67,7 +67,6 @@ end
|
|
67
67
|
class CreateFishBowl < Gourami::Form
|
68
68
|
|
69
69
|
record(:fish_bowl)
|
70
|
-
|
71
70
|
attribute(:width, type: :integer)
|
72
71
|
attribute(:height, type: :integer)
|
73
72
|
attribute(:liters, type: :float)
|
@@ -120,10 +119,9 @@ end
|
|
120
119
|
### Example of a form that Updates a record
|
121
120
|
|
122
121
|
```ruby
|
123
|
-
class UpdateFishBowl <
|
122
|
+
class UpdateFishBowl < Gourami::Form
|
124
123
|
|
125
124
|
record(:fish_bowl)
|
126
|
-
|
127
125
|
attribute(:width, type: :integer)
|
128
126
|
attribute(:height, type: :integer)
|
129
127
|
attribute(:liters, type: :float)
|
@@ -131,7 +129,7 @@ class UpdateFishBowl < CreateFishBowl
|
|
131
129
|
attribute(:filter_included, type: :boolean, default: false)
|
132
130
|
|
133
131
|
def self.new_from_record(fish_bowl)
|
134
|
-
new(
|
132
|
+
new(fish_bowl.attributes.merge(fish_bowl: fish_bowl))
|
135
133
|
end
|
136
134
|
|
137
135
|
def validate
|
@@ -165,7 +163,7 @@ class UpdateFishBowl < CreateFishBowl
|
|
165
163
|
# All attributes and validations inherited from CreateFishBowl.
|
166
164
|
|
167
165
|
def self.new_from_record(fish_bowl)
|
168
|
-
new(
|
166
|
+
new(fish_bowl.attributes.merge(fish_bowl: fish_bowl))
|
169
167
|
end
|
170
168
|
|
171
169
|
def perform
|
@@ -175,6 +173,81 @@ class UpdateFishBowl < CreateFishBowl
|
|
175
173
|
end
|
176
174
|
```
|
177
175
|
|
176
|
+
#### Extensions / Plugins
|
177
|
+
|
178
|
+
##### Gourami::Extensions::Changes
|
179
|
+
|
180
|
+
Check to see if an attribute is being changed:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
class UpdateUserEmail < Gourami::Form
|
184
|
+
|
185
|
+
include Gourami::Extensions::Changes
|
186
|
+
|
187
|
+
record(:user)
|
188
|
+
attribute(:email, :type => :string, :watch_changes => true)
|
189
|
+
|
190
|
+
def perform
|
191
|
+
user.update(attributes)
|
192
|
+
|
193
|
+
do_something_like_send_confirmation_email(email) if changes?(:email)
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
###### You can implement custom logic to determine if an attribute is changing
|
200
|
+
|
201
|
+
This is the equivalent behavior when you set `:watch_changes => true`
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
attribute(:email, :watch_changes => ->(new_value) { new_value != user.email })
|
205
|
+
```
|
206
|
+
|
207
|
+
Your logic to check for changes can be as sophisticated as you want.
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
class UpdatePageAuthorizedUsers < Gourami::Form
|
211
|
+
|
212
|
+
include Gourami::Extensions::Changes
|
213
|
+
|
214
|
+
record(:page)
|
215
|
+
attribute(:authorized_users,
|
216
|
+
:type => :array,
|
217
|
+
:watch_changes => ->(new_value) { new_value.sort.uniq != page.authorized_users.sort.uniq })
|
218
|
+
|
219
|
+
def perform
|
220
|
+
page.update(attributes)
|
221
|
+
|
222
|
+
do_something_like_notify_authorization_libraries(authorized_users) if changes?(:authorized_users)
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
228
|
+
You can also keep track of side effects due to changes by using `did_change`.
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
class UpdatePageWidgets < Gourami::Form
|
232
|
+
|
233
|
+
include Gourami::Extensions::Changes
|
234
|
+
|
235
|
+
record(:page)
|
236
|
+
attribute(:widgets,
|
237
|
+
:type => :array,
|
238
|
+
:element_type => :string,
|
239
|
+
:watch_changes => ->(new_value) {
|
240
|
+
did_change(:pro_widget, new_value.include?("pro"))
|
241
|
+
new_value.sort.uniq != page.widgets.sort.uniq
|
242
|
+
})
|
243
|
+
|
244
|
+
def validate
|
245
|
+
append_error(:widgets, :unauthorized) if changes?(:pro_widget) && !current_user_has_pro_account?
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
178
251
|
## Development
|
179
252
|
|
180
253
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/gourami.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
module Gourami
|
2
2
|
end
|
3
3
|
|
4
|
+
require "gourami/error"
|
5
|
+
require "gourami/configuration_error"
|
6
|
+
require "gourami/not_watching_changes_error"
|
7
|
+
require "gourami/required_attribute_error"
|
8
|
+
require "gourami/validation_error"
|
9
|
+
require "gourami/watch_changes_error"
|
10
|
+
|
4
11
|
require "gourami/attributes"
|
5
12
|
require "gourami/coercer"
|
13
|
+
require "gourami/extensions/changes"
|
6
14
|
require "gourami/extensions/resources"
|
7
15
|
require "gourami/form"
|
8
16
|
require "gourami/formatting_constants"
|
9
|
-
require "gourami/required_attribute_error"
|
10
|
-
require "gourami/validation_error"
|
11
17
|
require "gourami/validations"
|
12
18
|
require "gourami/version"
|
data/lib/gourami/attributes.rb
CHANGED
@@ -23,39 +23,42 @@ module Gourami
|
|
23
23
|
options = options.dup
|
24
24
|
options[:default] = default_block if block_given?
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
mixin = Module.new do |mixin|
|
27
|
+
unless options[:skip_reader]
|
28
|
+
mixin.send(:define_method, :"#{name}") do
|
29
|
+
value = instance_variable_get(:"@#{name}")
|
30
|
+
default = options[:default]
|
31
|
+
|
32
|
+
if value.nil? && default
|
33
|
+
default.respond_to?(:call) ? instance_exec(&default) : default
|
34
|
+
else
|
35
|
+
value
|
36
|
+
end
|
35
37
|
end
|
36
38
|
end
|
37
|
-
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
# Define external setter.
|
41
|
+
mixin.send(:define_method, :"#{name}=") do |value|
|
42
|
+
provided_attributes_names[name.to_s] = options
|
43
|
+
send(:"_#{name}=", value)
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
private :"_#{name}="
|
46
|
+
# Define internal setter.
|
47
|
+
mixin.send(:define_method, :"_#{name}=") do |value|
|
48
|
+
instance_variable_set(:"@#{name}", setter_filter(name, value, options))
|
49
|
+
end
|
50
|
+
mixin.send(:private, :"_#{name}=")
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
case options[:type]
|
53
|
+
when :boolean
|
54
|
+
mixin.send(:define_method, :"#{name}?") do
|
55
|
+
!!send(name)
|
56
|
+
end
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
60
|
+
include(mixin)
|
61
|
+
|
59
62
|
attributes[name] = options
|
60
63
|
end
|
61
64
|
|
@@ -71,7 +74,7 @@ module Gourami
|
|
71
74
|
define_method(:record) do
|
72
75
|
send(name)
|
73
76
|
end
|
74
|
-
attribute(name, options.merge(:skip => true))
|
77
|
+
attribute(name, options.merge(:skip => true, :record => true))
|
75
78
|
end
|
76
79
|
|
77
80
|
# Retrieve the list of attributes of the form.
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Gourami
|
2
|
+
module Extensions
|
3
|
+
module Changes
|
4
|
+
WATCH_CHANGES_VALID_RETURN_VALUES = [true, false].freeze
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def attribute(name, options = {}, &default_block)
|
8
|
+
super.tap do
|
9
|
+
watch_changes = options.fetch(:watch_changes, false)
|
10
|
+
|
11
|
+
if watch_changes
|
12
|
+
mixin = Module.new do |mixin|
|
13
|
+
mixin.send(:define_method, :"_#{name}=") do |value|
|
14
|
+
super(value).tap do
|
15
|
+
new_value = instance_variable_get(:"@#{name}")
|
16
|
+
attribute_did_change = if watch_changes.respond_to?(:call)
|
17
|
+
instance_exec(new_value, &watch_changes)
|
18
|
+
elsif !defined?(record)
|
19
|
+
raise ConfigurationError, "Default `watch_changes` behavior not available without a `record`. Try `attribute(:#{name}, :watch_changes => ->(new_value) { new_value != custom_check_logic )`"
|
20
|
+
elsif record.nil?
|
21
|
+
!new_value.nil?
|
22
|
+
else
|
23
|
+
record.send(name) != new_value
|
24
|
+
end
|
25
|
+
|
26
|
+
unless WATCH_CHANGES_VALID_RETURN_VALUES.include?(attribute_did_change)
|
27
|
+
raise WatchChangesError, "`watch_changes` block for `#{name.inspect}` must return one of #{WATCH_CHANGES_VALID_RETURN_VALUES.inspect}."
|
28
|
+
end
|
29
|
+
|
30
|
+
did_change(name, attribute_did_change)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
mixin.send(:private, :"_#{name}=")
|
34
|
+
end
|
35
|
+
include(mixin)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.included(klass)
|
42
|
+
klass.send(:extend, ClassMethods)
|
43
|
+
end
|
44
|
+
|
45
|
+
def changes?(attribute_name)
|
46
|
+
attribute_name_sym = attribute_name.to_sym
|
47
|
+
changed_attributes.fetch(attribute_name_sym) do
|
48
|
+
options = self.class.attributes.fetch(attribute_name_sym, {})
|
49
|
+
watch_changes = options.fetch(:watch_changes, false)
|
50
|
+
|
51
|
+
return false if watch_changes
|
52
|
+
|
53
|
+
raise NotWatchingChangesError, "`#{attribute_name}` is not being watched for changes. " \
|
54
|
+
"Try `attribute(:#{attribute_name}, :watch_changes => true)`"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def did_change(attribute_name, changed = true)
|
59
|
+
changed_attributes[attribute_name.to_sym] = !!changed
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def changed_attributes
|
65
|
+
@changed_attributes ||= {}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/gourami/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gourami
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TSMMark
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -87,13 +87,18 @@ files:
|
|
87
87
|
- lib/gourami.rb
|
88
88
|
- lib/gourami/attributes.rb
|
89
89
|
- lib/gourami/coercer.rb
|
90
|
+
- lib/gourami/configuration_error.rb
|
91
|
+
- lib/gourami/error.rb
|
92
|
+
- lib/gourami/extensions/changes.rb
|
90
93
|
- lib/gourami/extensions/resources.rb
|
91
94
|
- lib/gourami/form.rb
|
92
95
|
- lib/gourami/formatting_constants.rb
|
96
|
+
- lib/gourami/not_watching_changes_error.rb
|
93
97
|
- lib/gourami/required_attribute_error.rb
|
94
98
|
- lib/gourami/validation_error.rb
|
95
99
|
- lib/gourami/validations.rb
|
96
100
|
- lib/gourami/version.rb
|
101
|
+
- lib/gourami/watch_changes_error.rb
|
97
102
|
homepage: http://github.com/Vydia/gourami
|
98
103
|
licenses:
|
99
104
|
- MIT
|
@@ -114,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
119
|
version: '0'
|
115
120
|
requirements: []
|
116
121
|
rubyforge_project:
|
117
|
-
rubygems_version: 2.4.
|
122
|
+
rubygems_version: 2.4.6
|
118
123
|
signing_key:
|
119
124
|
specification_version: 4
|
120
125
|
summary: Keep your Routes, Controllers and Models thin.
|