form_model 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/.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
|