cave 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/Gemfile +8 -0
- data/README.md +66 -0
- data/Rakefile +8 -0
- data/cave.gemspec +16 -0
- data/lib/cave.rb +6 -0
- data/lib/cave/errors.rb +4 -0
- data/lib/cave/form.rb +75 -0
- data/lib/cave/model_form.rb +37 -0
- data/spec/gears/form_spec.rb +77 -0
- data/spec/gears/model_form_spec.rb +44 -0
- data/spec/helper.rb +2 -0
- metadata +91 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
Cave
|
2
|
+
=====
|
3
|
+
|
4
|
+
A simple Ruby form library, intended for use with Rails
|
5
|
+
|
6
|
+
Largely inspired by Code Climate's [7 Patterns to Refactor Fat ActiveRecord Models](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/) as a method for encapsulating validation into an object with a single responsibility.
|
7
|
+
|
8
|
+
### Forms
|
9
|
+
|
10
|
+
[Cave::Form](https://github.com/jamesdabbs/cave/blob/master/lib/cave/form.rb) is a light glue layer around
|
11
|
+
[Rails' ActiveModel::Validations](http://guides.rubyonrails.org/active_record_validations_callbacks.html)
|
12
|
+
and [Virtus](https://github.com/solnic/virtus)' type coercion. You may want to consult their documentation for
|
13
|
+
more options.
|
14
|
+
|
15
|
+
class FormClass < Cave::Form
|
16
|
+
field :name, String,
|
17
|
+
presence: true,
|
18
|
+
format: { :with => /\A[a-zA-Z]+\z/, :message => "Only letters allowed" }
|
19
|
+
# Takes any number of standard Rails validation helper options
|
20
|
+
field :favorite_number, Integer,
|
21
|
+
inclusion: { :in => 1..10 }
|
22
|
+
|
23
|
+
def persist!
|
24
|
+
# Define your persistence logic here.
|
25
|
+
# This method will be called whenever a valid form is saved.
|
26
|
+
"Form saved!"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
form = FormClass.bind name: 'James Dabbs', favorite_number: 11
|
31
|
+
form.valid?
|
32
|
+
=> false
|
33
|
+
form.errors.full_messages
|
34
|
+
=> ["Name Only letters allowed", "Favorite number is not included in the list"]
|
35
|
+
|
36
|
+
form = FormClass.bind name: 'jamesdabbs', favorite_number: '7'
|
37
|
+
form.valid?
|
38
|
+
=> true
|
39
|
+
form.favorite_number
|
40
|
+
=> 7 // Note the type coercion
|
41
|
+
form.save!
|
42
|
+
=> "Form saved!"
|
43
|
+
|
44
|
+
###Forms for Models
|
45
|
+
|
46
|
+
For the common use case of creating or updating a model with a form, Cave provides
|
47
|
+
the [Cave::ModelForm](https://github.com/jamesdabbs/cave/blob/master/lib/cave/model_form.rb) class.
|
48
|
+
|
49
|
+
class ProfileForm < Cave::ModelForm
|
50
|
+
model Profile
|
51
|
+
|
52
|
+
field :name, String, presence: true
|
53
|
+
field :age, Integer
|
54
|
+
end
|
55
|
+
|
56
|
+
ProfileForm.bind(name: 'James').save! # Creates a new Profile named 'James'
|
57
|
+
|
58
|
+
instance = Profile.first
|
59
|
+
ProfileForm.bind(instance, name: 'Jim').save! # Updates the Profile's name
|
60
|
+
|
61
|
+
###Planned features
|
62
|
+
|
63
|
+
- Add presenters for rendering forms into html (inc. a bootstrap template)
|
64
|
+
- Improve handling of intial values (as for unbound ModelForms)
|
65
|
+
- Write docs
|
66
|
+
- Requests?
|
data/Rakefile
ADDED
data/cave.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'cave'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.date = '2013-01-31'
|
5
|
+
s.summary = 'A simple Ruby form library, intended for use with Rails'
|
6
|
+
s.description = 'Cave Forms encapsulate validation logic into an object with that single responsibility'
|
7
|
+
s.authors = ['James Dabbs']
|
8
|
+
s.email = 'jamesdabbs@gmail.com'
|
9
|
+
s.files = %w{ Gemfile Rakefile README.md cave.gemspec }
|
10
|
+
s.files += Dir['lib/**/*.rb']
|
11
|
+
s.test_files = Dir['spec/**/*.rb']
|
12
|
+
s.homepage = 'http://github.com/jamesdabbs/cave'
|
13
|
+
|
14
|
+
s.add_dependency 'activemodel'
|
15
|
+
s.add_dependency 'virtus'
|
16
|
+
end
|
data/lib/cave.rb
ADDED
data/lib/cave/errors.rb
ADDED
data/lib/cave/form.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'virtus'
|
3
|
+
|
4
|
+
module Cave
|
5
|
+
class Form
|
6
|
+
include Virtus
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def fields
|
12
|
+
@_fields ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def field name, type, opts={}
|
16
|
+
@_fields ||= {}
|
17
|
+
@_fields[name] = type
|
18
|
+
|
19
|
+
attribute name, type
|
20
|
+
|
21
|
+
opts.each do |k,v|
|
22
|
+
validates name, {k => v}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def bind *args
|
27
|
+
f = new *args
|
28
|
+
f.instance_eval { @bound = true }
|
29
|
+
f
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
validate :field_coercion
|
35
|
+
|
36
|
+
def initialize attrs={}
|
37
|
+
super
|
38
|
+
bind attrs unless attrs.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def bind attrs={}
|
42
|
+
self.attributes = (attributes || {}).merge attrs
|
43
|
+
@bound = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def bound?
|
47
|
+
@bound
|
48
|
+
end
|
49
|
+
|
50
|
+
def unbound?
|
51
|
+
!@bound
|
52
|
+
end
|
53
|
+
|
54
|
+
def save!
|
55
|
+
raise Cave::ValidationError.new errors.full_messages.join(',') unless valid?
|
56
|
+
persist!
|
57
|
+
end
|
58
|
+
|
59
|
+
def persist!
|
60
|
+
raise "#{self.class} does not define a persist method"
|
61
|
+
end
|
62
|
+
|
63
|
+
private #----------
|
64
|
+
|
65
|
+
def field_coercion
|
66
|
+
self.class.fields.each do |name, type|
|
67
|
+
value = attributes[name]
|
68
|
+
unless value.nil? || value.is_a?(type)
|
69
|
+
errors.add name, "should be a(n) #{type}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Cave
|
2
|
+
class ModelForm < Form
|
3
|
+
def self.model klass=nil
|
4
|
+
@@model ||= klass
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize instance=nil, attrs={}
|
8
|
+
(instance, attrs = nil, instance) if instance.is_a? Hash
|
9
|
+
super attrs
|
10
|
+
self.for instance
|
11
|
+
end
|
12
|
+
|
13
|
+
def for instance
|
14
|
+
@instance = instance
|
15
|
+
check_instance_model
|
16
|
+
end
|
17
|
+
|
18
|
+
def persist!
|
19
|
+
if @instance
|
20
|
+
@instance.update_attributes attributes
|
21
|
+
else
|
22
|
+
@instance = self.class.model.create! attributes
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private #-----------
|
27
|
+
|
28
|
+
def check_instance_model
|
29
|
+
model = self.class.model
|
30
|
+
if @instance
|
31
|
+
raise TypeError.new("Instance #{@instance} is not a #{model}") unless @instance.is_a? model
|
32
|
+
else
|
33
|
+
raise TypeError.new("Please specify a model class to create") unless model.is_a? Class
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ExampleForm < Cave::Form
|
4
|
+
field :name, String, presence: true
|
5
|
+
field :age, Integer
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ExampleForm do
|
9
|
+
context 'when unbound' do
|
10
|
+
subject { ExampleForm.new }
|
11
|
+
|
12
|
+
it { should_not be_bound }
|
13
|
+
it { should_not be_valid }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when bound' do
|
17
|
+
subject { ExampleForm.bind name: 'James', age: 26 }
|
18
|
+
|
19
|
+
it { should be_bound }
|
20
|
+
its(:name) { should == 'James' }
|
21
|
+
|
22
|
+
it 'validates name' do
|
23
|
+
subject.name = ''
|
24
|
+
subject.should_not be_valid
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'coerces age' do
|
28
|
+
subject.age = '27'
|
29
|
+
subject.age.should be 27
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'fails on uncoercible ages' do
|
33
|
+
subject.age = 'invalid'
|
34
|
+
subject.should_not be_valid
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'allows uncoerced nils' do
|
38
|
+
subject.age = nil
|
39
|
+
subject.should be_valid
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when bound with no data' do
|
44
|
+
subject { ExampleForm.bind }
|
45
|
+
|
46
|
+
it { should be_bound }
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when valid' do
|
50
|
+
subject { ExampleForm.bind name: 'James', age: 26 }
|
51
|
+
|
52
|
+
it { should be_valid }
|
53
|
+
|
54
|
+
it 'has no errors' do
|
55
|
+
subject.valid?
|
56
|
+
subject.errors.should be_empty
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'can be saved' do
|
60
|
+
subject.should_receive :persist!
|
61
|
+
subject.save!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when invalid' do
|
66
|
+
let(:form) { ExampleForm.bind }
|
67
|
+
|
68
|
+
it 'has errors' do
|
69
|
+
subject.valid?
|
70
|
+
subject.errors.should be_present
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'cannot be saved' do
|
74
|
+
expect { subject.save! }.to raise_error Cave::ValidationError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class Profile < OpenStruct
|
4
|
+
def create! attrs; end
|
5
|
+
def update_attributes attrs; end
|
6
|
+
end
|
7
|
+
|
8
|
+
class ExampleModelForm < Cave::ModelForm
|
9
|
+
model Profile
|
10
|
+
|
11
|
+
field :name, String
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ExampleModelForm do
|
15
|
+
|
16
|
+
it 'checks instance against model' do
|
17
|
+
expect { ExampleModelForm.new 1 }.to raise_error TypeError
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when new' do
|
21
|
+
subject { ExampleModelForm.new name: 'James' }
|
22
|
+
|
23
|
+
it 'creates' do
|
24
|
+
Profile.should_receive :create!
|
25
|
+
subject.save!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'pre-existing' do
|
30
|
+
let(:instance) { Profile.new name: 'James' }
|
31
|
+
subject { ExampleModelForm.new instance, name: 'Jim' }
|
32
|
+
|
33
|
+
it 'updates' do
|
34
|
+
instance.should_receive(:update_attributes).with(name: 'Jim')
|
35
|
+
subject.save!
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'does not create' do
|
39
|
+
Profile.should_not_receive :create!
|
40
|
+
subject.save!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/spec/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cave
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- James Dabbs
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activemodel
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: virtus
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Cave Forms encapsulate validation logic into an object with that single
|
47
|
+
responsibility
|
48
|
+
email: jamesdabbs@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- Gemfile
|
54
|
+
- Rakefile
|
55
|
+
- README.md
|
56
|
+
- cave.gemspec
|
57
|
+
- lib/cave/errors.rb
|
58
|
+
- lib/cave/form.rb
|
59
|
+
- lib/cave/model_form.rb
|
60
|
+
- lib/cave.rb
|
61
|
+
- spec/gears/form_spec.rb
|
62
|
+
- spec/gears/model_form_spec.rb
|
63
|
+
- spec/helper.rb
|
64
|
+
homepage: http://github.com/jamesdabbs/cave
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.8.24
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: A simple Ruby form library, intended for use with Rails
|
88
|
+
test_files:
|
89
|
+
- spec/gears/form_spec.rb
|
90
|
+
- spec/gears/model_form_spec.rb
|
91
|
+
- spec/helper.rb
|