scrivener 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.
data/AUTHORS ADDED
@@ -0,0 +1,3 @@
1
+ * Cyril David (cyx)
2
+ * Lucas Nasif (ehqhvm)
3
+ * Michel Martens (soveran)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Michel Martens
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,123 @@
1
+ Scrivener
2
+ =========
3
+
4
+ Validation frontend for models.
5
+
6
+ Description
7
+ -----------
8
+
9
+ Scrivener removes the validation responsibility from models and acts as a
10
+ filter for whitelisted attributes.
11
+
12
+ A model may expose different APIs to satisfy different purposes. For example,
13
+ the set of validations for a User in a Sign up process may not be the same
14
+ as the one exposed to an Admin when editing a user profile. While you want
15
+ the User to provide an email, a password and a password confirmation, you
16
+ probably don't want the admin to mess with those attributes at all.
17
+
18
+ In a wizard, different model states ask for different validations, and a single
19
+ set of validations for the whole process is not the best solution.
20
+
21
+ Scrivener is Bureaucrat's little brother. It draws all the inspiration from it
22
+ and its features are a subset of Bureaucrat's. For a more robust and tested
23
+ solution, please [check it](https://github.com/tizoc/bureaucrat).
24
+
25
+ This library exists to satify the need of extracting Ohm's validations for
26
+ reuse in other scenarios. By doing this, all projects using Ohm::Validations
27
+ will be able to profit from extra assertions such as those provided by
28
+ [ohm-contrib](https://github.com/cyx/ohm-contrib).
29
+
30
+ Usage
31
+ -----
32
+
33
+ Using Scrivener feels very natural no matter what underlying model you are
34
+ using. As it provides its own validation and whitelisting features, you can
35
+ chose to ignore the ones that come bundled with ORMs.
36
+
37
+ This short example illustrates how to move the validation and whitelisting
38
+ responsibilities away from the model and into Scrivener:
39
+
40
+ # We use Sequel::Model in this example, but it applies to other ORMs such
41
+ # as Ohm or ActiveRecord.
42
+ class Article < Sequel::Model
43
+
44
+ # Whitelist for mass assigned attributes.
45
+ set_allowed_columns :title, :body, :state
46
+
47
+ # Validations for all contexts.
48
+ def validate
49
+ validates_presence :title
50
+ validates_presence :body
51
+ validates_presence :state
52
+ end
53
+ end
54
+
55
+ title = "Bartleby, the Scrivener"
56
+ body = "I am a rather elderly man..."
57
+
58
+ # When using the model...
59
+ article = Article.new(title: title, body: body)
60
+
61
+ article.valid? #=> false
62
+ article.errors.on(:state) #=> ["cannot be empty"]
63
+
64
+ Of course, what you would do instead is declare `:title` and `:body` as allowed
65
+ columns, then assign `:state` using the attribute accessor. The reason for this
66
+ example is to show how you need to work around the fact that there's a single
67
+ declaration for allowed columns and validations, which in many cases is a great
68
+ feature and in other is a minor obstacle.
69
+
70
+ Now see what happens with Scrivener:
71
+
72
+ # Now the model has no validations or whitelists. It may still have schema
73
+ # constraints, which is a good practice to enforce data integrity.
74
+ class Article < Sequel::Model
75
+ end
76
+
77
+ # The attribute accessors are the only fields that will be set. If more
78
+ # fields are sent when using mass assignment, a NoMethodError exception is
79
+ # raised.
80
+ #
81
+ # Note how in this example we don't ask the name on signup.
82
+ class Edit < Scrivener
83
+ attr_accessor :title
84
+ attr_accessor :body
85
+
86
+ def validate
87
+ assert_present :title
88
+ assert_present :body
89
+ end
90
+ end
91
+
92
+ edit = Edit.new(title: title, body: body)
93
+ edit.valid? #=> true
94
+
95
+ article = Article.new(edit.attributes)
96
+ article.save
97
+
98
+ # And now we only ask for the status.
99
+ class Publish < Scrivener
100
+ attr_accessor :status
101
+
102
+ def validate
103
+ assert_format /^(published|draft)$/
104
+ end
105
+ end
106
+
107
+ publish = Publish.new(status: "published")
108
+ publish.valid? #=> true
109
+
110
+ article.update_attributes(publish.attributes)
111
+
112
+ # If we try to change other fields...
113
+ publish = Publish.new(status: "published", title: title)
114
+ #=> NoMethodError: undefined method `title=' for #<Publish...>
115
+
116
+ It's important to note that using Scrivener implies a greater risk than using
117
+ the model validations. Having a central repository of mass assignable
118
+ attributes and validations is more secure in most scenarios.
119
+
120
+ Installation
121
+ ------------
122
+
123
+ $ gem install scrivener
@@ -0,0 +1,6 @@
1
+ task :test do
2
+ require "cutest"
3
+ Cutest.run(Dir["test/*.rb"])
4
+ end
5
+
6
+ task :default => :test
@@ -0,0 +1,56 @@
1
+ require File.expand_path("scrivener/validations", File.dirname(__FILE__))
2
+
3
+ class Scrivener
4
+ VERSION = "0.0.1"
5
+
6
+ include Validations
7
+
8
+ # Initialize with a hash of attributes and values.
9
+ # If extra attributes are sent, a NoMethodError exception will be raised.
10
+ #
11
+ # The grand daddy of all assertions. If you want to build custom
12
+ # assertions, or even quick and dirty ones, you can simply use this method.
13
+ #
14
+ # @example
15
+ #
16
+ # class EditPost < Scrivener
17
+ # attr_accessor :title
18
+ # attr_accessor :body
19
+ #
20
+ # def validate
21
+ # assert_present :title
22
+ # assert_present :body
23
+ # end
24
+ # end
25
+ #
26
+ # edit = EditPost.new(title: "Software Tools")
27
+ #
28
+ # edit.valid? #=> false
29
+ #
30
+ # edit.errors[:title] #=> []
31
+ # edit.errors[:body] #=> [:not_present]
32
+ #
33
+ # edit.body = "Recommended reading..."
34
+ #
35
+ # edit.valid? #=> true
36
+ #
37
+ # # Now it's safe to initialize the model.
38
+ # post = Post.new(edit.attributes)
39
+ # post.save
40
+ def initialize(attrs)
41
+ attrs.each do |key, val|
42
+ send(:"#{key}=", val)
43
+ end
44
+ end
45
+
46
+ # Return hash of attributes and values.
47
+ def attributes
48
+ Hash.new.tap do |atts|
49
+ instance_variables.each do |ivar|
50
+ att = ivar.to_s.sub(/@/, "").to_sym
51
+ atts[att] = send(att)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,152 @@
1
+ class Scrivener
2
+
3
+ # Provides a base implementation for extensible validation routines.
4
+ # {Scrivener::Validations} currently only provides the following assertions:
5
+ #
6
+ # * assert
7
+ # * assert_present
8
+ # * assert_format
9
+ # * assert_numeric
10
+ #
11
+ # The core tenets that Scrivener::Validations advocates can be summed up in a
12
+ # few bullet points:
13
+ #
14
+ # 1. Validations are much simpler and better done using composition rather
15
+ # than macros.
16
+ # 2. Error messages should be kept separate and possibly in the view or
17
+ # presenter layer.
18
+ # 3. It should be easy to write your own validation routine.
19
+ #
20
+ # Other validations are simply added on a per-model or per-project basis.
21
+ #
22
+ # If you want other validations you may want to take a peek at Ohm::Contrib
23
+ # and all of the validation modules it provides.
24
+ #
25
+ # @see http://cyx.github.com/ohm-contrib/doc/Ohm/WebValidations.html
26
+ # @see http://cyx.github.com/ohm-contrib/doc/Ohm/NumberValidations.html
27
+ # @see http://cyx.github.com/ohm-contrib/doc/Ohm/ExtraValidations.html
28
+ #
29
+ # @example
30
+ #
31
+ # class Quote
32
+ # attr_accessor :title
33
+ # attr_accessor :price
34
+ # attr_accessor :date
35
+ #
36
+ # def validate
37
+ # assert_present :title
38
+ # assert_numeric :price
39
+ # assert_format :date, /\A[\d]{4}-[\d]{1,2}-[\d]{1,2}\z
40
+ # end
41
+ # end
42
+ #
43
+ # s = Quote.new
44
+ # s.valid?
45
+ # # => false
46
+ #
47
+ # s.errors
48
+ # # => { :title => [:not_present],
49
+ # :price => [:not_numeric],
50
+ # :date => [:format] }
51
+ #
52
+ module Validations
53
+
54
+ # Check if the current model state is valid. Each call to {#valid?} will
55
+ # reset the {#errors} array.
56
+ #
57
+ # All validations should be declared in a `validate` method.
58
+ #
59
+ # @example
60
+ #
61
+ # class Login
62
+ # attr_accessor :username
63
+ # attr_accessor :password
64
+ #
65
+ # def validate
66
+ # assert_present :user
67
+ # assert_present :password
68
+ # end
69
+ # end
70
+ #
71
+ def valid?
72
+ errors.clear
73
+ validate
74
+ errors.empty?
75
+ end
76
+
77
+ # Base validate implementation. Override this method in subclasses.
78
+ def validate
79
+ end
80
+
81
+ # Hash of errors for each attribute in this model.
82
+ def errors
83
+ @errors ||= Hash.new { |hash, key| hash[key] = [] }
84
+ end
85
+
86
+ protected
87
+
88
+ # Allows you to do a validation check against a regular expression.
89
+ # It's important to note that this internally calls {#assert_present},
90
+ # therefore you need not structure your regular expression to check
91
+ # for a non-empty value.
92
+ #
93
+ # @param [Symbol] att The attribute you want to verify the format of.
94
+ # @param [Regexp] format The regular expression with which to compare
95
+ # the value of att with.
96
+ # @param [Array<Symbol, Symbol>] error The error that should be returned
97
+ # when the validation fails.
98
+ def assert_format(att, format, error = [att, :format])
99
+ if assert_present(att, error)
100
+ assert(send(att).to_s.match(format), error)
101
+ end
102
+ end
103
+
104
+ # The most basic and highly useful assertion. Simply checks if the
105
+ # value of the attribute is empty.
106
+ #
107
+ # @param [Symbol] att The attribute you wish to verify the presence of.
108
+ # @param [Array<Symbol, Symbol>] error The error that should be returned
109
+ # when the validation fails.
110
+ def assert_present(att, error = [att, :not_present])
111
+ assert(!send(att).to_s.empty?, error)
112
+ end
113
+
114
+ # Checks if all the characters of an attribute is a digit. If you want to
115
+ # verify that a value is a decimal, try looking at Ohm::Contrib's
116
+ # assert_decimal assertion.
117
+ #
118
+ # @param [Symbol] att The attribute you wish to verify the numeric format.
119
+ # @param [Array<Symbol, Symbol>] error The error that should be returned
120
+ # when the validation fails.
121
+ # @see http://cyx.github.com/ohm-contrib/doc/Ohm/NumberValidations.html
122
+ def assert_numeric(att, error = [att, :not_numeric])
123
+ if assert_present(att, error)
124
+ assert_format(att, /^\d+$/, error)
125
+ end
126
+ end
127
+
128
+ # The grand daddy of all assertions. If you want to build custom
129
+ # assertions, or even quick and dirty ones, you can simply use this method.
130
+ #
131
+ # @example
132
+ #
133
+ # class CreatePost
134
+ # attr_accessor :slug
135
+ # attr_accessor :votes
136
+ #
137
+ # def validate
138
+ # assert_slug :slug
139
+ # assert votes.to_i > 0, [:votes, :not_valid]
140
+ # end
141
+ #
142
+ # protected
143
+ # def assert_slug(att, error = [att, :not_slug])
144
+ # assert send(att).to_s =~ /\A[a-z\-0-9]+\z/, error
145
+ # end
146
+ # end
147
+ def assert(value, error)
148
+ value or errors[error.first].push(error.last) && false
149
+ end
150
+ end
151
+ end
152
+
@@ -0,0 +1,21 @@
1
+ require "./lib/scrivener"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "scrivener"
5
+ s.version = Scrivener::VERSION
6
+ s.summary = "Validation frontend for models."
7
+ s.description = "Scrivener removes the validation responsibility from models and acts as a filter for whitelisted attributes."
8
+ s.authors = ["Michel Martens"]
9
+ s.email = ["michel@soveran.com"]
10
+ s.homepage = "http://github.com/soveran/scrivener"
11
+ s.files = Dir[
12
+ "LICENSE",
13
+ "AUTHORS",
14
+ "README.md",
15
+ "Rakefile",
16
+ "lib/**/*.rb",
17
+ "*.gemspec",
18
+ "test/**/*.rb"
19
+ ]
20
+ s.add_development_dependency "cutest"
21
+ end
@@ -0,0 +1,82 @@
1
+ require File.expand_path("../lib/scrivener", File.dirname(__FILE__))
2
+
3
+ class S < Scrivener
4
+ attr_accessor :a
5
+ attr_accessor :b
6
+ end
7
+
8
+ scope do
9
+ test "raise when there are extra fields" do
10
+ atts = { :a => 1, :b => 2, :c => 3 }
11
+
12
+ assert_raise NoMethodError do
13
+ s = S.new(atts)
14
+ end
15
+ end
16
+
17
+ test "not raise when there are less fields" do
18
+ atts = { :a => 1 }
19
+
20
+ assert s = S.new(atts)
21
+ end
22
+
23
+ test "return attributes" do
24
+ atts = { :a => 1, :b => 2 }
25
+
26
+ s = S.new(atts)
27
+
28
+ assert_equal atts, s.attributes
29
+ end
30
+ end
31
+
32
+ class T < Scrivener
33
+ attr_accessor :a
34
+ attr_accessor :b
35
+
36
+ def validate
37
+ assert_present :a
38
+ assert_present :b
39
+ end
40
+ end
41
+
42
+ scope do
43
+ test "validations" do
44
+ atts = { :a => 1, :b => 2 }
45
+
46
+ t = T.new(atts)
47
+
48
+ assert t.valid?
49
+ end
50
+
51
+ test "validation errors" do
52
+ atts = { :a => 1 }
53
+
54
+ t = T.new(atts)
55
+
56
+ assert_equal false, t.valid?
57
+ assert_equal [], t.errors[:a]
58
+ assert_equal [:not_present], t.errors[:b]
59
+ end
60
+ end
61
+
62
+ class Quote
63
+ include Scrivener::Validations
64
+
65
+ attr_accessor :foo
66
+
67
+ def validate
68
+ assert_present :foo
69
+ end
70
+ end
71
+
72
+ scope do
73
+ test "validations without Scrivener" do
74
+ q = Quote.new
75
+ q.foo = 1
76
+ assert q.valid?
77
+
78
+ q = Quote.new
79
+ assert_equal false, q.valid?
80
+ assert_equal [:not_present], q.errors[:foo]
81
+ end
82
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scrivener
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michel Martens
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-18 00:00:00.000000000 -03:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: cutest
17
+ requirement: &2154104200 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *2154104200
26
+ description: Scrivener removes the validation responsibility from models and acts
27
+ as a filter for whitelisted attributes.
28
+ email:
29
+ - michel@soveran.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE
35
+ - AUTHORS
36
+ - README.md
37
+ - Rakefile
38
+ - lib/scrivener/validations.rb
39
+ - lib/scrivener.rb
40
+ - scrivener.gemspec
41
+ - test/scrivener_test.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/soveran/scrivener
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.6.2
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Validation frontend for models.
67
+ test_files: []