cave 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|