organ 0.0.1

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 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: []