form_model 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +156 -0
- data/Rakefile +1 -0
- data/form_model.gemspec +29 -0
- data/lib/form_model.rb +5 -0
- data/lib/form_model/associated_validation.rb +29 -0
- data/lib/form_model/errors.rb +5 -0
- data/lib/form_model/mapper.rb +117 -0
- data/lib/form_model/model.rb +174 -0
- data/lib/form_model/version.rb +3 -0
- data/spec/mappers_spec.rb +80 -0
- data/spec/scenerios/product_spec.rb +134 -0
- data/spec/scenerios/user_spec.rb +16 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/factories.rb +21 -0
- data/spec/support/forms/product_form.rb +25 -0
- data/spec/support/forms/user_form.rb +11 -0
- data/spec/support/mappers/date_hour_min_mapper.rb +35 -0
- data/spec/support/mappers/dev_email_mapper.rb +17 -0
- data/spec/support/mappers/money_mapper.rb +17 -0
- data/spec/support/models/product.rb +26 -0
- data/spec/support/models/user.rb +22 -0
- data/spec/support/virtus/v_money.rb +18 -0
- metadata +217 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 hookercookerman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# FormModel
|
2
|
+
|
3
|
+
Idea is to have an easy way to create form objects;
|
4
|
+
|
5
|
+
Inspiration
|
6
|
+
[http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/](Code Climate Extract From Objects)
|
7
|
+
|
8
|
+
What are form objects
|
9
|
+
|
10
|
+
1. They can validate your form inputs; (leaving your model to validate
|
11
|
+
domain validations)
|
12
|
+
|
13
|
+
2. hide access to the model; only form attributes are allowed to be
|
14
|
+
updated (mass assignment anyone)
|
15
|
+
|
16
|
+
3. have a single place to show any transformation that the form needs to
|
17
|
+
represent from the model; Example Price Hashes; Time Strings;
|
18
|
+
locations and value objects
|
19
|
+
|
20
|
+
4. Essentially you now have 1 place to do any form stuff
|
21
|
+
|
22
|
+
At present this is tied to mongoid; if you want other support I accept
|
23
|
+
pull requests :)
|
24
|
+
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
Add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
gem 'form_model'
|
31
|
+
|
32
|
+
And then execute:
|
33
|
+
|
34
|
+
$ bundle
|
35
|
+
|
36
|
+
Or install it yourself as:
|
37
|
+
|
38
|
+
$ gem install form_model
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
Typical flow In Rails App
|
43
|
+
|
44
|
+
have a forms directory;
|
45
|
+
|
46
|
+
Create a Form that binds to a partical model class;
|
47
|
+
Define what attributes you want the form to have;
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class ProductForm
|
51
|
+
include FormModel
|
52
|
+
bind_to{Product}
|
53
|
+
|
54
|
+
# Attributes
|
55
|
+
attribute :name, String
|
56
|
+
attribute :title, String
|
57
|
+
attribute :description, String
|
58
|
+
attribute :price, VMoney
|
59
|
+
attribute :selected_price, VMoney
|
60
|
+
|
61
|
+
attribute :start_at_date, String
|
62
|
+
attribute :start_at_hr, String
|
63
|
+
attribute :start_at_min, String
|
64
|
+
|
65
|
+
# Mappers
|
66
|
+
mapper MoneyMapper, form: {price: :price}
|
67
|
+
mapper DateHourMinMapper,
|
68
|
+
form: {date: :start_at_date, hour: :start_at_hr, min: :start_at_min},
|
69
|
+
model: {time: :start_at}
|
70
|
+
|
71
|
+
# Validations
|
72
|
+
validates :title, presence: true
|
73
|
+
validates_associated :price
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
# Creating a Form Object
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
product_form = ProductForm.new
|
81
|
+
```
|
82
|
+
|
83
|
+
As the product form is bound to a product class a new product will be
|
84
|
+
associated with this form.
|
85
|
+
|
86
|
+
# Loading a Form from the model (typical Approach)
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
product = Product.find(params[:id])
|
90
|
+
product_form = ProductForm.load(product)
|
91
|
+
```
|
92
|
+
|
93
|
+
This will map the model attributes on to the form model; applying any
|
94
|
+
specific mappers that have been created for the form object
|
95
|
+
|
96
|
+
# Saving A Form
|
97
|
+
|
98
|
+
A form *save* method will use both validations on the form
|
99
|
+
model and the model to determine if its valid and a save will also call
|
100
|
+
a models save method;
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
product_form = ProductForm.load(product).update(params[:product])
|
104
|
+
if product_form.save
|
105
|
+
# do stuff
|
106
|
+
else
|
107
|
+
# do other stuff
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
# Updating model Explicity
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
product_form = ProductForm.new
|
115
|
+
product_form.update(params[:product])
|
116
|
+
product_form.update_data_model!
|
117
|
+
```
|
118
|
+
|
119
|
+
updating a form with a hash will only update the form; if you wish to
|
120
|
+
set the attributes on the model you will need to call update_data_model!
|
121
|
+
*NOTE* this will not save the model
|
122
|
+
|
123
|
+
# Mappers
|
124
|
+
|
125
|
+
if you need to transform data in and out of forms and models you can
|
126
|
+
create mappers; as shown in the productForm example; look at the
|
127
|
+
spec/support mappers for examples
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
class MoneyMapper
|
131
|
+
include FormModel::Mapper
|
132
|
+
form_keys :price
|
133
|
+
model_keys :price
|
134
|
+
|
135
|
+
def to_form(model)
|
136
|
+
assert_model_values(model, :price) do |values|
|
137
|
+
{form_attribute(:price) => VMoney.new(values[:price])}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_model(form)
|
142
|
+
assert_form_values(form, :price) do |values|
|
143
|
+
{model_attribute(:price) => values[:price].to_hash.stringify_keys}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
|
150
|
+
## Contributing
|
151
|
+
|
152
|
+
1. Fork it
|
153
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
154
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
155
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
156
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/form_model.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'form_model/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "form_model"
|
8
|
+
gem.version = FormModel::VERSION
|
9
|
+
gem.authors = ["hookercookerman"]
|
10
|
+
gem.email = ["hookercookerman@gmail.com"]
|
11
|
+
gem.description = %q{A Library to create Form objects to encapsulate forms}
|
12
|
+
gem.summary = %q{A Library to create Form objects to encapsulate forms}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'virtus', '>=0.5.0'
|
21
|
+
gem.add_dependency "activemodel", ">= 3.0.0"
|
22
|
+
gem.add_dependency "activesupport", ">= 3.0.0"
|
23
|
+
|
24
|
+
gem.add_development_dependency "rspec", "~> 2.11.0"
|
25
|
+
gem.add_development_dependency "factory_girl", "~> 4.0.0"
|
26
|
+
gem.add_development_dependency "fuubar", "~> 1.0.0"
|
27
|
+
gem.add_development_dependency "activemodel", "~> 3.2.8"
|
28
|
+
gem.add_development_dependency 'rake'
|
29
|
+
end
|
data/lib/form_model.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module FormModel
|
3
|
+
class AssociatedValidator < ActiveModel::EachValidator
|
4
|
+
|
5
|
+
# Validates that the associations provided are either all nil or all
|
6
|
+
# valid. If neither is true then the appropriate errors will be added to
|
7
|
+
# the parent document.
|
8
|
+
#
|
9
|
+
# @example Validate the association.
|
10
|
+
# validator.validate_each(document, :name, name)
|
11
|
+
#
|
12
|
+
# @param [ Document ] document The document to validate.
|
13
|
+
# @param [ Symbol ] attribute The relation to validate.
|
14
|
+
# @param [ Object ] value The value of the relation.
|
15
|
+
def validate_each(form, attribute, value)
|
16
|
+
begin
|
17
|
+
valid = Array.wrap(value).collect do |doc|
|
18
|
+
if doc.nil?
|
19
|
+
true
|
20
|
+
else
|
21
|
+
doc.valid?
|
22
|
+
end
|
23
|
+
end.all?
|
24
|
+
end
|
25
|
+
form.errors.add(attribute, :invalid, options) unless valid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
require "active_model/validations"
|
3
|
+
|
4
|
+
module FormModel
|
5
|
+
module Mapper
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :_form_keys
|
11
|
+
class_attribute :_model_keys
|
12
|
+
self._form_keys = []
|
13
|
+
self._model_keys = []
|
14
|
+
|
15
|
+
attr_accessor :_form_keys_values
|
16
|
+
attr_accessor :_model_keys_values
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
extract_options!(options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def form_keys(*keys)
|
25
|
+
self._form_keys += keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def model_keys(*keys)
|
29
|
+
self._model_keys += keys
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert_model_values(model, *values, &block)
|
34
|
+
hash = Array(values).inject({}) do |var, key|
|
35
|
+
value = model_value(model, key)
|
36
|
+
raise FormModel::MapperAssertionError if value.nil?
|
37
|
+
var[key] = value
|
38
|
+
var
|
39
|
+
end
|
40
|
+
block.call(hash)
|
41
|
+
rescue FormModel::MapperAssertionError
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert_form_values(form, *values, &block)
|
46
|
+
hash = Array(values).inject({}) do |var, key|
|
47
|
+
value = form_value(form, key)
|
48
|
+
raise FormModel::MapperAssertionError if value.nil?
|
49
|
+
var[key] = value
|
50
|
+
var
|
51
|
+
end
|
52
|
+
block.call(hash)
|
53
|
+
rescue FormModel::MapperAssertionError
|
54
|
+
{}
|
55
|
+
end
|
56
|
+
|
57
|
+
def form_keys
|
58
|
+
self.class._form_keys
|
59
|
+
end
|
60
|
+
|
61
|
+
def model_keys
|
62
|
+
self.class._model_keys
|
63
|
+
end
|
64
|
+
|
65
|
+
def form_value(form, key)
|
66
|
+
if value_key = _form_keys_values[key]
|
67
|
+
form.send(value_key)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def form_attribute(key)
|
72
|
+
_form_keys_values[key].to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
def model_value(model, key)
|
76
|
+
if value_key = _model_keys_values[key]
|
77
|
+
model.send(value_key)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def model_attribute(key)
|
82
|
+
_model_keys_values[key].to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_form(attributes)
|
86
|
+
{}
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_model(attribute)
|
90
|
+
{}
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
def extract_options!(options)
|
95
|
+
form_options = options[:form] || options[:model] || {}
|
96
|
+
model_options = options[:model] || options[:form] || {}
|
97
|
+
extract_form_options!(form_options)
|
98
|
+
extract_model_options!(model_options)
|
99
|
+
check_have_all_values
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_have_all_values
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def extract_form_options!(options)
|
107
|
+
raise Exception.new unless options.keys.all?{|key| _form_keys.include?(key)}
|
108
|
+
@_form_keys_values = options
|
109
|
+
end
|
110
|
+
|
111
|
+
def extract_model_options!(options)
|
112
|
+
raise Exception.new unless options.keys.all?{|key| _model_keys.include?(key)}
|
113
|
+
@_model_keys_values = options
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require "active_model"
|
2
|
+
require "virtus"
|
3
|
+
require "active_support/concern"
|
4
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
5
|
+
require 'active_support/core_ext/hash/slice'
|
6
|
+
require 'active_support/core_ext/hash/keys'
|
7
|
+
|
8
|
+
module FormModel
|
9
|
+
include Virtus
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
include ActiveModel::Validations
|
12
|
+
|
13
|
+
included do
|
14
|
+
class_attribute :after_read_block
|
15
|
+
class_attribute :before_write_block
|
16
|
+
class_attribute :bound_block
|
17
|
+
class_attribute :mappers
|
18
|
+
self.mappers = []
|
19
|
+
|
20
|
+
attr_accessor :data_model
|
21
|
+
alias :model= :data_model=
|
22
|
+
alias :model :data_model
|
23
|
+
|
24
|
+
def initialize(model, attributes = nil)
|
25
|
+
if model.is_a?(Hash)
|
26
|
+
super(model)
|
27
|
+
else
|
28
|
+
super(attributes)
|
29
|
+
@data_model = model
|
30
|
+
assert_correct_model
|
31
|
+
apply_mappers_to_form!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
def validates_associated(*args)
|
38
|
+
validates_with(FormModel::AssociatedValidator, _merge_attributes(args))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid?(type = :model_and_form)
|
43
|
+
return super if type == :form
|
44
|
+
update_data_model!
|
45
|
+
ok = (super and data_model.valid?)
|
46
|
+
merge_data_model_errors! unless ok
|
47
|
+
ok
|
48
|
+
end
|
49
|
+
|
50
|
+
def merge_errors_with!(object)
|
51
|
+
object.errors.to_hash.each do |key, value|
|
52
|
+
self.errors.add(key, value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def bound_class
|
57
|
+
self.class.bound_class
|
58
|
+
end
|
59
|
+
|
60
|
+
def save
|
61
|
+
valid? ? data_model.save : false
|
62
|
+
end
|
63
|
+
|
64
|
+
def form_valid?
|
65
|
+
valid?(:form)
|
66
|
+
end
|
67
|
+
|
68
|
+
def update(attrs = {})
|
69
|
+
self.attributes = attrs || {}
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def persisted?
|
74
|
+
data_model.persisted?
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_model
|
78
|
+
data_model.to_model
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_key
|
82
|
+
data_model.to_key
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_param
|
86
|
+
data_model.to_param
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_path
|
90
|
+
data_model.to_path
|
91
|
+
end
|
92
|
+
|
93
|
+
def update_data_model!
|
94
|
+
unless data_model
|
95
|
+
@data_model = bound_class.new
|
96
|
+
end
|
97
|
+
attrs = attributes.slice(*data_model_attribute_names).stringify_keys
|
98
|
+
apply_mappers_to_model!(attrs)
|
99
|
+
self.instance_exec(&before_write_block) unless self.class.before_write_block.nil?
|
100
|
+
data_model.write_attributes(attrs)
|
101
|
+
data_model
|
102
|
+
end
|
103
|
+
|
104
|
+
def respond_to?(method_sym, include_private = false)
|
105
|
+
super || data_model.respond_to?(method_sym, include_private)
|
106
|
+
end
|
107
|
+
|
108
|
+
module ClassMethods
|
109
|
+
def bind_to(&block)
|
110
|
+
self.bound_block = block
|
111
|
+
end
|
112
|
+
|
113
|
+
def mapper(mapper_class, options = {})
|
114
|
+
mappers << mapper_class.new(options)
|
115
|
+
end
|
116
|
+
|
117
|
+
def after_read &block
|
118
|
+
self.after_read_block = block
|
119
|
+
end
|
120
|
+
|
121
|
+
def before_write &block
|
122
|
+
self.before_write_block = block
|
123
|
+
end
|
124
|
+
|
125
|
+
def bound_class
|
126
|
+
@bound_class ||= self.bound_block.call
|
127
|
+
end
|
128
|
+
|
129
|
+
def form_attributes
|
130
|
+
self.attribute_set.map{|attr| attr.name.to_s}
|
131
|
+
end
|
132
|
+
|
133
|
+
def load(data_model)
|
134
|
+
data_model_attributes = data_model.attributes
|
135
|
+
attrs = data_model_attributes.slice(*form_attributes)
|
136
|
+
self.new(data_model, attrs).tap do |instance|
|
137
|
+
instance.instance_exec(&after_read_block) unless after_read_block.nil?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
def merge_data_model_errors!
|
144
|
+
data_model.errors.to_hash.each do |key, value|
|
145
|
+
self.errors.add(key, value)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def assert_correct_model
|
150
|
+
if !data_model.is_a?(bound_class)
|
151
|
+
raise ModelMisMatchError.new("Tried to use object of class #{data_model.class.name} only #{bound_class.name} allowed")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def apply_mappers_to_form!
|
156
|
+
mappers.each do |mapper|
|
157
|
+
self.attributes = self.attributes.merge(mapper.to_form(data_model))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def apply_mappers_to_model!(attrs)
|
162
|
+
mappers.each do |mapper|
|
163
|
+
attrs.merge!(mapper.to_model(self))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def method_missing(method, *args, &block)
|
168
|
+
data_model.send(method, *args, &block)
|
169
|
+
end
|
170
|
+
|
171
|
+
def data_model_attribute_names
|
172
|
+
data_model.fields.keys.map(&:to_sym) + data_model.relations.keys.map(&:to_sym)
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path("../spec_helper", __FILE__)
|
3
|
+
|
4
|
+
describe "FormMode::Mappers" do
|
5
|
+
context "new mapper" do
|
6
|
+
let(:new_mapper) do
|
7
|
+
Class.new do
|
8
|
+
include FormModel::Mapper
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to set form keys" do
|
13
|
+
new_mapper.should respond_to(:form_keys)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be able to set form keys" do
|
17
|
+
new_mapper.should respond_to(:model_keys)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "mapper instance with set keys" do
|
22
|
+
let(:new_mapper) do
|
23
|
+
Class.new do
|
24
|
+
include FormModel::Mapper
|
25
|
+
form_keys :price
|
26
|
+
model_keys :price
|
27
|
+
end
|
28
|
+
end
|
29
|
+
let(:options){{form: {price: :price}}}
|
30
|
+
let(:form){double(:price => "form_price")}
|
31
|
+
let(:model){double(:price => "model_price")}
|
32
|
+
|
33
|
+
subject{new_mapper.new(options)}
|
34
|
+
|
35
|
+
it "should have a form attribute price" do
|
36
|
+
subject.form_attribute(:price).should eq("price")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should have a model attribute price" do
|
40
|
+
subject.form_attribute(:price).should eq("price")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to the form value" do
|
44
|
+
subject.form_value(form, :price).should eq("form_price")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should be able to the model value" do
|
48
|
+
subject.model_value(model, :price).should eq("model_price")
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#asset_form_values" do
|
52
|
+
it "should return a filled hash with values are not nil" do
|
53
|
+
subject.assert_form_values(form, :price) do |values|
|
54
|
+
values.should eq({:price => "form_price"})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return empty hash if one of the values is nil" do
|
59
|
+
subject.assert_form_values(form, :price, :beans) do |values|
|
60
|
+
values.should eq({:price => "form_price"})
|
61
|
+
end.should eq({})
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#asset_model_values" do
|
66
|
+
it "should return a filled hash with values are not nil" do
|
67
|
+
subject.assert_model_values(model, :price) do |values|
|
68
|
+
values.should eq({:price => "model_price"})
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return empty hash if one of the values is nil" do
|
73
|
+
subject.assert_model_values(model, :price, :beans) do |values|
|
74
|
+
values.should eq({:price => "model_price"})
|
75
|
+
end.should eq({})
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
3
|
+
|
4
|
+
describe "ProductForm" do
|
5
|
+
let(:product) do
|
6
|
+
FactoryGirl.build :product
|
7
|
+
end
|
8
|
+
subject{ProductForm.load(product)}
|
9
|
+
before {product.stub(:persisted? => false)}
|
10
|
+
|
11
|
+
it "should delegate persisted?" do
|
12
|
+
product.should_receive(:persisted?)
|
13
|
+
subject.persisted?
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "when loading from a new product" do
|
17
|
+
let(:product) do
|
18
|
+
FactoryGirl.build :product
|
19
|
+
end
|
20
|
+
subject{ProductForm.load(product)}
|
21
|
+
before{subject.stub(data_model_attribute_names: [:price])}
|
22
|
+
its(:name){should be_nil}
|
23
|
+
its(:title){should be_nil}
|
24
|
+
its(:description){should be_nil}
|
25
|
+
its(:valid?){should be_false}
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "when loading form with incorrect model type" do
|
29
|
+
let(:user) {FactoryGirl.build(:user)}
|
30
|
+
it "should raise Exception" do
|
31
|
+
expect{ProductForm.load(user)}.to raise_error(FormModel::ModelMisMatchError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "when loading from a product with given attributes" do
|
36
|
+
let(:product) do
|
37
|
+
FactoryGirl.build :product, title: "testing title", description: "testing description", price: {cents: 10}
|
38
|
+
end
|
39
|
+
subject{ProductForm.load(product)}
|
40
|
+
before{subject.stub(data_model_attribute_names: [:price])}
|
41
|
+
its(:title){should eq("testing title")}
|
42
|
+
its(:name){should be_nil}
|
43
|
+
its(:description){should eq("testing description")}
|
44
|
+
its(:valid?){should be_true}
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "validation" do
|
48
|
+
let(:product) do
|
49
|
+
FactoryGirl.build :product, title: "testing title", description: "testing description"
|
50
|
+
end
|
51
|
+
subject{ProductForm.load(product)}
|
52
|
+
before {subject.stub(data_model_attribute_names: [:price])}
|
53
|
+
|
54
|
+
it "should not be valid as the data model is not valid" do
|
55
|
+
product.stub(valid?: false, errors: {price: "not valid"})
|
56
|
+
subject.should_not be_valid
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not be valid as price is not valid" do
|
60
|
+
product.stub(valid?: true)
|
61
|
+
subject.should_not be_valid
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "save" do
|
66
|
+
let(:product) do
|
67
|
+
FactoryGirl.build :product, title: "testing title", description: "testing description", price: {cents: 10}
|
68
|
+
end
|
69
|
+
subject{ProductForm.load(product)}
|
70
|
+
before do
|
71
|
+
subject.stub(data_model_attribute_names: [:title])
|
72
|
+
product.stub(save: true)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should write_attributes to the model" do
|
76
|
+
product.should_receive(:write_attributes)
|
77
|
+
subject.save
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should save to the model" do
|
81
|
+
product.should_receive(:write_attributes)
|
82
|
+
subject.save
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should check validation" do
|
86
|
+
subject.should_receive(:save)
|
87
|
+
subject.save
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "when mapping a price of 50 GBP to the form model" do
|
92
|
+
let(:product) {FactoryGirl.build(:product, price: {"currency" => "GBP", cents: 50})}
|
93
|
+
subject{ProductForm.load(product)}
|
94
|
+
its(:price){should eq(VMoney.new(currency: "GBP", cents: 50))}
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "when setting money via amount" do
|
98
|
+
let(:product) {FactoryGirl.build(:product, price: {"currency" => "GBP", cents: 50})}
|
99
|
+
|
100
|
+
before do
|
101
|
+
@form = ProductForm.load(product)
|
102
|
+
@form.stub(data_model_attribute_names: [:price])
|
103
|
+
@form.update(price: {"amount" => "20.0"})
|
104
|
+
@form.update_data_model!
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should set a price hash on the product" do
|
108
|
+
product.price["cents"].should eq(2000)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "when mapping a vmoney price from the form to the model" do
|
113
|
+
let(:product) {FactoryGirl.build(:product, price: {"currency" => "GBP", cents: 50})}
|
114
|
+
before do
|
115
|
+
@form = ProductForm.load(product)
|
116
|
+
@form.stub(data_model_attribute_names: [:price])
|
117
|
+
@form.price = {"currency" => "EUR"}
|
118
|
+
@form.update_data_model!
|
119
|
+
end
|
120
|
+
it "should set a price hash on the product" do
|
121
|
+
product.price["currency"].should eq("EUR")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "when mapping a start_time with of now" do
|
126
|
+
let(:now){Time.now}
|
127
|
+
let(:split_time){now.strftime("%R").split(?:)}
|
128
|
+
let(:product) {FactoryGirl.build(:product, start_at: now)}
|
129
|
+
subject{ProductForm.load(product)}
|
130
|
+
its(:start_at_date){ should eq(now.to_date.to_s)}
|
131
|
+
its(:start_at_hr) { should eq(split_time.first)}
|
132
|
+
its(:start_at_min) { should eq(split_time.last)}
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
3
|
+
|
4
|
+
describe "User" do
|
5
|
+
let(:user) {FactoryGirl.build(:user)}
|
6
|
+
subject{UserForm.load(user)}
|
7
|
+
before do
|
8
|
+
subject.stub(data_model_attribute_names: [:email])
|
9
|
+
subject.email = "hookercookerman@gmail.com"
|
10
|
+
subject.update_data_model!
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should map a real email to a dev email" do
|
14
|
+
user.email.should eq("hookercookerman$gmail.com")
|
15
|
+
end
|
16
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
unless ENV["TRAVIS"]
|
5
|
+
require 'simplecov'
|
6
|
+
SimpleCov.start do
|
7
|
+
add_group "lib", "lib"
|
8
|
+
add_group "spec", "spec"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require "pry"
|
13
|
+
require "form_model"
|
14
|
+
|
15
|
+
Dir.glob(File.expand_path('../../spec/support/virtus/**/*.rb', __FILE__)) do |file|
|
16
|
+
require file
|
17
|
+
end
|
18
|
+
|
19
|
+
Dir.glob(File.expand_path('../../spec/support/mappers/**/*.rb', __FILE__)) do |file|
|
20
|
+
require file
|
21
|
+
end
|
22
|
+
|
23
|
+
Dir.glob(File.expand_path('../../spec/support/**/*.rb', __FILE__)) do |file|
|
24
|
+
require file
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec.configure do |config|
|
28
|
+
config.filter_run :focus => true
|
29
|
+
config.run_all_when_everything_filtered = true
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'factory_girl'
|
2
|
+
|
3
|
+
FactoryGirl.define do
|
4
|
+
|
5
|
+
factory :product do
|
6
|
+
trait :base do
|
7
|
+
sequence(:id)
|
8
|
+
name {'Magic'}
|
9
|
+
description {"Do it like someone else"}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
factory :user do
|
14
|
+
trait :base do
|
15
|
+
sequence(:id)
|
16
|
+
name {'Magic'}
|
17
|
+
email {"test@test.com"}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class ProductForm
|
2
|
+
include FormModel
|
3
|
+
bind_to{Product}
|
4
|
+
|
5
|
+
# Attributes
|
6
|
+
attribute :name, String
|
7
|
+
attribute :title, String
|
8
|
+
attribute :description, String
|
9
|
+
attribute :price, VMoney
|
10
|
+
attribute :selected_price, VMoney
|
11
|
+
|
12
|
+
attribute :start_at_date, String
|
13
|
+
attribute :start_at_hr, String
|
14
|
+
attribute :start_at_min, String
|
15
|
+
|
16
|
+
# Mappers
|
17
|
+
mapper MoneyMapper, form: {price: :price}
|
18
|
+
mapper DateHourMinMapper,
|
19
|
+
form: {date: :start_at_date, hour: :start_at_hr, min: :start_at_min},
|
20
|
+
model: {time: :start_at}
|
21
|
+
|
22
|
+
# Validations
|
23
|
+
validates :title, presence: true
|
24
|
+
validates_associated :price
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class DateHourMinMapper
|
2
|
+
include FormModel::Mapper
|
3
|
+
|
4
|
+
form_keys :date, :hour, :min
|
5
|
+
model_keys :time
|
6
|
+
|
7
|
+
def to_form(model)
|
8
|
+
assert_model_values(model, :time) do |values|
|
9
|
+
time = values[:time]
|
10
|
+
split_time = split_time(time)
|
11
|
+
{
|
12
|
+
form_attribute(:date) => time.to_date.to_s,
|
13
|
+
form_attribute(:hour) => split_time.first,
|
14
|
+
form_attribute(:min) => split_time.last
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_model(form)
|
20
|
+
assert_form_values(form, :date, :hour, :min) do |values|
|
21
|
+
{model_attribute(:time) => time_from_form(values)}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def split_time(time)
|
27
|
+
time.strftime("%R").split(?:)
|
28
|
+
end
|
29
|
+
|
30
|
+
def time_from_form(values)
|
31
|
+
time = Date.parse(values[:date]).to_time
|
32
|
+
time.change(:hour => values[:hour], min: values[:min])
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class DevEmailMapper
|
2
|
+
include FormModel::Mapper
|
3
|
+
form_keys :email
|
4
|
+
model_keys :email
|
5
|
+
|
6
|
+
def to_form(model)
|
7
|
+
assert_model_values(model, :email) do |values|
|
8
|
+
{form_attribute(:email) => values[:email].gsub("$", "@")}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_model(form)
|
13
|
+
assert_form_values(form, :email) do |values|
|
14
|
+
{model_attribute(:email) => values[:email].gsub("@", "$")}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class MoneyMapper
|
2
|
+
include FormModel::Mapper
|
3
|
+
form_keys :price
|
4
|
+
model_keys :price
|
5
|
+
|
6
|
+
def to_form(model)
|
7
|
+
assert_model_values(model, :price) do |values|
|
8
|
+
{form_attribute(:price) => VMoney.new(values[:price])}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_model(form)
|
13
|
+
assert_form_values(form, :price) do |values|
|
14
|
+
{model_attribute(:price) => values[:price].to_hash.stringify_keys}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Product
|
2
|
+
attr_accessor :id, :price, :title, :name, :description, :start_at
|
3
|
+
|
4
|
+
def price
|
5
|
+
@price ||= {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def attributes
|
9
|
+
{"id" => id, "price" => price, "description" => description, "title" => title, start_at: start_at}
|
10
|
+
end
|
11
|
+
|
12
|
+
def write_attributes(attributes)
|
13
|
+
attributes.each do |attr, value|
|
14
|
+
self.send("#{attr}=", value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def errors
|
23
|
+
{}
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class User
|
2
|
+
attr_accessor :id, :name, :email
|
3
|
+
|
4
|
+
def attributes
|
5
|
+
{"id" => id, "name" => name, "email" => email}
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_attributes(attributes)
|
9
|
+
attributes.each do |attr, value|
|
10
|
+
self.send("#{attr}=", value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def errors
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class VMoney
|
2
|
+
include Virtus::ValueObject
|
3
|
+
include ActiveModel::Validations
|
4
|
+
|
5
|
+
# attributes
|
6
|
+
attribute :cents, Integer, default: 0
|
7
|
+
attribute :currency, String
|
8
|
+
|
9
|
+
def amount
|
10
|
+
sprintf "%0.02f", (BigDecimal(cents) / BigDecimal("100"))
|
11
|
+
end
|
12
|
+
|
13
|
+
def amount=(value)
|
14
|
+
self.cents = (BigDecimal(value) * BigDecimal("100")).to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
validates :cents, presence: true, :numericality => { :only_integer => true, greater_than: 0 }
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: form_model
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- hookercookerman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: virtus
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.5.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.5.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activemodel
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 3.0.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: 3.0.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activesupport
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 3.0.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.11.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.11.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: factory_girl
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 4.0.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 4.0.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: fuubar
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.0.0
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.0.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: activemodel
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 3.2.8
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 3.2.8
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rake
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
description: A Library to create Form objects to encapsulate forms
|
143
|
+
email:
|
144
|
+
- hookercookerman@gmail.com
|
145
|
+
executables: []
|
146
|
+
extensions: []
|
147
|
+
extra_rdoc_files: []
|
148
|
+
files:
|
149
|
+
- .gitignore
|
150
|
+
- Gemfile
|
151
|
+
- LICENSE.txt
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- form_model.gemspec
|
155
|
+
- lib/form_model.rb
|
156
|
+
- lib/form_model/associated_validation.rb
|
157
|
+
- lib/form_model/errors.rb
|
158
|
+
- lib/form_model/mapper.rb
|
159
|
+
- lib/form_model/model.rb
|
160
|
+
- lib/form_model/version.rb
|
161
|
+
- spec/mappers_spec.rb
|
162
|
+
- spec/scenerios/product_spec.rb
|
163
|
+
- spec/scenerios/user_spec.rb
|
164
|
+
- spec/spec_helper.rb
|
165
|
+
- spec/support/factories.rb
|
166
|
+
- spec/support/forms/product_form.rb
|
167
|
+
- spec/support/forms/user_form.rb
|
168
|
+
- spec/support/mappers/date_hour_min_mapper.rb
|
169
|
+
- spec/support/mappers/dev_email_mapper.rb
|
170
|
+
- spec/support/mappers/money_mapper.rb
|
171
|
+
- spec/support/models/product.rb
|
172
|
+
- spec/support/models/user.rb
|
173
|
+
- spec/support/virtus/v_money.rb
|
174
|
+
homepage: ''
|
175
|
+
licenses: []
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
182
|
+
requirements:
|
183
|
+
- - ! '>='
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
segments:
|
187
|
+
- 0
|
188
|
+
hash: -2564772485630625639
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
none: false
|
191
|
+
requirements:
|
192
|
+
- - ! '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
segments:
|
196
|
+
- 0
|
197
|
+
hash: -2564772485630625639
|
198
|
+
requirements: []
|
199
|
+
rubyforge_project:
|
200
|
+
rubygems_version: 1.8.23
|
201
|
+
signing_key:
|
202
|
+
specification_version: 3
|
203
|
+
summary: A Library to create Form objects to encapsulate forms
|
204
|
+
test_files:
|
205
|
+
- spec/mappers_spec.rb
|
206
|
+
- spec/scenerios/product_spec.rb
|
207
|
+
- spec/scenerios/user_spec.rb
|
208
|
+
- spec/spec_helper.rb
|
209
|
+
- spec/support/factories.rb
|
210
|
+
- spec/support/forms/product_form.rb
|
211
|
+
- spec/support/forms/user_form.rb
|
212
|
+
- spec/support/mappers/date_hour_min_mapper.rb
|
213
|
+
- spec/support/mappers/dev_email_mapper.rb
|
214
|
+
- spec/support/mappers/money_mapper.rb
|
215
|
+
- spec/support/models/product.rb
|
216
|
+
- spec/support/models/user.rb
|
217
|
+
- spec/support/virtus/v_money.rb
|