reform 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +0 -1
- data/CHANGES.md +13 -0
- data/Gemfile +0 -2
- data/README.md +276 -94
- data/Rakefile +6 -0
- data/TODO.md +10 -1
- data/database.sqlite3 +0 -0
- data/lib/reform/active_record.rb +2 -0
- data/lib/reform/composition.rb +55 -0
- data/lib/reform/form/active_model.rb +60 -15
- data/lib/reform/form/active_record.rb +3 -3
- data/lib/reform/form/composition.rb +69 -0
- data/lib/reform/form.rb +183 -80
- data/lib/reform/rails.rb +8 -1
- data/lib/reform/representer.rb +38 -0
- data/lib/reform/version.rb +1 -1
- data/lib/reform.rb +5 -2
- data/reform.gemspec +3 -2
- data/test/active_model_test.rb +83 -9
- data/test/coercion_test.rb +26 -0
- data/test/composition_test.rb +57 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/albums_controller.rb +18 -0
- data/test/dummy/app/controllers/application_controller.rb +4 -0
- data/test/dummy/app/controllers/musician_controller.rb +5 -0
- data/test/dummy/app/forms/album_form.rb +18 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/album.rb +4 -0
- data/test/dummy/app/models/song.rb +3 -0
- data/test/dummy/app/views/albums/new.html.erb +28 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +20 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +16 -0
- data/test/dummy/config/environments/production.rb +46 -0
- data/test/dummy/config/environments/test.rb +33 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/production.log +0 -0
- data/test/dummy/log/server.log +0 -0
- data/test/errors_test.rb +95 -0
- data/test/form_composition_test.rb +60 -0
- data/test/nested_form_test.rb +129 -0
- data/test/rails/integration_test.rb +54 -0
- data/test/reform_test.rb +80 -114
- data/test/test_helper.rb +14 -1
- metadata +86 -11
- data/lib/reform/form/dsl.rb +0 -38
- data/test/dsl_test.rb +0 -43
data/test/active_model_test.rb
CHANGED
@@ -1,12 +1,84 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class
|
3
|
+
class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
|
4
|
+
class SongForm < Reform::Form
|
5
|
+
include Reform::Form::ActiveModel
|
6
|
+
|
7
|
+
property :name
|
8
|
+
end
|
9
|
+
|
10
|
+
let (:artist) { Artist.create(:name => "Frank Zappa") }
|
11
|
+
let (:form) { SongForm.new(artist) }
|
12
|
+
|
13
|
+
it { form.persisted?.must_equal true }
|
14
|
+
it { form.to_key.must_equal [artist.id] }
|
15
|
+
it { form.to_param.must_equal "#{artist.id}" }
|
16
|
+
it { form.to_model.must_equal form }
|
17
|
+
it { form.id.must_equal artist.id }
|
18
|
+
|
19
|
+
describe "::model_name" do
|
20
|
+
it { form.class.model_name.must_be_kind_of ActiveModel::Name }
|
21
|
+
it { form.class.model_name.to_s.must_equal "NewActiveModelTest::Song" }
|
22
|
+
|
23
|
+
let (:class_with_model) {
|
24
|
+
Class.new(Reform::Form) do
|
25
|
+
include Reform::Form::ActiveModel
|
26
|
+
|
27
|
+
model :album
|
28
|
+
end
|
29
|
+
}
|
30
|
+
|
31
|
+
it { class_with_model.model_name.must_be_kind_of ActiveModel::Name }
|
32
|
+
it { class_with_model.model_name.to_s.must_equal "Album" }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class FormBuilderCompatTest < MiniTest::Spec
|
37
|
+
let (:form_class) {
|
38
|
+
Class.new(Reform::Form) do
|
39
|
+
include Reform::Form::ActiveModel::FormBuilderMethods
|
40
|
+
|
41
|
+
property :artist do
|
42
|
+
property :name
|
43
|
+
validates :name, :presence => true
|
44
|
+
end
|
45
|
+
|
46
|
+
collection :songs do
|
47
|
+
property :title
|
48
|
+
validates :title, :presence => true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
}
|
52
|
+
let (:form) { form_class.new(OpenStruct.new(:artist => Artist.new, :songs => [OpenStruct.new])) }
|
53
|
+
# TODO: test when keys are missing!
|
54
|
+
|
55
|
+
it "respects _attributes params hash" do
|
56
|
+
form.validate("artist_attributes" => {"name" => "Blink 182"},
|
57
|
+
"songs_attributes" => {"0" => {"title" => "Damnit"}})
|
58
|
+
|
59
|
+
form.artist.name.must_equal "Blink 182"
|
60
|
+
form.songs.first.title.must_equal "Damnit"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "defines _attributes= setter so Rails' FB works properly" do
|
64
|
+
form.must_respond_to("artist_attributes=")
|
65
|
+
form.must_respond_to("songs_attributes=")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns flat errors hash" do
|
69
|
+
form.validate("artist_attributes" => {"name" => ""},
|
70
|
+
"songs_attributes" => {"0" => {"title" => ""}})
|
71
|
+
form.errors.messages.must_equal(:"artist.name" => ["can't be blank"], :"songs.title" => ["can't be blank"])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class ActiveModelWithCompositionTest < MiniTest::Spec
|
4
76
|
class HitForm < Reform::Form
|
5
|
-
include
|
77
|
+
include Composition
|
6
78
|
include Reform::Form::ActiveModel
|
7
79
|
|
8
|
-
property :title,
|
9
|
-
properties [:name, :genre],
|
80
|
+
property :title, :on => :song
|
81
|
+
properties [:name, :genre], :on => :artist # we need to check both ::property and ::properties here!
|
10
82
|
|
11
83
|
model :hit, :on => :song
|
12
84
|
end
|
@@ -21,11 +93,11 @@ class ActiveModelTest < MiniTest::Spec
|
|
21
93
|
end
|
22
94
|
|
23
95
|
it "doesn't delegate when :on missing" do
|
24
|
-
class
|
25
|
-
include
|
96
|
+
class SongOnlyForm < Reform::Form
|
97
|
+
include Composition
|
26
98
|
include Reform::Form::ActiveModel
|
27
99
|
|
28
|
-
property
|
100
|
+
property :title, :on => :song
|
29
101
|
|
30
102
|
model :song
|
31
103
|
end.new(:song => rio, :artist => duran).song.must_equal rio
|
@@ -34,6 +106,7 @@ class ActiveModelTest < MiniTest::Spec
|
|
34
106
|
|
35
107
|
|
36
108
|
it "creates composition readers" do
|
109
|
+
skip "we don't want those anymore since they don't represent the form internal state!"
|
37
110
|
form.song.must_equal rio
|
38
111
|
form.artist.must_equal duran
|
39
112
|
end
|
@@ -55,12 +128,13 @@ class ActiveModelTest < MiniTest::Spec
|
|
55
128
|
end
|
56
129
|
|
57
130
|
it "provides #to_model" do
|
58
|
-
HitForm.new(:song => OpenStruct.new
|
131
|
+
form = HitForm.new(:song => OpenStruct.new, :artist => OpenStruct.new)
|
132
|
+
form.to_model.must_equal form
|
59
133
|
end
|
60
134
|
|
61
135
|
it "works with any order of ::model and ::property" do
|
62
136
|
class AnotherForm < Reform::Form
|
63
|
-
include
|
137
|
+
include Composition
|
64
138
|
include Reform::Form::ActiveModel
|
65
139
|
|
66
140
|
model :song, :on => :song
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "reform/form/coercion"
|
3
|
+
|
4
|
+
class CoercionTest < MiniTest::Spec
|
5
|
+
it "allows coercion" do
|
6
|
+
form = Class.new(Reform::Form) do
|
7
|
+
include Reform::Form::Coercion
|
8
|
+
|
9
|
+
property :written_at, :type => DateTime
|
10
|
+
end.new(OpenStruct.new(:written_at => "31/03/1981"))
|
11
|
+
|
12
|
+
form.written_at.must_be_kind_of DateTime
|
13
|
+
form.written_at.must_equal DateTime.parse("Tue, 31 Mar 1981 00:00:00 +0000")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "allows coercion in validate" do
|
17
|
+
form = Class.new(Reform::Form) do
|
18
|
+
include Reform::Form::Coercion
|
19
|
+
|
20
|
+
property :id, :type => Integer
|
21
|
+
end.new(OpenStruct.new())
|
22
|
+
|
23
|
+
form.validate("id" => "1")
|
24
|
+
form.to_hash.must_equal("id" => 1)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class CompositionTest < ReformSpec
|
2
|
+
class SongAndArtist < Reform::Composition
|
3
|
+
map({:artist => [:name], :song => [:title]}) #SongAndArtistMap.representable_attrs
|
4
|
+
end
|
5
|
+
|
6
|
+
let (:comp) { SongAndArtist.new(:artist => @artist=OpenStruct.new, :song => rio) }
|
7
|
+
|
8
|
+
it "delegates to models as defined" do
|
9
|
+
comp.name.must_equal nil
|
10
|
+
comp.title.must_equal "Rio"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "raises when non-mapped property" do
|
14
|
+
assert_raises NoMethodError do
|
15
|
+
comp.raise_an_exception
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "creates readers to models" do
|
20
|
+
comp.song.object_id.must_equal rio.object_id
|
21
|
+
comp.artist.object_id.must_equal @artist.object_id
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "::map_from" do
|
25
|
+
it "creates the same mapping" do
|
26
|
+
comp =
|
27
|
+
Class.new(Reform::Composition) do
|
28
|
+
map_from(
|
29
|
+
Class.new(Reform::Representer) do
|
30
|
+
property :name, :on => :artist
|
31
|
+
property :title, :on => :song
|
32
|
+
end
|
33
|
+
)
|
34
|
+
end.
|
35
|
+
new(:artist => duran, :song => rio)
|
36
|
+
|
37
|
+
comp.name.must_equal "Duran Duran"
|
38
|
+
comp.title.must_equal "Rio"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#nested_hash_for" do
|
43
|
+
it "returns nested hash" do
|
44
|
+
comp.nested_hash_for(:name => "Jimi Hendrix", :title => "Fire").must_equal({:artist=>{:name=>"Jimi Hendrix"}, :song=>{:title=>"Fire"}})
|
45
|
+
end
|
46
|
+
|
47
|
+
it "works with strings" do
|
48
|
+
comp.nested_hash_for("name" => "Jimi Hendrix", "title" => "Fire").must_equal({:artist=>{:name=>"Jimi Hendrix"}, :song=>{:title=>"Fire"}})
|
49
|
+
end
|
50
|
+
|
51
|
+
it "works with strings in map" do
|
52
|
+
Class.new(Reform::Composition) do
|
53
|
+
map(:artist => ["name"])
|
54
|
+
end.new([nil]).nested_hash_for(:name => "Jimi Hendrix").must_equal({:artist=>{:name=>"Jimi Hendrix"}})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/test/dummy/Rakefile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
|
+
|
4
|
+
require File.expand_path('../config/application', __FILE__)
|
5
|
+
require 'rake'
|
6
|
+
|
7
|
+
Rails::Application.load_tasks
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class AlbumsController < ActionController::Base
|
2
|
+
def new
|
3
|
+
album = Album.new(:songs => [Song.new, Song.new])
|
4
|
+
@form = AlbumForm.new(album)
|
5
|
+
end
|
6
|
+
|
7
|
+
def create
|
8
|
+
album = Album.new(songs: [Song.new, Song.new])
|
9
|
+
@form = AlbumForm.new(album)
|
10
|
+
|
11
|
+
if @form.validate(params["album"])
|
12
|
+
@form.save
|
13
|
+
redirect_to album_path(album)
|
14
|
+
else
|
15
|
+
render :new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class AlbumForm < Reform::Form # FIXME: sub forms don't inherit FBM.
|
2
|
+
|
3
|
+
model :album
|
4
|
+
|
5
|
+
property :title
|
6
|
+
|
7
|
+
collection :songs do
|
8
|
+
property :title
|
9
|
+
validates :title, presence: true
|
10
|
+
end
|
11
|
+
|
12
|
+
validates :title, presence: true
|
13
|
+
|
14
|
+
def save
|
15
|
+
super
|
16
|
+
model.save
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
New Album
|
2
|
+
|
3
|
+
<%= form_for @form do |f| %>
|
4
|
+
<% if @form.errors.any? %>
|
5
|
+
<div>
|
6
|
+
<h3><%= pluralize(@form.errors.count, "error") %> prohibited this form from being saved:</h3>
|
7
|
+
|
8
|
+
<ul>
|
9
|
+
<% @form.errors.full_messages.each do |msg| %>
|
10
|
+
<li><%= msg %></li>
|
11
|
+
<% end %>
|
12
|
+
</ul>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<%= f.label :title, "Album Title" %>
|
17
|
+
<%= f.text_field :title %>
|
18
|
+
|
19
|
+
<%= f.fields_for :songs do |song| %>
|
20
|
+
<div>
|
21
|
+
<%= song.label :title %>
|
22
|
+
<%= song.text_field :title %>
|
23
|
+
</div>
|
24
|
+
<% end %>
|
25
|
+
|
26
|
+
<%= f.submit %>
|
27
|
+
<% end %>
|
28
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../boot', __FILE__)
|
2
|
+
|
3
|
+
require "active_model/railtie"
|
4
|
+
require "action_controller/railtie"
|
5
|
+
require "action_view/railtie"
|
6
|
+
|
7
|
+
Bundler.require
|
8
|
+
|
9
|
+
module Dummy
|
10
|
+
class Application < Rails::Application
|
11
|
+
config.encoding = "utf-8"
|
12
|
+
|
13
|
+
# Configure sensitive parameters which will be filtered from the log file.
|
14
|
+
config.filter_parameters += [:password]
|
15
|
+
|
16
|
+
config.cache_store = :memory_store
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require "reform/rails"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# SQLite version 3.x
|
2
|
+
# gem install sqlite3-ruby (not necessary on OS X Leopard)
|
3
|
+
development:
|
4
|
+
adapter: sqlite3
|
5
|
+
database: db/development.sqlite3
|
6
|
+
pool: 5
|
7
|
+
timeout: 5000
|
8
|
+
|
9
|
+
# Warning: The database defined as "test" will be erased and
|
10
|
+
# re-generated from your development database when you run "rake".
|
11
|
+
# Do not set this db to the same as development or production.
|
12
|
+
test:
|
13
|
+
adapter: sqlite3
|
14
|
+
database: db/test.sqlite3
|
15
|
+
pool: 5
|
16
|
+
timeout: 5000
|
17
|
+
|
18
|
+
production:
|
19
|
+
adapter: sqlite3
|
20
|
+
database: db/production.sqlite3
|
21
|
+
pool: 5
|
22
|
+
timeout: 5000
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Dummy::Application.configure do
|
2
|
+
# Settings specified here will take precedence over those in config/environment.rb
|
3
|
+
|
4
|
+
# In the development environment your application's code is reloaded on
|
5
|
+
# every request. This slows down response time but is perfect for development
|
6
|
+
# since you don't have to restart the webserver when you make code changes.
|
7
|
+
config.cache_classes = false
|
8
|
+
|
9
|
+
# Log error messages when you accidentally call methods on nil.
|
10
|
+
config.whiny_nils = true
|
11
|
+
|
12
|
+
# Show full error reports and disable caching
|
13
|
+
config.consider_all_requests_local = true
|
14
|
+
config.action_controller.perform_caching = false
|
15
|
+
config.secret_key_base = 123
|
16
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Dummy::Application.configure do
|
2
|
+
# Settings specified here will take precedence over those in config/environment.rb
|
3
|
+
|
4
|
+
# The production environment is meant for finished, "live" apps.
|
5
|
+
# Code is not reloaded between requests
|
6
|
+
config.cache_classes = true
|
7
|
+
|
8
|
+
# Full error reports are disabled and caching is turned on
|
9
|
+
config.consider_all_requests_local = false
|
10
|
+
config.action_controller.perform_caching = true
|
11
|
+
|
12
|
+
# Specifies the header that your server uses for sending files
|
13
|
+
config.action_dispatch.x_sendfile_header = "X-Sendfile"
|
14
|
+
|
15
|
+
# For nginx:
|
16
|
+
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
|
17
|
+
|
18
|
+
# If you have no front-end server that supports something like X-Sendfile,
|
19
|
+
# just comment this out and Rails will serve the files
|
20
|
+
|
21
|
+
# See everything in the log (default is :info)
|
22
|
+
# config.log_level = :debug
|
23
|
+
|
24
|
+
# Use a different logger for distributed setups
|
25
|
+
# config.logger = SyslogLogger.new
|
26
|
+
|
27
|
+
# Use a different cache store in production
|
28
|
+
# config.cache_store = :mem_cache_store
|
29
|
+
|
30
|
+
# Disable Rails's static asset server
|
31
|
+
# In production, Apache or nginx will already do this
|
32
|
+
config.serve_static_assets = false
|
33
|
+
|
34
|
+
# Enable serving of images, stylesheets, and javascripts from an asset server
|
35
|
+
# config.action_controller.asset_host = "http://assets.example.com"
|
36
|
+
|
37
|
+
# Disable delivery errors, bad email addresses will be ignored
|
38
|
+
# config.action_mailer.raise_delivery_errors = false
|
39
|
+
|
40
|
+
# Enable threaded mode
|
41
|
+
# config.threadsafe!
|
42
|
+
|
43
|
+
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
44
|
+
# the I18n.default_locale when a translation can not be found)
|
45
|
+
config.i18n.fallbacks = true
|
46
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Dummy::Application.configure do
|
2
|
+
# Settings specified here will take precedence over those in config/environment.rb
|
3
|
+
|
4
|
+
# The test environment is used exclusively to run your application's
|
5
|
+
# test suite. You never need to work with it otherwise. Remember that
|
6
|
+
# your test database is "scratch space" for the test suite and is wiped
|
7
|
+
# and recreated between test runs. Don't rely on the data there!
|
8
|
+
config.cache_classes = true
|
9
|
+
|
10
|
+
# Log error messages when you accidentally call methods on nil.
|
11
|
+
config.whiny_nils = true
|
12
|
+
|
13
|
+
# Show full error reports and disable caching
|
14
|
+
config.consider_all_requests_local = true
|
15
|
+
config.action_controller.perform_caching = false
|
16
|
+
|
17
|
+
# Raise exceptions instead of rendering exception templates
|
18
|
+
config.action_dispatch.show_exceptions = false
|
19
|
+
|
20
|
+
# Disable request forgery protection in test environment
|
21
|
+
config.action_controller.allow_forgery_protection = false
|
22
|
+
config.secret_key_base = "yo"
|
23
|
+
|
24
|
+
# Tell Action Mailer not to deliver emails to the real world.
|
25
|
+
# The :test delivery method accumulates sent emails in the
|
26
|
+
# ActionMailer::Base.deliveries array.
|
27
|
+
#config.action_mailer.delivery_method = :test
|
28
|
+
|
29
|
+
# Use SQL instead of Active Record's schema dumper when creating the test database.
|
30
|
+
# This is necessary if your schema can't be completely dumped by the schema dumper,
|
31
|
+
# like if you have constraints or database-specific column types
|
32
|
+
# config.active_record.schema_format = :sql
|
33
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
data/test/errors_test.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ErrorsTest < MiniTest::Spec
|
4
|
+
class AlbumForm < Reform::Form
|
5
|
+
property :title
|
6
|
+
|
7
|
+
property :hit do
|
8
|
+
property :title
|
9
|
+
validates :title, :presence => true
|
10
|
+
end
|
11
|
+
|
12
|
+
collection :songs do
|
13
|
+
property :title
|
14
|
+
validates :title, :presence => true
|
15
|
+
end
|
16
|
+
|
17
|
+
validates :title, :presence => true
|
18
|
+
end
|
19
|
+
|
20
|
+
let (:album) do
|
21
|
+
OpenStruct.new(
|
22
|
+
:title => "Blackhawks Over Los Angeles",
|
23
|
+
:hit => song,
|
24
|
+
:songs => songs # TODO: document this requirement
|
25
|
+
)
|
26
|
+
end
|
27
|
+
let (:song) { OpenStruct.new(:title => "Downtown") }
|
28
|
+
let (:songs) { [song=OpenStruct.new(:title => "Calling"), song] }
|
29
|
+
let (:form) { AlbumForm.new(album) }
|
30
|
+
|
31
|
+
|
32
|
+
describe "incorrect #validate" do
|
33
|
+
before { form.validate(
|
34
|
+
"hit" =>{"title" => ""},
|
35
|
+
"title" => "",
|
36
|
+
"songs" => [{"title" => ""}, {"title" => ""}]) } # FIXME: what happens if item is missing?
|
37
|
+
|
38
|
+
it do
|
39
|
+
form.errors.messages.must_equal({
|
40
|
+
:title => ["can't be blank"],
|
41
|
+
:"hit.title"=>["can't be blank"],
|
42
|
+
:"songs.title"=>["can't be blank"]})
|
43
|
+
end
|
44
|
+
|
45
|
+
it do
|
46
|
+
#form.errors.must_equal({:title => ["can't be blank"]})
|
47
|
+
# TODO: this should only contain local errors?
|
48
|
+
end
|
49
|
+
|
50
|
+
# nested forms keep their own Errors:
|
51
|
+
it { form.hit.errors.messages.must_equal({:title=>["can't be blank"]}) }
|
52
|
+
it { form.songs[0].errors.messages.must_equal({:title=>["can't be blank"]}) }
|
53
|
+
|
54
|
+
it do
|
55
|
+
form.errors.messages.must_equal({
|
56
|
+
:title => ["can't be blank"],
|
57
|
+
:"hit.title" => ["can't be blank"],
|
58
|
+
:"songs.title"=> ["can't be blank"]})
|
59
|
+
end # TODO: add another invalid item.
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#validate with main form invalid" do
|
63
|
+
before { @result = form.validate("title"=>"") }
|
64
|
+
|
65
|
+
it { @result.must_equal false }
|
66
|
+
it { form.errors.messages.must_equal({:title=>["can't be blank"]}) }
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#validate with middle nested form invalid" do
|
70
|
+
before { @result = form.validate("hit"=>{"title" => ""}) }
|
71
|
+
|
72
|
+
it { @result.must_equal false }
|
73
|
+
it { form.errors.messages.must_equal({:"hit.title"=>["can't be blank"]}) }
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#validate with last nested form invalid" do
|
77
|
+
before { @result = form.validate("songs"=>[{"title" => ""}]) }
|
78
|
+
|
79
|
+
it { @result.must_equal false }
|
80
|
+
it { form.errors.messages.must_equal({:"songs.title"=>["can't be blank"]}) }
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "correct #validate" do
|
84
|
+
before { @result = form.validate(
|
85
|
+
"hit" => {"title" => "Sacrifice"},
|
86
|
+
"title" => "Second Heat",
|
87
|
+
"songs" => [{"title"=>"Heart Of A Lion"}]
|
88
|
+
) }
|
89
|
+
|
90
|
+
it { @result.must_equal true }
|
91
|
+
it { form.hit.title.must_equal "Sacrifice" }
|
92
|
+
it { form.title.must_equal "Second Heat" }
|
93
|
+
it { form.songs.first.title.must_equal "Heart Of A Lion" }
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FormCompositionTest < MiniTest::Spec
|
4
|
+
class SongForm < Reform::Form
|
5
|
+
include Composition
|
6
|
+
|
7
|
+
property :title, :on => :song
|
8
|
+
properties [:name, :genre], :on => :artist
|
9
|
+
|
10
|
+
validates :name, :title, :genre, :presence => true
|
11
|
+
end
|
12
|
+
|
13
|
+
let (:form) { SongForm.new(:song => song, :artist => artist) }
|
14
|
+
let (:song) { OpenStruct.new(:title => "Rio") }
|
15
|
+
let (:artist) { OpenStruct.new(:name => "Duran Duran") }
|
16
|
+
|
17
|
+
|
18
|
+
# delegation form -> composition works
|
19
|
+
it { form.title.must_equal "Rio" }
|
20
|
+
it { form.name.must_equal "Duran Duran" }
|
21
|
+
# delegation form -> composed models (e.g. when saving this can be handy)
|
22
|
+
it { form.song.must_equal song }
|
23
|
+
it { form.artist.must_equal artist }
|
24
|
+
|
25
|
+
|
26
|
+
it "creates Composition for you" do
|
27
|
+
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal false
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#save" do
|
31
|
+
it "provides data block argument" do
|
32
|
+
hash = {}
|
33
|
+
|
34
|
+
form.save do |data, map|
|
35
|
+
hash[:name] = data.name
|
36
|
+
hash[:title] = data.title
|
37
|
+
end
|
38
|
+
|
39
|
+
hash.must_equal({:name=>"Duran Duran", :title=>"Rio"})
|
40
|
+
end
|
41
|
+
|
42
|
+
it "provides nested symbolized hash as second block argument" do
|
43
|
+
hash = {}
|
44
|
+
|
45
|
+
form.save do |data, map|
|
46
|
+
hash = map
|
47
|
+
end
|
48
|
+
|
49
|
+
hash.must_equal({:song=>{:title=>"Rio"}, :artist=>{:name=>"Duran Duran"}})
|
50
|
+
end
|
51
|
+
|
52
|
+
it "pushes data to models when no block passed" do
|
53
|
+
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb")
|
54
|
+
form.save
|
55
|
+
|
56
|
+
artist.name.must_equal "Frenzal Rhomb"
|
57
|
+
song.title.must_equal "Greyhound"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|