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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5a514a33e36dee0b4a2c39ef0320f4a8fa70ab24
4
- data.tar.gz: 6acabd9872fd0346833fa7f50b527d1413a202f0
3
+ metadata.gz: f97edb63e52af97e6e63f41a74a824a04da00c21
4
+ data.tar.gz: 74bff6e2f3563c96205fc155f0b21db04df37169
5
5
  SHA512:
6
- metadata.gz: 97e2db4cccf243740e2e1548673df9860a06aeb856516c77e9596bab2ff06c39c86c982266a5cbe24d33b6383d885cc1e4411d0fb934c7570628de2000edbfa7
7
- data.tar.gz: 15cff403f9f9a2d2a7536278fa114fff0f503e8a13e539546f8ae049d978c76231c71fe7e60c5c65721b4761d2178bd6dd4340be5fbd23f3f472f635a6111f35
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
- - list a set of attributes
26
- - validate user input
27
- - perform an action
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 Your validation rules here
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 < CreateFishBowl
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({ fish_bowl: fish_bowl }.merge(fish_bowl.attributes))
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({ fish_bowl: fish_bowl }.merge(fish_bowl.attributes))
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.
@@ -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"
@@ -23,39 +23,42 @@ module Gourami
23
23
  options = options.dup
24
24
  options[:default] = default_block if block_given?
25
25
 
26
- unless options[:skip_reader]
27
- define_method(:"#{name}") do
28
- value = instance_variable_get(:"@#{name}")
29
- default = options[:default]
30
-
31
- if value.nil? && default
32
- default.respond_to?(:call) ? instance_exec(&default) : default
33
- else
34
- value
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
- # Define external setter.
40
- define_method(:"#{name}=") do |value|
41
- provided_attributes_names[name.to_s] = options
42
- send(:"_#{name}=", value)
43
- end
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
- # Define internal setter.
46
- define_method(:"_#{name}=") do |value|
47
- value = setter_filter(name, value, options)
48
- instance_variable_set(:"@#{name}", value)
49
- end
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
- case options[:type]
53
- when :boolean
54
- define_method(:"#{name}?") do
55
- !!send(name)
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,4 @@
1
+ module Gourami
2
+ class ConfigurationError < Gourami::Error
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Gourami
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ module Gourami
2
+ class NotWatchingChangesError < Gourami::Error
3
+ end
4
+ end
@@ -1,4 +1,4 @@
1
1
  module Gourami
2
- class RequiredAttributeError < StandardError
2
+ class RequiredAttributeError < Gourami::Error
3
3
  end
4
4
  end
@@ -1,5 +1,5 @@
1
1
  module Gourami
2
- class ValidationError < StandardError
2
+ class ValidationError < Gourami::Error
3
3
 
4
4
  def self.stringify_errors(errors)
5
5
  [].tap do |array|
@@ -1,3 +1,3 @@
1
1
  module Gourami
2
- VERSION = "0.1.2".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
@@ -0,0 +1,4 @@
1
+ module Gourami
2
+ class WatchChangesError < Gourami::Error
3
+ end
4
+ end
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.1.2
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-05-09 00:00:00.000000000 Z
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.8
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.