organ 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8033a1f23ace0b7e8ba5b04ecbf34b842f6bb1bc
4
+ data.tar.gz: edc103641da1a031b8ffec33070aa49ae88fd13a
5
+ SHA512:
6
+ metadata.gz: ce1a123bbb939f0f28efbade808bcefd58a7151fdab8ebbc2588e533d03fb5d3f9c496f2be01a6cd9013ff26286b992b4f60c69d11163e85340bb4e3b8938ab9
7
+ data.tar.gz: cd965088fc635e7421658cffda80367d80be5d17fc94b64a46e9943e7eb8b14a34386de8c2326df7cfc1973c11f4172c359fb1fe737437ec2b89edd5abdb168d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Sebastian Borrazas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,321 @@
1
+ Organ
2
+ =====
3
+
4
+ Forms with integrated validations and attribute coercing.
5
+
6
+ Introduction
7
+ -----------
8
+
9
+ Organ is a small library for manipulating form-based data with validations
10
+ attributes coercion.
11
+
12
+ These forms are very useful for handling HTTP requests where we receive certain
13
+ parameters and need to coerce them, validate them and then do something with
14
+ them. They are made so that the system has 1 form per service, so the form
15
+ names should usually be very explicit of the service they are providing
16
+ (`CreateUser`, `DeleteUser`, `SendTweet`, `NotifyAdmin`).
17
+
18
+ You should reuse forms behaviour (including validations) through inheritance
19
+ or modules (like having `CreateUser` inherit from `UpdateUser`). They can be
20
+ extended to handle worker jobs, to act as presenters, etc.
21
+
22
+ They do not handle HTML rendering of forms or do any HTTP manipulation.
23
+
24
+ The name `Organ` was inspired by the fact that organs beheave in a module manner
25
+ so that they do one thing only.
26
+
27
+ Usage
28
+ -----
29
+
30
+ A form is simply a class which inherits from `Organ::Form`. You can specify
31
+ the attributes the form will have with the `attributes` class method.
32
+
33
+ The `attributes` class method takes the attribute name and any options that
34
+ attribute has.
35
+
36
+ The options can be:
37
+
38
+ * `:type` - The type for which that attribute will be coerced.
39
+ * `:skip` - If `true` it won't include the attribute when calling the
40
+ `#attributes` method on the instance.
41
+ * `:skip_reader` - If `true`, it won't create the attribute reader for that
42
+ attribute.
43
+
44
+ Example:
45
+
46
+ ```ruby
47
+ class CreateCustomer < Organ::Form
48
+
49
+ attribute(:name, :type => :string, :trim => true)
50
+ attribute(:address, :type => :string, :trim => true)
51
+
52
+ def validate
53
+ validate_presence(:name)
54
+ validate_length(:name, :min => 4, :max => 255)
55
+ validate_length(:address, :max => 255)
56
+
57
+ validate_uniqueness(:address) do |addr|
58
+ Customer.where(:address => addr).empty?
59
+ end
60
+ end
61
+
62
+ def perform
63
+ Customer.create(attributes)
64
+ end
65
+
66
+ end
67
+
68
+ # Sinatra example
69
+ post "/customer" do
70
+ form = CreateCustomer.new(params[:customer])
71
+ content_type(:json)
72
+ if form.valid?
73
+ form.perform
74
+ status(204)
75
+ else
76
+ status(422)
77
+ JSON.generate("errors" => form.errors)
78
+ end
79
+ end
80
+ ```
81
+
82
+ Default types
83
+ -------------
84
+
85
+ The default types you can use are:
86
+
87
+ ### :string
88
+
89
+ Coerces the value into a string or nil if no value given. If the `:trim` option
90
+ is given it also strips the preceding/trailing whitespaces and newlines.
91
+
92
+ ### :boolean
93
+
94
+ Coerces the value into false (if no value given) or true otherwise.
95
+
96
+ ### :array
97
+
98
+ Coerces the value into an Array. If it can't be coerced into an Array, it
99
+ returns an empty Array. An additional `:element_type` option with another type
100
+ can be specifed to coerce all the elements of the array into it.
101
+
102
+ If a Hash is passed instead of an array, it takes the Hash values.
103
+
104
+ ### :float
105
+
106
+ Coerce the value into a Float, or nil of the value can't be coerced into a
107
+ float.
108
+
109
+ ### :hash
110
+
111
+ Coerces the value into a Hash. If it can't be coerced into a Hash, it returns
112
+ an empty Hash. An additional `:key_type` and/or `:value_type` can be specified
113
+ to coerce the keys/values of the hash respectively.
114
+
115
+ ### :integer
116
+
117
+ Coerces the value into a Fixnum. If it can't be coerced it returns nil.
118
+
119
+ ### :date
120
+
121
+ Coerces the value into a date. If the value doesn't have the `%Y-%m-%d` format
122
+ it returns nil.
123
+
124
+ Default validations
125
+ -------------------
126
+
127
+ ### validate_presence
128
+
129
+ If the value is falsy or an empty string it appends a `:blank` error to the
130
+ attribute.
131
+
132
+ ### validate_uniqueness
133
+
134
+ If the value is present and the block passed returns false, it appends a
135
+ `:taken` error to the attribute. Example:
136
+
137
+ ```ruby
138
+ validate_uniqueness(:username) do |username|
139
+ User.where(:username => username).empty?
140
+ end
141
+ ```
142
+
143
+ ### validate_email_format
144
+
145
+ If the value is present and doesn't match an emails format, it appends an
146
+ `:invalid` error to the attribute.
147
+
148
+ ### validate_format
149
+
150
+ If the value is present and doesn't match the specified format, it appends an
151
+ `:invalid` error to the attribute.
152
+
153
+ ### validate_length
154
+
155
+ If the value is present and shorter than the `:min` option, it appends a
156
+ `:too_short` error to the attribute. If it's longer than the `:max` option, it
157
+ appends a `:too_long` error to the attribute. Example:
158
+
159
+ ```ruby
160
+ validate_length(:username, :min => 3, :max => 255)
161
+ validate_length(:first_name, :max => 255)
162
+ ```
163
+
164
+ ### validate_inclusion
165
+
166
+ If the value is present and not included on the given list it appends a
167
+ `:not_included` error to the attribute.
168
+
169
+ ### validate_range
170
+
171
+ If the value is present and less than the `:min` option, it appends a
172
+ `:less_than` error to the attribute. If it's greater than the `:max` option, it
173
+ appends a `:greater_than` error to the attribute. Example:
174
+
175
+ ```ruby
176
+ validate_range(:age, :min => 18)
177
+ ```
178
+
179
+ Extensions
180
+ ----------
181
+
182
+ These forms were meant to be extended when necessary. These are a few examples
183
+ of how they can be extended.
184
+
185
+ ### Extensions::Paginate
186
+
187
+ An extension to paginate results with Sequel Datasets.
188
+
189
+ ```ruby
190
+ module Extensions
191
+ module Presenter
192
+
193
+ DEFAULT_PER_PAGE = 30
194
+ MAX_PER_PAGE = 100
195
+
196
+ def self.included(base)
197
+ base.attribute(:page, :type => :integer, :skip_reader => true)
198
+ base.attribute(:per_page, :type => :integer, :skip_reader => true)
199
+ end
200
+
201
+ def each(&block)
202
+ results.each(&block)
203
+ end
204
+
205
+ def total_pages
206
+ @total_pages ||= (1.0 * dataset.count / per_page).ceil
207
+ end
208
+
209
+ def any?
210
+ total_pages > 0
211
+ end
212
+
213
+ def results
214
+ @results ||= begin
215
+ start = (page - 1) * per_page
216
+ _dataset = dataset.limit(per_page, start)
217
+ _dataset.all
218
+ end
219
+ end
220
+
221
+ def per_page
222
+ if @per_page && @per_page >= 1 && per_page <= MAX_PER_PAGE
223
+ @per_page
224
+ else
225
+ DEFAULT_PER_PAGE
226
+ end
227
+ end
228
+
229
+ def page
230
+ @page && @page > 1 ? @page : 1
231
+ end
232
+
233
+ end
234
+ end
235
+
236
+ module Presenters
237
+ class UserPets < Organ::Form
238
+
239
+ include Extensions::Paginate
240
+
241
+ attribute(:user_id, :type => :integer)
242
+
243
+ def dataset
244
+ Pet.where(:user_id => user_id)
245
+ end
246
+
247
+ end
248
+ end
249
+
250
+ # Sinatra app
251
+ get "/pets" do
252
+ presenter = Presenter::UserPets.new({
253
+ :user_id => session[:user_id],
254
+ :page => params[:page],
255
+ :per_page => params[:per_page]
256
+ })
257
+ erb(:"pets/index", :locals => { :pets => presenter })
258
+ end
259
+ ```
260
+
261
+ ### Extensions::Worker
262
+
263
+ An extension to create worker job handlers using
264
+ [Ost](https://github.com/soveran/ost).
265
+
266
+ ```ruby
267
+ module Extensions
268
+ module Worker
269
+
270
+ def self.queue_job(attributes)
271
+ queue << JSON.generate(attributes)
272
+ end
273
+
274
+ def self.stop
275
+ queue.stop
276
+ end
277
+
278
+ def self.watch_queue
279
+ queue.each do |json_str|
280
+ attributes = JSON.parse(json_str)
281
+ new(attributes).perform
282
+ end
283
+ end
284
+
285
+ private
286
+
287
+ def self.queue
288
+ Ost[self.name]
289
+ end
290
+
291
+ end
292
+ end
293
+
294
+ module Workers
295
+ class EmailNotifier < Organ::Form
296
+
297
+ include Extensions::Worker
298
+
299
+ attribute(:email)
300
+ attribute(:message)
301
+
302
+ def perform
303
+ # send message to email...
304
+ end
305
+
306
+ end
307
+ end
308
+
309
+ # Sinatra app
310
+ get "/queue_email" do
311
+ Workers::EmailNotifier.queue_job(params[:notification])
312
+ status(204)
313
+ end
314
+ ```
315
+
316
+ Aknowledgements
317
+ ---------------
318
+
319
+ This library was inspired mainly by @soveran
320
+ [Scrivener](https://github.com/soveran/scrivener) and was made with the help ofn
321
+ @grilix.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "rake/testtask"
2
+
3
+ desc "Run all tests"
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.pattern = "./spec/**/*_spec.rb"
6
+ t.verbose = false
7
+ end
data/lib/organ.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative "organ/form"
2
+
3
+ module Organ
4
+
5
+ VERSION = "0.0.1"
6
+
7
+ end
@@ -0,0 +1,136 @@
1
+ module Organ
2
+ module Coercer
3
+
4
+ # Coerce the value into a String or nil if no value given.
5
+ #
6
+ # @param value [Object]
7
+ # @option options [Boolan] :trim (false)
8
+ # If true, it strips the preceding/trailing whitespaces and newlines.
9
+ # It also replaces multiple consecutive spaces into one.
10
+ #
11
+ # @return [String, nil]
12
+ #
13
+ # @api semipublic
14
+ def coerce_string(value, options = {})
15
+ value = value ? value.to_s : nil
16
+ if value && options[:trim]
17
+ value = value.strip.gsub(/\s{2,}/, " ")
18
+ end
19
+ value
20
+ end
21
+
22
+ # Corce the value into true or false.
23
+ #
24
+ # @param value [Object]
25
+ #
26
+ # @return [Boolean]
27
+ #
28
+ # @api semipublic
29
+ def coerce_boolean(value, options = {})
30
+ !!value
31
+ end
32
+
33
+ # Coerce the value into an Array.
34
+ #
35
+ # @param value [Object]
36
+ # The value to be coerced.
37
+ # @option options [Symbol] :element_type (nil)
38
+ # The type of the value to coerce each element of the array. No coercion
39
+ # done if type is nil.
40
+ #
41
+ # @return [Array]
42
+ # The coerced Array.
43
+ #
44
+ # @api semipublic
45
+ def coerce_array(value, options = {})
46
+ element_type = options[:element_type]
47
+ value = value.values if value.kind_of?(Hash)
48
+ if value.kind_of?(Array)
49
+ if element_type
50
+ value.map do |array_element|
51
+ send("coerce_#{element_type}", array_element)
52
+ end
53
+ else
54
+ value
55
+ end
56
+ else
57
+ []
58
+ end
59
+ end
60
+
61
+ # Coerce the value into a Float.
62
+ #
63
+ # @param value [Object]
64
+ #
65
+ # @return [Float, nil]
66
+ #
67
+ # @api semipublic
68
+ def coerce_float(value, options = {})
69
+ Float(value) rescue nil
70
+ end
71
+
72
+ # Coerce the value into a Hash.
73
+ #
74
+ # @param value [Object]
75
+ # The value to be coerced.
76
+ # @option options :key_type [Symbol] (nil)
77
+ # The type of the hash keys to coerce, no coersion done if type is nil.
78
+ # @options options :value_type [Symbol] (nil)
79
+ # The type of the hash values to coerce, no coersion done if type is nil.
80
+ #
81
+ # @return [Hash]
82
+ # The coerced Hash.
83
+ #
84
+ # @api semipublic
85
+ def coerce_hash(value, options = {})
86
+ key_type = options[:key_type]
87
+ value_type = options[:value_type]
88
+ if value.kind_of?(Hash)
89
+ value.each_with_object({}) do |(key, value), coerced_hash|
90
+ key = send("coerce_#{key_type}", key) if key_type
91
+ value = send("coerce_#{value_type}", value) if value_type
92
+ coerced_hash[key] = value
93
+ end
94
+ else
95
+ {}
96
+ end
97
+ end
98
+
99
+ # Coerce the value into an Integer.
100
+ #
101
+ # @param value [Object]
102
+ # The value to be coerced.
103
+ #
104
+ # @return [Integer, nil]
105
+ # An Integer if the value can be coerced or nil otherwise.
106
+ #
107
+ # @api semipublic
108
+ def coerce_integer(value, options = {})
109
+ value = value.to_s
110
+ if value.match(/\A0|[1-9]\d*\z/)
111
+ value.to_i
112
+ else
113
+ nil
114
+ end
115
+ end
116
+
117
+ # Coerce the value into a Date.
118
+ #
119
+ # @param value [Object]
120
+ # The value to be coerced.
121
+ #
122
+ # @return [Date, nil]
123
+ # A Date if the value can be coerced or nil otherwise.
124
+ #
125
+ # @api semipublic
126
+ def coerce_date(value, options = {})
127
+ value = coerce(value, String)
128
+ begin
129
+ Date.strptime(value, "%Y-%m-%d")
130
+ rescue ArgumentError
131
+ nil
132
+ end
133
+ end
134
+
135
+ end
136
+ end
data/lib/organ/form.rb ADDED
@@ -0,0 +1,131 @@
1
+ require_relative "validation_error"
2
+ require_relative "validations"
3
+ require_relative "coercer"
4
+
5
+ module Organ
6
+ # Form for doing actions based on the attributes specified.
7
+ # This class has to be inherited by different forms, each performing a
8
+ # different action. If validations are needed, #validate method should be
9
+ # overridden.
10
+ #
11
+ # @example
12
+ #
13
+ # class LoginForm < Organ::Form
14
+ #
15
+ # attribute(:username, :type => :string)
16
+ # attribute(:password, :type => :string)
17
+ #
18
+ # def validate
19
+ # unless valid_login?
20
+ # append_error(:username, :invalid)
21
+ # end
22
+ # end
23
+ #
24
+ # private
25
+ #
26
+ # def valid_login?
27
+ # user = User.where(username: username).first
28
+ # user && check_password_secure(user.password, password)
29
+ # end
30
+ #
31
+ # end
32
+ #
33
+ class Form
34
+
35
+ include Organ::Validations
36
+ include Organ::Coercer
37
+
38
+ # Copy parent attributes to inherited class.
39
+ #
40
+ # @param klass [Class]
41
+ #
42
+ # @api private
43
+ def self.inherited(klass)
44
+ super(klass)
45
+ klass.instance_variable_set(:@attributes, attributes.dup)
46
+ end
47
+
48
+ # Define an attribute for the form.
49
+ #
50
+ # @param name
51
+ # The Symbol attribute name.
52
+ # @option options [Symbol] :type (nil)
53
+ # The type of this attribute.
54
+ # @option options [Symbol] :skip_reader (false)
55
+ # If true, skips from creating the attribute reader.
56
+ #
57
+ # @api public
58
+ def self.attribute(name, options = {})
59
+ attr_reader(name) unless options[:skip_reader]
60
+ define_method("#{name}=") do |value|
61
+ if options[:type]
62
+ value = send("coerce_#{options[:type]}", value, options)
63
+ end
64
+ instance_variable_set("@#{name}", value)
65
+ end
66
+ attributes[name] = options
67
+ end
68
+
69
+ # Retrieve the list of attributes of the form.
70
+ #
71
+ # @return [Hash]
72
+ # The class attributes hash.
73
+ #
74
+ # @api private
75
+ def self.attributes
76
+ @attributes ||= {}
77
+ end
78
+
79
+ # Initialize a new Form::Base form.
80
+ #
81
+ # @param attrs [Hash]
82
+ # The attributes values to use for the new instance.
83
+ #
84
+ # @api public
85
+ def initialize(attrs = {})
86
+ set_attributes(attrs)
87
+ end
88
+
89
+ # Set the attributes belonging to the form.
90
+ #
91
+ # @param attrs [Hash<String, Object>]
92
+ #
93
+ # @api public
94
+ def set_attributes(attrs)
95
+ return unless attrs.kind_of?(Hash)
96
+
97
+ attrs = coerce_hash(attrs, :key_type => :string)
98
+
99
+ self.class.attributes.each do |attribute_name, _|
100
+ send("#{attribute_name}=", attrs[attribute_name.to_s])
101
+ end
102
+ end
103
+
104
+ # Get the all attributes with its values of the current form.
105
+ #
106
+ # @return [Hash<Symbol, Object>]
107
+ #
108
+ # @api public
109
+ def attributes
110
+ self.class.attributes.each_with_object({}) do |(name, opts), attrs|
111
+ attrs[name] = send(name) unless opts[:skip]
112
+ end
113
+ end
114
+
115
+ # Validate and perform the form actions. If any errors come up during the
116
+ # validation or the #perform method, raise an exception with the errors.
117
+ #
118
+ # @raise [Organ::ValidationError]
119
+ #
120
+ # @api public
121
+ def perform!
122
+ if valid?
123
+ perform
124
+ end
125
+ if errors.any?
126
+ raise Organ::ValidationError.new(errors)
127
+ end
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,20 @@
1
+ module Organ
2
+ class ValidationError < StandardError
3
+
4
+ # !@attribute [r] errors
5
+ # @return [Hash<Symbol, Array>]
6
+ #
7
+ # @api public
8
+ attr_reader :errors
9
+
10
+ # Initialize the Organ::ValidationError.
11
+ #
12
+ # @param errors [Hash<Symbol, Array>]
13
+ #
14
+ # @api public
15
+ def initialize(errors)
16
+ @errors = errors
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,161 @@
1
+ module Organ
2
+ module Validations
3
+
4
+ EMAIL_FORMAT = /\A
5
+ ([0-9a-zA-Z\.][-\w\+\.]*)@
6
+ ([0-9a-zA-Z_][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9}\z/x
7
+
8
+ # Get the current form errors Hash.
9
+ #
10
+ # @return [Hash<Symbol, Array<Symbol>]
11
+ # The errors Hash, having the Symbol attribute names as keys and an
12
+ # array of errors (Symbols) as the value.
13
+ #
14
+ # @api public
15
+ def errors
16
+ @errors ||= Hash.new { |hash, key| hash[key] = [] }
17
+ end
18
+
19
+ # Determine if current form instance is valid by running the validations
20
+ # specified on #validate.
21
+ #
22
+ # @return [Boolean]
23
+ #
24
+ # @api public
25
+ def valid?
26
+ errors.clear
27
+ validate
28
+ errors.empty?
29
+ end
30
+
31
+ # Append an error to the given attribute.
32
+ #
33
+ # @param attribute_name [Symbol]
34
+ # @param error [Symbol]
35
+ # The error identifier.
36
+ #
37
+ # @api public
38
+ def append_error(attribute_name, error)
39
+ errors[attribute_name] << error
40
+ end
41
+
42
+ # Validate the presence of the attribute value. If the value is nil or
43
+ # false append a :blank error to the attribute.
44
+ #
45
+ # @param attribute_name [Symbol]
46
+ #
47
+ # @api public
48
+ def validate_presence(attribute_name)
49
+ value = send(attribute_name)
50
+ if !value || value.to_s.empty?
51
+ append_error(attribute_name, :blank)
52
+ end
53
+ end
54
+
55
+ # Validate the uniqueness of the attribute value. The uniqueness is
56
+ # determined by the block given. If the value is not unique, append the
57
+ # :taken error to the attribute.
58
+ #
59
+ # @param attribute_name [Symbol]
60
+ # @param block [Proc]
61
+ # A block to determine if a given value is unique or not. It receives
62
+ # the value and should return true if the value is unique.
63
+ #
64
+ # @api public
65
+ def validate_uniqueness(attribute_name, &block)
66
+ value = send(attribute_name)
67
+ unless block.call(value)
68
+ append_error(attribute_name, :taken)
69
+ end
70
+ end
71
+
72
+ # Validate the email format. If the value does not match the email format,
73
+ # append the :invalid error to the attribute.
74
+ #
75
+ # @param attribute_name [Symbol]
76
+ #
77
+ # @api public
78
+ def validate_email_format(attribute_name)
79
+ validate_format(attribute_name, EMAIL_FORMAT)
80
+ end
81
+
82
+ # Validate the format of the attribute value. If the value does not match
83
+ # the regexp given, append :invalid error to the attribute.
84
+ #
85
+ # @param attribute_name [Symbol]
86
+ # @param format [Regexp]
87
+ #
88
+ # @api public
89
+ def validate_format(attribute_name, format)
90
+ value = send(attribute_name)
91
+ if value && !(format =~ value)
92
+ append_error(attribute_name, :invalid)
93
+ end
94
+ end
95
+
96
+ # Validate the length of a String, Array or any other form attribute which
97
+ # responds to #size. If the value is too short, append the :too_short
98
+ # error to the attribute. If the value is too long append the :too_long
99
+ # error to the attribute.
100
+ #
101
+ # @param attribute_name [Symbol]
102
+ # @option options [Integer, nil] :min (nil)
103
+ # @option options [Integer, nil] :max (nil)
104
+ #
105
+ # @api public
106
+ def validate_length(attribute_name, options = {})
107
+ min = options.fetch(:min, nil)
108
+ max = options.fetch(:max, nil)
109
+ value = send(attribute_name)
110
+
111
+ if value
112
+ length = value.size
113
+ if min && length < min
114
+ append_error(attribute_name, :too_short)
115
+ end
116
+ if max && length > max
117
+ append_error(attribute_name, :too_long)
118
+ end
119
+ end
120
+ end
121
+
122
+ # Validate the value of the given attribute is included in the list. If
123
+ # the value is not included in the list, append the :not_included error to
124
+ # the attribute.
125
+ #
126
+ # @param attribute_name [Symbol]
127
+ # @param attribute_name [Symbol]
128
+ # @param list [Array]
129
+ #
130
+ # @api public
131
+ def validate_inclusion(attribute_name, list)
132
+ value = send(attribute_name)
133
+ if value && !list.include?(value)
134
+ append_error(attribute_name, :not_included)
135
+ end
136
+ end
137
+
138
+ # Validate the range in which the attribute can be. If the value is less
139
+ # than the min a :less_than_min error will be appended. If the value is
140
+ # greater than the max a :greater_than_max error will be appended.
141
+ #
142
+ # @param attribute_name [Symbol]
143
+ # @option options [Integer] :min (nil)
144
+ # The minimum value the attribute can take, if nil, no validation is made.
145
+ # @option options [Integer] :max (nil)
146
+ # The maximum value the attribute can take, if nil, no validation is made.
147
+ #
148
+ # @api public
149
+ def validate_range(attribute_name, options = {})
150
+ value = send(attribute_name)
151
+
152
+ return unless value
153
+
154
+ min = options.fetch(:min, nil)
155
+ max = options.fetch(:max, nil)
156
+ append_error(attribute_name, :less_than) if min && value < min
157
+ append_error(attribute_name, :greater_than) if max && value > max
158
+ end
159
+
160
+ end
161
+ end
data/organ.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "organ"
3
+ s.version = "0.0.1"
4
+ s.summary = "Forms with integrated validations and attribute coercing."
5
+ s.description = "A small library for manipulating form-based data with validations and attributes coercion."
6
+ s.authors = ["Sebastian Borrazas"]
7
+ s.email = ["seba.borrazas@gmail.com"]
8
+ s.homepage = "http://github.com/sborrazas/organ"
9
+
10
+ s.files = Dir[
11
+ "LICENSE",
12
+ "CHANGELOG",
13
+ "README.md",
14
+ "Rakefile",
15
+ "lib/**/*.rb",
16
+ "*.gemspec",
17
+ "test/*.*"
18
+ ]
19
+
20
+ s.require_paths = ["lib"]
21
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: organ
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sebastian Borrazas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A small library for manipulating form-based data with validations and
14
+ attributes coercion.
15
+ email:
16
+ - seba.borrazas@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - Rakefile
24
+ - lib/organ.rb
25
+ - lib/organ/coercer.rb
26
+ - lib/organ/form.rb
27
+ - lib/organ/validation_error.rb
28
+ - lib/organ/validations.rb
29
+ - organ.gemspec
30
+ homepage: http://github.com/sborrazas/organ
31
+ licenses: []
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 2.2.0
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Forms with integrated validations and attribute coercing.
53
+ test_files: []