gourami 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|