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 ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'pry'
7
+ gem 'rspec'
8
+ end
@@ -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?
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :test => :spec
8
+ task :default => :spec
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'cave/errors'
2
+ require 'cave/form'
3
+ require 'cave/model_form'
4
+
5
+ module Cave
6
+ end
@@ -0,0 +1,4 @@
1
+ module Cave
2
+ class ValidationError < Exception
3
+ end
4
+ end
@@ -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
@@ -0,0 +1,2 @@
1
+ require 'rspec'
2
+ require 'cave'
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