conformista 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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +6 -0
- data/.travis.yml +5 -0
- data/.yardopts +8 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +58 -0
- data/HISTORY.md +6 -0
- data/LICENSE +19 -0
- data/README.md +173 -0
- data/Rakefile +15 -0
- data/conformista.gemspec +46 -0
- data/lib/conformista.rb +9 -0
- data/lib/conformista/form_object.rb +123 -0
- data/lib/conformista/presenting.rb +117 -0
- data/lib/conformista/transactions.rb +20 -0
- data/lib/conformista/validations.rb +36 -0
- data/lib/conformista/version.rb +3 -0
- data/spec/conformista/form_object_spec.rb +126 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/active_model_examples.rb +44 -0
- data/spec/support/post.rb +22 -0
- metadata +173 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 1f04bcb7b29bfa87a83e80616ed0052d3b25a257
|
|
4
|
+
data.tar.gz: 19965cebba0c769c08cecac890457305da0ba9f9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 14fbe9eb6bc28bc6cbbc43a4fcc2931182a0f1f38e71cfc0d96a78e594bde95eff0f69fe260e5082ac35a27f56169e2547978738e7f6bceec21a78e9823f1bc1
|
|
7
|
+
data.tar.gz: 65723d4816355a3a2a829617f982b904a2250d2114822daa4f6fec8105e86b3a4eae61f902790335960a0556cfea50978e485071f8b3c258a986782afca7e714
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
conformista (0.0.1)
|
|
5
|
+
activemodel
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
activemodel (4.0.0)
|
|
11
|
+
activesupport (= 4.0.0)
|
|
12
|
+
builder (~> 3.1.0)
|
|
13
|
+
activerecord (4.0.0)
|
|
14
|
+
activemodel (= 4.0.0)
|
|
15
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
|
16
|
+
activesupport (= 4.0.0)
|
|
17
|
+
arel (~> 4.0.0)
|
|
18
|
+
activerecord-deprecated_finders (1.0.3)
|
|
19
|
+
activesupport (4.0.0)
|
|
20
|
+
i18n (~> 0.6, >= 0.6.4)
|
|
21
|
+
minitest (~> 4.2)
|
|
22
|
+
multi_json (~> 1.3)
|
|
23
|
+
thread_safe (~> 0.1)
|
|
24
|
+
tzinfo (~> 0.3.37)
|
|
25
|
+
arel (4.0.0)
|
|
26
|
+
atomic (1.1.14)
|
|
27
|
+
builder (3.1.4)
|
|
28
|
+
diff-lcs (1.2.4)
|
|
29
|
+
i18n (0.6.5)
|
|
30
|
+
kramdown (1.2.0)
|
|
31
|
+
minitest (4.7.5)
|
|
32
|
+
multi_json (1.8.0)
|
|
33
|
+
rake (10.1.0)
|
|
34
|
+
rspec (2.14.1)
|
|
35
|
+
rspec-core (~> 2.14.0)
|
|
36
|
+
rspec-expectations (~> 2.14.0)
|
|
37
|
+
rspec-mocks (~> 2.14.0)
|
|
38
|
+
rspec-core (2.14.5)
|
|
39
|
+
rspec-expectations (2.14.3)
|
|
40
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
|
41
|
+
rspec-mocks (2.14.3)
|
|
42
|
+
sqlite3 (1.3.8)
|
|
43
|
+
thread_safe (0.1.3)
|
|
44
|
+
atomic
|
|
45
|
+
tzinfo (0.3.37)
|
|
46
|
+
yard (0.8.7.2)
|
|
47
|
+
|
|
48
|
+
PLATFORMS
|
|
49
|
+
ruby
|
|
50
|
+
|
|
51
|
+
DEPENDENCIES
|
|
52
|
+
activerecord
|
|
53
|
+
conformista!
|
|
54
|
+
kramdown
|
|
55
|
+
rake
|
|
56
|
+
rspec
|
|
57
|
+
sqlite3
|
|
58
|
+
yard
|
data/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (C) 2013 Arjan van der Gaag
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
8
|
+
so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Conformista [](http://travis-ci.org/avdgaag/conformista)
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
Conformista is a library to make building presenters -- and form objects in
|
|
6
|
+
particular -- easier. It provides an ActiveModel-compliant base class that your
|
|
7
|
+
own form objects can inherit from, along with standard behaviour for creating,
|
|
8
|
+
loading, validating and persisting business objects (usually ActiveRecord
|
|
9
|
+
models).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Conformista is distributed as a Ruby gem, which should be installed on most Macs and
|
|
14
|
+
Linux systems. Once you have ensured you have a working installation of Ruby
|
|
15
|
+
and Ruby gems, install the gem as follows from the command line:
|
|
16
|
+
|
|
17
|
+
$ gem install conformista
|
|
18
|
+
|
|
19
|
+
To use it with Bundler, add it to your `Gemfile`:
|
|
20
|
+
|
|
21
|
+
gem 'conformista'
|
|
22
|
+
|
|
23
|
+
Then install it by running `bundle install`.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
The canonical example of a form object is a sign up form. Let's say we want to
|
|
28
|
+
let a visitor to our Rails application sign up for an account. That will include
|
|
29
|
+
creating an `Account`, `User` and `Profile`.
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
class Account < ActiveRecord::Base
|
|
33
|
+
validates :name, presence: true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class User < ActiveRecord::Base
|
|
37
|
+
validates :email, :password, presence: true
|
|
38
|
+
has_secure_password
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Profile < ActiveRecord::Base
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Instead of using `accepts_nested_attributes_for`, or cramming all the data in
|
|
46
|
+
the `User` model, or using a lot of controller logic, we use a form object to
|
|
47
|
+
present these three entities as a single object to the view:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
class SignupController < ApplicationController
|
|
51
|
+
def new
|
|
52
|
+
@signup = Signup.new
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def create
|
|
56
|
+
@signup = Signup.new(params[:signup])
|
|
57
|
+
if @signup.save
|
|
58
|
+
redirect_to root_url, notice: 'Thanks for signing up!'
|
|
59
|
+
else
|
|
60
|
+
render 'new'
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The single object we use is a form object that inherits from
|
|
67
|
+
`Conformista::FormObject`:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
class Signup < Conformista::FormObject
|
|
71
|
+
presents User => %i[email password],
|
|
72
|
+
Account => %i[name],
|
|
73
|
+
Profile => %i[bio twitter github]
|
|
74
|
+
|
|
75
|
+
attr_accessor :password_confirmation,
|
|
76
|
+
:terms_and_conditions
|
|
77
|
+
|
|
78
|
+
validates :password, confirmation: true
|
|
79
|
+
validates :terms_and_conditions, acceptance: true
|
|
80
|
+
|
|
81
|
+
after_save :deliver_welcome_email
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def deliver_welcome_email
|
|
86
|
+
Notifications.welcome_email(user).deliver
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
We can now generate a form for our form object:
|
|
92
|
+
|
|
93
|
+
```erb
|
|
94
|
+
<%= form_for @signup, url: signup_path do |f| %>
|
|
95
|
+
<p>
|
|
96
|
+
<%= f.label :name %><br>
|
|
97
|
+
<%= f.text_field :name %>
|
|
98
|
+
</p>
|
|
99
|
+
<p>
|
|
100
|
+
<%= f.label :email %><br>
|
|
101
|
+
<%= f.text_field :email %>
|
|
102
|
+
</p>
|
|
103
|
+
<p>
|
|
104
|
+
<%= f.label :password %>
|
|
105
|
+
<%= f.password_field :password %>
|
|
106
|
+
</p>
|
|
107
|
+
<p>
|
|
108
|
+
<%= f.label :password_confirmation %><br>
|
|
109
|
+
<%= f.password_field :password_confirmation %>
|
|
110
|
+
</p>
|
|
111
|
+
<p>
|
|
112
|
+
<%= f.label :bio %><br>
|
|
113
|
+
<%= f.text_area :bio %><br>
|
|
114
|
+
</p>
|
|
115
|
+
<p>
|
|
116
|
+
<%= f.label :twitter %><br>
|
|
117
|
+
<%= f.text_field :twitter %><br>
|
|
118
|
+
</p>
|
|
119
|
+
<p>
|
|
120
|
+
<%= f.label :github %><br>
|
|
121
|
+
<%= f.text_field :github %><br>
|
|
122
|
+
</p>
|
|
123
|
+
<p>
|
|
124
|
+
<%= f.check_box :terms_and_conditions %>
|
|
125
|
+
I have read and agree to the terms and conditions
|
|
126
|
+
</p>
|
|
127
|
+
<p>
|
|
128
|
+
<%= f.submit %>
|
|
129
|
+
</p>
|
|
130
|
+
<% end %>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Note how the logic specifically tied to the form, like the acceptance and
|
|
134
|
+
confirmation validations, and the email delivery, have been moved out of the
|
|
135
|
+
model layer into the presenter layer. However, the single presenter object
|
|
136
|
+
will re-use the data-specific validations defined in the model.
|
|
137
|
+
|
|
138
|
+
### Documentation
|
|
139
|
+
|
|
140
|
+
See the inline [API
|
|
141
|
+
docs](http://rubydoc.info/github/avdgaag/conformista/master/frames) for more
|
|
142
|
+
information.
|
|
143
|
+
|
|
144
|
+
## Other
|
|
145
|
+
|
|
146
|
+
### Note on Patches/Pull Requests
|
|
147
|
+
|
|
148
|
+
1. Fork the project.
|
|
149
|
+
2. Make your feature addition or bug fix.
|
|
150
|
+
3. Add tests for it. This is important so I don't break it in a future version
|
|
151
|
+
unintentionally.
|
|
152
|
+
4. Commit, do not mess with rakefile, version, or history. (if you want to have
|
|
153
|
+
your own version, that is fine but bump version in a commit by itself I can
|
|
154
|
+
ignore when I pull)
|
|
155
|
+
5. Send me a pull request. Bonus points for topic branches.
|
|
156
|
+
|
|
157
|
+
### Issues
|
|
158
|
+
|
|
159
|
+
Please report any issues, defects or suggestions in the [Github issue
|
|
160
|
+
tracker](https://github.com/avdgaag/conformista/issues).
|
|
161
|
+
|
|
162
|
+
### What has changed?
|
|
163
|
+
|
|
164
|
+
See the [HISTORY](https://github.com/avdgaag/conformista/blob/master/HISTORY.md) file
|
|
165
|
+
for a detailed changelog.
|
|
166
|
+
|
|
167
|
+
### Credits
|
|
168
|
+
|
|
169
|
+
Created by: Arjan van der Gaag
|
|
170
|
+
URL: [http://arjanvandergaag.nl](http://arjanvandergaag.nl)
|
|
171
|
+
Project homepage: [http://avdgaag.github.com/conformista](http://avdgaag.github.com/conformista)
|
|
172
|
+
Date: september 2013
|
|
173
|
+
License: [MIT-license](https://github.com/avdgaag/conformista/blob/master/LICENSE) (same as Ruby)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
require 'bundler'
|
|
3
|
+
Bundler::GemHelper.install_tasks
|
|
4
|
+
Bundler.setup
|
|
5
|
+
|
|
6
|
+
desc 'Default: run specs.'
|
|
7
|
+
task :default => :spec
|
|
8
|
+
|
|
9
|
+
require 'rspec/core/rake_task'
|
|
10
|
+
desc 'Run specs'
|
|
11
|
+
RSpec::Core::RakeTask.new
|
|
12
|
+
|
|
13
|
+
require 'yard'
|
|
14
|
+
desc 'Generate API docs'
|
|
15
|
+
YARD::Rake::YardocTask.new
|
data/conformista.gemspec
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/conformista/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
# Metadata
|
|
6
|
+
s.name = 'conformista'
|
|
7
|
+
s.version = Conformista::VERSION
|
|
8
|
+
s.platform = Gem::Platform::RUBY
|
|
9
|
+
s.authors = ['Arjan van der Gaag']
|
|
10
|
+
s.email = %q{arjan@arjanvandergaag.nl}
|
|
11
|
+
s.description = %q{A library for creating form objects for Rails applications.}
|
|
12
|
+
s.homepage = %q{http://avdgaag.github.com/conformista}
|
|
13
|
+
s.summary = <<-EOS
|
|
14
|
+
Conformista is a library to make building presenters -- and form objects in
|
|
15
|
+
particular -- easier. It provides an ActiveModel-compliant base class that your
|
|
16
|
+
own form objects can inherit from, along with standard behaviour for creating,
|
|
17
|
+
loading, validating and persisting business objects (usually ActiveRecord
|
|
18
|
+
models).
|
|
19
|
+
EOS
|
|
20
|
+
|
|
21
|
+
# Files
|
|
22
|
+
s.files = `git ls-files`.split("
|
|
23
|
+
")
|
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("
|
|
25
|
+
")
|
|
26
|
+
s.executables = `git ls-files -- bin/*`.split("
|
|
27
|
+
").map{ |f| File.basename(f) }
|
|
28
|
+
s.require_paths = ["lib"]
|
|
29
|
+
|
|
30
|
+
# Rdoc
|
|
31
|
+
s.rdoc_options = ['--charset=UTF-8']
|
|
32
|
+
s.extra_rdoc_files = [
|
|
33
|
+
'LICENSE',
|
|
34
|
+
'README.md',
|
|
35
|
+
'HISTORY.md'
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# Dependencies
|
|
39
|
+
s.add_dependency 'activemodel'
|
|
40
|
+
s.add_development_dependency 'kramdown'
|
|
41
|
+
s.add_development_dependency 'yard'
|
|
42
|
+
s.add_development_dependency 'rspec'
|
|
43
|
+
s.add_development_dependency 'rake'
|
|
44
|
+
s.add_development_dependency 'activerecord'
|
|
45
|
+
s.add_development_dependency 'sqlite3'
|
|
46
|
+
end
|
data/lib/conformista.rb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module Conformista
|
|
2
|
+
# The FormObject is an ActiveModel-compliant object that knows how to present
|
|
3
|
+
# multiple Ruby objects (usually descendents of ActiveRecord::Base) to the
|
|
4
|
+
# view layer in an application. The form object is specifically designed to
|
|
5
|
+
# work with Rails `form_for` helpers.
|
|
6
|
+
#
|
|
7
|
+
# The form object's responsibility is not too complex:
|
|
8
|
+
#
|
|
9
|
+
# * Provide accessors to presented models.
|
|
10
|
+
# * Delegate selected attributes on the form object to the appropriate
|
|
11
|
+
# presented models.
|
|
12
|
+
# * Delegate model validation and persistence.
|
|
13
|
+
#
|
|
14
|
+
# We can make the behaviour a little more complex by defining validations
|
|
15
|
+
# specific to our form object. Validations that belong on presenters rather
|
|
16
|
+
# than data models include:
|
|
17
|
+
#
|
|
18
|
+
# * Matching password confirmation
|
|
19
|
+
# * Acceptance of terms and conditions
|
|
20
|
+
#
|
|
21
|
+
# In your `FormObject` subclass, you can override methods to customize the
|
|
22
|
+
# default behaviour. The default behaviour includes:
|
|
23
|
+
#
|
|
24
|
+
# * models are built using the `new` method
|
|
25
|
+
# * models are saved using the `save` method
|
|
26
|
+
# * model attributes are set using accessor methods
|
|
27
|
+
# * models are validated using the `valid?` method
|
|
28
|
+
#
|
|
29
|
+
# You can customise its behaviour by overriding methods. You can add new
|
|
30
|
+
# behaviour by using callbacks (`:validation`, `:save` and `:persist`).
|
|
31
|
+
#
|
|
32
|
+
# @example Present multiple models and override some default behaviour
|
|
33
|
+
# class SignupForm < Conformista::FormObject
|
|
34
|
+
# presents account: %i[name],
|
|
35
|
+
# user: %i[email password],
|
|
36
|
+
# profile: %i[twitter github bio]
|
|
37
|
+
#
|
|
38
|
+
# private
|
|
39
|
+
#
|
|
40
|
+
# def build_profile
|
|
41
|
+
# user.build_profile
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# def persist_user
|
|
45
|
+
# user.save!
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
class FormObject
|
|
49
|
+
extend ActiveModel::Callbacks
|
|
50
|
+
include ActiveModel::Model
|
|
51
|
+
include ActiveModel::Validations::Callbacks
|
|
52
|
+
|
|
53
|
+
define_model_callbacks :save, :persist
|
|
54
|
+
before_save :delegate_attributes
|
|
55
|
+
|
|
56
|
+
include Transactions
|
|
57
|
+
include Validations
|
|
58
|
+
extend Presenting
|
|
59
|
+
|
|
60
|
+
def initialize(params = {})
|
|
61
|
+
set_attributes(params)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Whether all presented models are persisted.
|
|
65
|
+
def persisted?
|
|
66
|
+
presented_models.all? do |model|
|
|
67
|
+
send(model.model_name.singular).persisted?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Persist all models, if they are all valid. This invokes
|
|
72
|
+
# the `:save` hook.
|
|
73
|
+
def save
|
|
74
|
+
run_callbacks :save do
|
|
75
|
+
persist_models
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Delegate the hash of attributes to the presented models
|
|
80
|
+
# and save the object.
|
|
81
|
+
#
|
|
82
|
+
# @see #save
|
|
83
|
+
# @param [Hash] params
|
|
84
|
+
def update_attributes(params)
|
|
85
|
+
set_attributes(params)
|
|
86
|
+
save
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def set_attributes(params)
|
|
92
|
+
params.each do |attr, value|
|
|
93
|
+
public_send :"#{attr}=", value
|
|
94
|
+
end if params
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def presented_models
|
|
98
|
+
[]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def delegate_attributes
|
|
102
|
+
each_model do |record, name|
|
|
103
|
+
send :"delegate_#{name}_attributes"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def persist_models
|
|
108
|
+
run_callbacks :persist do
|
|
109
|
+
presented_models.inject(true) do |all_saved, model|
|
|
110
|
+
record_saved = send :"persist_#{model.model_name.singular}"
|
|
111
|
+
all_saved &&= record_saved
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def each_model
|
|
117
|
+
presented_models.each do |presented_model|
|
|
118
|
+
name = presented_model.model_name.singular
|
|
119
|
+
yield send(name), name
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
module Conformista
|
|
2
|
+
# Presenting provides the macro to extend a form object with the methods to
|
|
3
|
+
# present models and to build, persist and validate them.
|
|
4
|
+
#
|
|
5
|
+
# Given a model `User`, you can override the following methods to customise
|
|
6
|
+
# the default behaviour:
|
|
7
|
+
#
|
|
8
|
+
# * `build_user`: should return a new instance of the model
|
|
9
|
+
# * `user`: should return the currently presented model instance
|
|
10
|
+
# * `user=`: set the currently presented model instance
|
|
11
|
+
# * `persist_user`: persist the current model instance
|
|
12
|
+
# * `load_user_attributes`: get model attributes and store it in the form
|
|
13
|
+
# object
|
|
14
|
+
# * `delegate_user_attributes`: set model attributes from the form object
|
|
15
|
+
# attributes
|
|
16
|
+
# * `validate_user`: test if the model instance is valid
|
|
17
|
+
#
|
|
18
|
+
# @example Present a single model
|
|
19
|
+
# class SignupForm < Conformista::FormObject
|
|
20
|
+
# presents User, :email, :password
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Present multiple models
|
|
24
|
+
# class SignupForm < Conformista::FormObject
|
|
25
|
+
# presents User => %i[email password],
|
|
26
|
+
# Profile => %i[twitter github bio]
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @see FormObject
|
|
30
|
+
module Presenting
|
|
31
|
+
# Convenience method to use either `present_models` or `present_model`,
|
|
32
|
+
# based on the number of arguments passed in.
|
|
33
|
+
#
|
|
34
|
+
# @overload presents(model, *attributes)
|
|
35
|
+
# Present a single model and its attributes
|
|
36
|
+
# @param [Class] model
|
|
37
|
+
# @param [Symbol] method name
|
|
38
|
+
# @overload presents(models)
|
|
39
|
+
# Present multiple models using a Hash of classes and attributes
|
|
40
|
+
# @param [Hash] models as keys, array of attributes as value
|
|
41
|
+
def presents(*args)
|
|
42
|
+
if args.size == 1
|
|
43
|
+
present_models *args
|
|
44
|
+
else
|
|
45
|
+
present_model *args
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Present multiple models using a Hash of classes and attributes
|
|
50
|
+
#
|
|
51
|
+
# @param [Hash] options models as keys, array of attributes as value
|
|
52
|
+
def present_models(options = {})
|
|
53
|
+
options.each do |model, attributes|
|
|
54
|
+
present_model model, *attributes
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Present a single model and its attributes
|
|
59
|
+
#
|
|
60
|
+
# @param [Class] model the class of object to present
|
|
61
|
+
# @param [Symbol] attributes one or more attribute names
|
|
62
|
+
def present_model(model, *attributes)
|
|
63
|
+
model_name = model.model_name.singular
|
|
64
|
+
ivar = :"@#{model_name}".to_sym
|
|
65
|
+
|
|
66
|
+
mod = Module.new do
|
|
67
|
+
attr_accessor *attributes
|
|
68
|
+
|
|
69
|
+
define_method :presented_models do
|
|
70
|
+
super().tap do |orig|
|
|
71
|
+
orig << model unless orig.include? model
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
define_method model_name do
|
|
76
|
+
if instance_variable_get(ivar).nil?
|
|
77
|
+
instance_variable_set ivar, send(:"build_#{model_name}")
|
|
78
|
+
else
|
|
79
|
+
instance_variable_get ivar
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
define_method :"build_#{model_name}" do
|
|
84
|
+
model.new
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
define_method :"#{model_name}=" do |obj|
|
|
88
|
+
instance_variable_set(ivar, obj).tap do |obj|
|
|
89
|
+
send :"load_#{model_name}_attributes"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
define_method :"load_#{model_name}_attributes" do
|
|
94
|
+
attributes.each do |attribute|
|
|
95
|
+
send("#{attribute}=", send(model_name).send("#{attribute}"))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
define_method :"delegate_#{model_name}_attributes" do
|
|
100
|
+
attributes.each do |attribute|
|
|
101
|
+
send(model_name).send("#{attribute}=", send(attribute))
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
define_method :"validate_#{model_name}" do
|
|
106
|
+
send(model_name).valid?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
define_method :"persist_#{model_name}" do
|
|
110
|
+
send(model_name).save
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
include mod
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Conformista
|
|
2
|
+
# Provides transactional functionality to form objects, wrapping persistence
|
|
3
|
+
# operations in a database transaction to ensure either all presented models
|
|
4
|
+
# are persisted, or none are.
|
|
5
|
+
module Transactions
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.around_persist :wrap_in_database_transaction
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def wrap_in_database_transaction
|
|
13
|
+
ActiveRecord::Base.transaction do
|
|
14
|
+
yield.tap do |all_saved|
|
|
15
|
+
raise ActiveRecord::Rollback unless all_saved
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Conformista
|
|
2
|
+
# Adds validation behaviour to ActiveModel-compliant objects. This means that
|
|
3
|
+
# objects this module is mixed into will only run `save` if it responds
|
|
4
|
+
# positively to `valid?`.
|
|
5
|
+
#
|
|
6
|
+
# This model makes sure that all attributes are properly delegated before any
|
|
7
|
+
# validations run, and copies any presented model errors to the host object.
|
|
8
|
+
#
|
|
9
|
+
# You can override how specific models are validated by overriding the
|
|
10
|
+
# `validate_MODEL_NAME` method.
|
|
11
|
+
module Validations
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.before_validation :delegate_attributes
|
|
14
|
+
base.before_validation :validate_models
|
|
15
|
+
base.before_validation :copy_validation_errors
|
|
16
|
+
base.before_save :valid?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def validate_models
|
|
22
|
+
each_model do |record, name|
|
|
23
|
+
send :"validate_#{name}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def copy_validation_errors
|
|
28
|
+
each_model do |record, name|
|
|
29
|
+
record.errors.each do |key, value|
|
|
30
|
+
errors.add key, value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module Conformista
|
|
2
|
+
describe FormObject do
|
|
3
|
+
it_should_behave_like 'ActiveModel'
|
|
4
|
+
|
|
5
|
+
it { should respond_to(:save) }
|
|
6
|
+
it { should have(0).presented_models }
|
|
7
|
+
|
|
8
|
+
context 'when presenting a single model' do
|
|
9
|
+
let(:example_form_object_class) do
|
|
10
|
+
Class.new(described_class) do
|
|
11
|
+
presents Post, :title
|
|
12
|
+
validates :title, length: { minimum: 2, allow_blank: true }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
stub_const('Example', example_form_object_class)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
subject { example_form_object_class.new }
|
|
21
|
+
|
|
22
|
+
it { should have(1).presented_models }
|
|
23
|
+
its(:presented_models) { should include(Post) }
|
|
24
|
+
its(:post) { should be_instance_of(Post) }
|
|
25
|
+
it { should_not be_persisted }
|
|
26
|
+
|
|
27
|
+
it 'is persisted after saving' do
|
|
28
|
+
subject.title = 'foo'
|
|
29
|
+
subject.save
|
|
30
|
+
expect(subject).to be_persisted
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'can be created using an existing record' do
|
|
34
|
+
post = Post.new
|
|
35
|
+
example = example_form_object_class.new(post: post)
|
|
36
|
+
expect(example.post).to be(post)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'can customize the build strategy' do
|
|
40
|
+
example = example_form_object_class.new
|
|
41
|
+
def example.build_post; 'foo'; end
|
|
42
|
+
expect(example.post).to eql('foo')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'delegates listed attributes to the model before validating' do
|
|
46
|
+
subject.title = 'foo'
|
|
47
|
+
subject.valid?
|
|
48
|
+
expect(subject.post.title).to eql('foo')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'reads attributes from models when initializing' do
|
|
52
|
+
post = Post.new
|
|
53
|
+
post.title = 'foo'
|
|
54
|
+
example = example_form_object_class.new(post: post)
|
|
55
|
+
expect(example.title).to eql('foo')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'validates the model when validating the form object' do
|
|
59
|
+
expect(subject.post).to receive(:valid?)
|
|
60
|
+
subject.valid?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'copies validation errors to the form object' do
|
|
64
|
+
subject.valid?
|
|
65
|
+
expect(subject).to have(1).errors
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'saves the post when valid' do
|
|
69
|
+
subject.title = 'foo'
|
|
70
|
+
expect(subject).to be_valid
|
|
71
|
+
expect { subject.save }.to change { Post.count }.by(1)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'does not save the post when invalid' do
|
|
75
|
+
expect(subject.post).not_to receive(:save)
|
|
76
|
+
expect { subject.save }.not_to change { Post.count }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'sets and saves attributes with update_attributes' do
|
|
80
|
+
subject.title = 'foo'
|
|
81
|
+
subject.save
|
|
82
|
+
expect {
|
|
83
|
+
subject.update_attributes title: 'bla'
|
|
84
|
+
}.to change { subject.post.title }.from('foo').to('bla')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'uses own validations to generate errors' do
|
|
88
|
+
subject.title = 'x'
|
|
89
|
+
expect(subject).not_to be_valid
|
|
90
|
+
expect(subject.errors[:title]).to include('is too short (minimum is 2 characters)')
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context 'when presenting two models' do
|
|
95
|
+
let(:example_form_object_class) do
|
|
96
|
+
Class.new(described_class) do
|
|
97
|
+
presents Post, :title
|
|
98
|
+
presents Comment, :body
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
before { stub_const('Example', example_form_object_class) }
|
|
103
|
+
subject { example_form_object_class.new }
|
|
104
|
+
it { should have(2).presented_models }
|
|
105
|
+
its(:presented_models) { should include(Comment) }
|
|
106
|
+
its(:comment) { should be_instance_of(Comment) }
|
|
107
|
+
it { should_not be_persisted }
|
|
108
|
+
|
|
109
|
+
it 'is persisted after saving' do
|
|
110
|
+
subject.title = 'foo'
|
|
111
|
+
subject.body = 'bar'
|
|
112
|
+
subject.save
|
|
113
|
+
expect(subject).to be_persisted
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'does not save one model when the other fails' do
|
|
117
|
+
subject.title = 'foo'
|
|
118
|
+
subject.body = 'bar'
|
|
119
|
+
expect(subject.post).to receive(:save).and_return(false)
|
|
120
|
+
expect {
|
|
121
|
+
subject.save
|
|
122
|
+
}.not_to change { Comment.count }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
shared_examples_for 'ActiveModel' do
|
|
2
|
+
it '#to_key' do
|
|
3
|
+
expect(model).to respond_to(:to_key)
|
|
4
|
+
def model.persisted?() false end
|
|
5
|
+
expect(model.to_key).to be_nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it '#to_param' do
|
|
9
|
+
expect(model).to respond_to(:to_param)
|
|
10
|
+
def model.to_key() [1] end
|
|
11
|
+
def model.persisted?() false end
|
|
12
|
+
expect(model.to_param).to be_nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it '#to_partial_path' do
|
|
16
|
+
expect(model).to respond_to(:to_partial_path)
|
|
17
|
+
expect(model.to_partial_path).to be_kind_of(String)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it '#persisted?' do
|
|
21
|
+
expect(model).to respond_to(:persisted?)
|
|
22
|
+
expect { model.persisted? == true || model.persisted? == false }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'model naming' do
|
|
26
|
+
expect(model.class).to respond_to(:model_name)
|
|
27
|
+
model_name = model.class.model_name
|
|
28
|
+
expect(model_name).to respond_to(:to_str)
|
|
29
|
+
expect(model_name.human).to respond_to(:to_str)
|
|
30
|
+
expect(model_name.singular).to respond_to(:to_str)
|
|
31
|
+
expect(model_name.plural).to respond_to(:to_str)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'errors aref' do
|
|
35
|
+
expect(model).to respond_to(:errors)
|
|
36
|
+
expect(model.errors[:hello]).to be_a(Array)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def model
|
|
42
|
+
subject
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
|
|
3
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
|
4
|
+
ActiveRecord::Migration.verbose = false
|
|
5
|
+
|
|
6
|
+
ActiveRecord::Migration.create_table :posts do |t|
|
|
7
|
+
t.string :title
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
ActiveRecord::Migration.create_table :comments do |t|
|
|
12
|
+
t.text :body
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Post < ActiveRecord::Base
|
|
17
|
+
validates :title, presence: true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Comment < ActiveRecord::Base
|
|
21
|
+
validates :body, presence: true
|
|
22
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: conformista
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Arjan van der Gaag
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2013-10-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activemodel
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '>='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '>='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: kramdown
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - '>='
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - '>='
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: yard
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - '>='
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - '>='
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - '>='
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rake
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - '>='
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - '>='
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: activerecord
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - '>='
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - '>='
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: sqlite3
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - '>='
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - '>='
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
description: A library for creating form objects for Rails applications.
|
|
112
|
+
email: arjan@arjanvandergaag.nl
|
|
113
|
+
executables: []
|
|
114
|
+
extensions: []
|
|
115
|
+
extra_rdoc_files:
|
|
116
|
+
- LICENSE
|
|
117
|
+
- README.md
|
|
118
|
+
- HISTORY.md
|
|
119
|
+
files:
|
|
120
|
+
- .gitignore
|
|
121
|
+
- .rspec
|
|
122
|
+
- .travis.yml
|
|
123
|
+
- .yardopts
|
|
124
|
+
- Gemfile
|
|
125
|
+
- Gemfile.lock
|
|
126
|
+
- HISTORY.md
|
|
127
|
+
- LICENSE
|
|
128
|
+
- README.md
|
|
129
|
+
- Rakefile
|
|
130
|
+
- conformista.gemspec
|
|
131
|
+
- lib/conformista.rb
|
|
132
|
+
- lib/conformista/form_object.rb
|
|
133
|
+
- lib/conformista/presenting.rb
|
|
134
|
+
- lib/conformista/transactions.rb
|
|
135
|
+
- lib/conformista/validations.rb
|
|
136
|
+
- lib/conformista/version.rb
|
|
137
|
+
- spec/conformista/form_object_spec.rb
|
|
138
|
+
- spec/spec_helper.rb
|
|
139
|
+
- spec/support/active_model_examples.rb
|
|
140
|
+
- spec/support/post.rb
|
|
141
|
+
homepage: http://avdgaag.github.com/conformista
|
|
142
|
+
licenses: []
|
|
143
|
+
metadata: {}
|
|
144
|
+
post_install_message:
|
|
145
|
+
rdoc_options:
|
|
146
|
+
- --charset=UTF-8
|
|
147
|
+
require_paths:
|
|
148
|
+
- lib
|
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
150
|
+
requirements:
|
|
151
|
+
- - '>='
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: '0'
|
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - '>='
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '0'
|
|
159
|
+
requirements: []
|
|
160
|
+
rubyforge_project:
|
|
161
|
+
rubygems_version: 2.1.5
|
|
162
|
+
signing_key:
|
|
163
|
+
specification_version: 4
|
|
164
|
+
summary: Conformista is a library to make building presenters -- and form objects
|
|
165
|
+
in particular -- easier. It provides an ActiveModel-compliant base class that your
|
|
166
|
+
own form objects can inherit from, along with standard behaviour for creating, loading,
|
|
167
|
+
validating and persisting business objects (usually ActiveRecord models).
|
|
168
|
+
test_files:
|
|
169
|
+
- spec/conformista/form_object_spec.rb
|
|
170
|
+
- spec/spec_helper.rb
|
|
171
|
+
- spec/support/active_model_examples.rb
|
|
172
|
+
- spec/support/post.rb
|
|
173
|
+
has_rdoc:
|