forminate 0.1.0 → 0.2.0

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.
@@ -71,7 +71,7 @@ module Forminate
71
71
  # validation hash.
72
72
  def association_validators
73
73
  associations.reduce({}) do |assoc_validators, (name, object)|
74
- if should_validate_assoc?(name) && object.respond_to?(:_validators)
74
+ if validate_assoc?(name) && object.respond_to?(:_validators)
75
75
  object._validators.each do |attr, validators|
76
76
  new_validators = validators.reduce([]) do |new_validators, validator|
77
77
  new_validator = validator.dup
@@ -14,13 +14,13 @@ describe Forminate::ClientSideValidations do
14
14
  attribute :tax
15
15
 
16
16
  attributes_for :dummy_user
17
- attributes_for :dummy_book, :validate => false
18
- attributes_for :dummy_credit_card, :validate => :require_credit_card?
17
+ attributes_for :dummy_book, validate: false
18
+ attributes_for :dummy_credit_card, validate: :require_credit_card?
19
19
 
20
20
  validates_numericality_of :total
21
21
 
22
22
  def self.name
23
- "Cart"
23
+ 'Cart'
24
24
  end
25
25
 
26
26
  def calculate_total
@@ -33,11 +33,11 @@ describe Forminate::ClientSideValidations do
33
33
  end
34
34
  end
35
35
 
36
- describe "#client_side_validation_hash" do
37
- it "constructs a hash of validations and messages for use with the client_side_validations gem" do
36
+ describe '#client_side_validation_hash' do
37
+ it 'constructs a hash of validations and messages for use with the client_side_validations gem' do
38
38
  expected_hash = {
39
39
  total: {
40
- numericality: [{ messages: { numericality: "is not a number" } }]
40
+ numericality: [{ messages: { numericality: 'is not a number' } }]
41
41
  },
42
42
  dummy_user_email: {
43
43
  presence: [{ message: "can't be blank" }]
@@ -46,7 +46,7 @@ describe Forminate::ClientSideValidations do
46
46
  expect(model.client_side_validation_hash).to eq(expected_hash)
47
47
  expected_hash = {
48
48
  total: {
49
- numericality: [{ messages: { numericality: "is not a number" } }]
49
+ numericality: [{ messages: { numericality: 'is not a number' } }]
50
50
  },
51
51
  dummy_user_email: {
52
52
  presence: [{ message: "can't be blank" }]
@@ -55,13 +55,13 @@ describe Forminate::ClientSideValidations do
55
55
  presence: [{ message: "can't be blank" }],
56
56
  length: [{
57
57
  messages: {
58
- minimum: "is too short (minimum is 12 characters)",
59
- maximum: "is too long (maximum is 19 characters)"
58
+ minimum: 'is too short (minimum is 12 characters)',
59
+ maximum: 'is too long (maximum is 19 characters)'
60
60
  },
61
61
  minimum: 12,
62
62
  maximum: 19
63
63
  }]
64
- },
64
+ }
65
65
  }
66
66
  model.dummy_book_price = 12.95
67
67
  expect(model.client_side_validation_hash).to eq(expected_hash)
@@ -0,0 +1,250 @@
1
+ require 'spec_helper'
2
+
3
+ describe Forminate do
4
+ subject(:model) { model_class.new }
5
+
6
+ let :model_class do
7
+ Class.new do
8
+ include Forminate
9
+
10
+ attribute :total
11
+ attribute :tax
12
+
13
+ attributes_for :dummy_user
14
+ attributes_for :dummy_book, validate: false
15
+ attributes_for :dummy_credit_card, validate: :require_credit_card?
16
+
17
+ validates_numericality_of :total
18
+
19
+ def self.name
20
+ 'Cart'
21
+ end
22
+
23
+ def calculate_total
24
+ self.total = dummy_book.price || 0.0
25
+ end
26
+
27
+ def require_credit_card?
28
+ dummy_book.price && dummy_book.price > 0.0
29
+ end
30
+ end
31
+ end
32
+
33
+ describe '.attributes_for' do
34
+ it 'adds a reader method for each attribute of the associated model' do
35
+ expect(model.respond_to?(:dummy_user_first_name)).to be_true
36
+ end
37
+
38
+ it 'adds reader and writer methods for each attribute of the associated model' do
39
+ model.dummy_user_first_name = 'Mo'
40
+ expect(model.dummy_user_first_name).to eq('Mo')
41
+ end
42
+
43
+ it 'adds reader methods for each associated model' do
44
+ expect(model.dummy_user).to be_an_instance_of(DummyUser)
45
+ end
46
+
47
+ it 'adds the association to the list of association names' do
48
+ expect(model_class.association_names).to include(:dummy_user)
49
+ end
50
+
51
+ describe ':validate option' do
52
+ context 'true or false' do
53
+ it 'validates associated object based value given' do
54
+ model.calculate_total
55
+ expect(model.valid?).to be_false
56
+ model.dummy_user_email = 'bob@example.com'
57
+ expect(model.valid?).to be_true
58
+ end
59
+ end
60
+
61
+ context 'method name (as a symbol) that evaluates to true or false' do
62
+ it 'validates associated objects based on result of method call' do
63
+ model.calculate_total
64
+ model.dummy_user_email = 'bob@example.com'
65
+ expect(model.valid?).to be_true
66
+ model.dummy_book_price = 12.95
67
+ expect(model.valid?).to be_false
68
+ model.dummy_credit_card_number = 4242424242424242
69
+ expect(model.valid?).to be_true
70
+ end
71
+ end
72
+
73
+ context 'invalid value' do
74
+ let(:bad_model_class) do
75
+ Class.new do
76
+ include Forminate
77
+ attributes_for :dummy_book, validate: [:say, :what?]
78
+ end
79
+ end
80
+
81
+ it 'raises an NotImplemented error' do
82
+ expect { bad_model_class.new.valid? }
83
+ .to raise_error(NotImplementedError)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '.attribute_names' do
90
+ it 'includes the names of its own attributes and the attributes of associated models' do
91
+ expected_attributes = %w(
92
+ total
93
+ tax
94
+ dummy_user_id
95
+ dummy_user_first_name
96
+ dummy_user_last_name
97
+ dummy_user_email
98
+ dummy_book_title
99
+ dummy_book_price
100
+ dummy_credit_card_number
101
+ dummy_credit_card_expiration
102
+ dummy_credit_card_cvv
103
+ )
104
+ expect(model_class.attribute_names).to eq(expected_attributes)
105
+ end
106
+ end
107
+
108
+ describe '.association_names' do
109
+ it 'includes the names of associated models' do
110
+ expect(model_class.association_names)
111
+ .to eq([:dummy_user, :dummy_book, :dummy_credit_card])
112
+ end
113
+ end
114
+
115
+ describe '.association_validations' do
116
+ it 'includes the names and conditions of association validations' do
117
+ expect(model_class.association_validations)
118
+ .to eq(
119
+ dummy_user: true,
120
+ dummy_book: false,
121
+ dummy_credit_card: :require_credit_card?
122
+ )
123
+ end
124
+ end
125
+
126
+ describe '#initialize' do
127
+ it 'builds associated objects and creates reader methods' do
128
+ expect(model.dummy_user).to be_an_instance_of(DummyUser)
129
+ end
130
+
131
+ it 'creates writer methods for associated objects' do
132
+ new_dummy_user = DummyUser.new(first_name: 'Mo')
133
+ expect(model.dummy_user).to_not be(new_dummy_user)
134
+ model.dummy_user = new_dummy_user
135
+ expect(model.dummy_user).to be(new_dummy_user)
136
+ end
137
+
138
+ it 'sets association attributes based on an options hash' do
139
+ new_model = model_class.new(
140
+ dummy_user_first_name: 'Mo',
141
+ dummy_user_last_name: 'Lawson',
142
+ dummy_book_title: 'The Hobbit'
143
+ )
144
+ expect(new_model.dummy_user_first_name).to eq('Mo')
145
+ expect(new_model.dummy_user_last_name).to eq('Lawson')
146
+ end
147
+
148
+ context 'primary key of an associated AR model is present' do
149
+ it 'populates the matching model with values from the database' do
150
+ user = DummyUser.create(
151
+ first_name: 'Mo',
152
+ last_name: 'Lawson',
153
+ email: 'mo@example.com'
154
+ )
155
+ new_model = model_class.new(dummy_user_id: user.id)
156
+ expect(new_model.dummy_user_first_name).to eq('Mo')
157
+ end
158
+ end
159
+
160
+ it 'sets attributes based on an options hash' do
161
+ new_model = model_class.new(total: 21.49)
162
+ expect(new_model.total).to eq(21.49)
163
+ end
164
+ end
165
+
166
+ describe '#association_names' do
167
+ it 'delegates to self.association_names' do
168
+ expect(model.association_names).to eq(model_class.association_names)
169
+ end
170
+ end
171
+
172
+ describe '#associations' do
173
+ it 'returns a hash of association names and associated objects' do
174
+ expect(model.associations[:dummy_user]).to be_an_instance_of(DummyUser)
175
+ end
176
+ end
177
+
178
+ describe '#save' do
179
+ context 'object is valid' do
180
+ it 'saves associations and returns self' do
181
+ model.dummy_user_email = 'bob@example.com'
182
+ model.calculate_total
183
+ DummyUser.any_instance.should_receive(:save)
184
+ expect(model.save).to eq(model)
185
+ end
186
+ end
187
+
188
+ context 'object is not valid' do
189
+ it 'does not save associations and returns false' do
190
+ DummyUser.any_instance.should_not_receive(:save)
191
+ expect(model.save).to be_false
192
+ end
193
+ end
194
+ end
195
+
196
+ context 'setting an attribute using the attribute name' do
197
+ it 'reflects the change on the associated object' do
198
+ model.dummy_user_first_name = 'Mo'
199
+ expect(model.dummy_user.first_name).to eq('Mo')
200
+ end
201
+ end
202
+
203
+ context "setting an attribute using the association's attribute" do
204
+ it 'reflects the change on the attribute name' do
205
+ model.dummy_user.last_name = 'Lawson'
206
+ expect(model.dummy_user_last_name).to eq('Lawson')
207
+ end
208
+ end
209
+
210
+ describe '#method_missing' do
211
+ context 'associated object responds to method' do
212
+ it 'returns the value from the association' do
213
+ new_model = model_class.new(dummy_user_temporary_note: 'Only stopping by')
214
+ expect(new_model.dummy_user_temporary_note).to eq('Only stopping by')
215
+ end
216
+ end
217
+
218
+ context 'associated object does not respond to method' do
219
+ it 'raises a NoMethodError' do
220
+ expect { model.dummy_user_bogus_method }.to raise_error(NoMethodError)
221
+ end
222
+ end
223
+ end
224
+
225
+ describe '#respond_to_missing?' do
226
+ context 'associated object responds to method' do
227
+ it 'returns true' do
228
+ expect(model.respond_to?(:dummy_user_temporary_note)).to be_true
229
+ end
230
+ end
231
+
232
+ context 'associated object does not respond to method' do
233
+ it 'returns false' do
234
+ expect(model.respond_to?(:dummy_user_bogus_method)).to be_false
235
+ end
236
+ end
237
+ end
238
+
239
+ it 'delegates to attr_accessors of associated objects' do
240
+ model.dummy_user_temporary_note = 'Only stopping by'
241
+ expect(model.dummy_user.temporary_note).to eq('Only stopping by')
242
+ expect(model.dummy_user_temporary_note).to eq('Only stopping by')
243
+ end
244
+
245
+ it 'inherits the validations of its associated objects' do
246
+ model.valid?
247
+ expect(model.errors.full_messages)
248
+ .to eq(["Dummy user email can't be blank", 'Total is not a number'])
249
+ end
250
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,23 @@
1
+ require 'codeclimate-test-reporter'
2
+ CodeClimate::TestReporter.start
3
+
1
4
  require 'bundler/setup'
2
5
  require 'forminate'
3
6
 
7
+ require 'active_record'
8
+ ActiveRecord::Base.establish_connection(
9
+ adapter: 'sqlite3',
10
+ database: ':memory:'
11
+ )
12
+ ActiveRecord::Schema.define do
13
+ suppress_messages do
14
+ create_table :dummy_users do |t|
15
+ t.string :first_name
16
+ t.string :last_name
17
+ t.string :email
18
+ end
19
+ end
20
+ end
21
+
4
22
  # Requires supporting files in spec/support/
5
23
  Dir["#{File.dirname(__FILE__)}/support/*.rb"].each { |file| require file }
@@ -1,5 +1,3 @@
1
- require 'active_attr'
2
-
3
1
  class DummyBook
4
2
  include ActiveAttr::Model
5
3
 
@@ -1,5 +1,3 @@
1
- require 'active_attr'
2
-
3
1
  class DummyCreditCard
4
2
  include ActiveAttr::Model
5
3
 
@@ -1,17 +1,5 @@
1
- require 'active_attr'
2
-
3
- class DummyUser
4
- include ActiveAttr::Model
5
-
6
- attribute :first_name
7
- attribute :last_name
8
- attribute :email
9
-
10
- attr_accessor :full_name
11
-
1
+ class DummyUser < ActiveRecord::Base
12
2
  validates_presence_of :email
13
3
 
14
- def save
15
- # fake a persisted model
16
- end
4
+ attr_accessor :temporary_note
17
5
  end
metadata CHANGED
@@ -1,125 +1,157 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forminate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mo Lawson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-06 00:00:00.000000000 Z
11
+ date: 2014-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_attr
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 3.0.2
34
- - - <
34
+ - - "<"
35
35
  - !ruby/object:Gem::Version
36
36
  version: '4.1'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
- - - '>='
41
+ - - ">="
42
42
  - !ruby/object:Gem::Version
43
43
  version: 3.0.2
44
- - - <
44
+ - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '4.1'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: client_side_validations
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - ~>
51
+ - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '3.2'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - ~>
58
+ - - "~>"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '3.2'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: bundler
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ~>
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '1.3'
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - ~>
72
+ - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: '1.3'
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: rake
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - '>='
79
+ - - ">="
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0'
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - '>='
86
+ - - ">="
87
87
  - !ruby/object:Gem::Version
88
88
  version: '0'
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: rspec
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ~>
93
+ - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '2.11'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - ~>
100
+ - - "~>"
101
101
  - !ruby/object:Gem::Version
102
102
  version: '2.11'
103
+ - !ruby/object:Gem::Dependency
104
+ name: activerecord
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3.2'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.2'
117
+ - !ruby/object:Gem::Dependency
118
+ name: sqlite3
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
103
131
  description: Form objects for Rails applications
104
132
  email:
105
- - moklawson@gmail.com
133
+ - mo@molawson.com
106
134
  executables: []
107
135
  extensions: []
108
136
  extra_rdoc_files: []
109
137
  files:
110
- - .gitignore
111
- - .travis.yml
138
+ - ".gitignore"
139
+ - ".travis.yml"
112
140
  - Gemfile
113
141
  - LICENSE.txt
114
142
  - README.md
115
143
  - Rakefile
116
144
  - forminate.gemspec
117
145
  - lib/forminate.rb
146
+ - lib/forminate/association_builder.rb
147
+ - lib/forminate/association_definition.rb
118
148
  - lib/forminate/client_side_validations.rb
119
149
  - lib/forminate/version.rb
120
- - spec/lib/forminate/client_side_validations.rb
121
- - spec/lib/forminate/client_side_validations_spec.rb
122
- - spec/lib/forminate_spec.rb
150
+ - spec/forminate/association_builder_spec.rb
151
+ - spec/forminate/association_definition_spec.rb
152
+ - spec/forminate/client_side_validations.rb
153
+ - spec/forminate/client_side_validations_spec.rb
154
+ - spec/forminate_spec.rb
123
155
  - spec/spec_helper.rb
124
156
  - spec/support/dummy_book.rb
125
157
  - spec/support/dummy_credit_card.rb
@@ -134,24 +166,26 @@ require_paths:
134
166
  - lib
135
167
  required_ruby_version: !ruby/object:Gem::Requirement
136
168
  requirements:
137
- - - '>='
169
+ - - ">="
138
170
  - !ruby/object:Gem::Version
139
171
  version: '0'
140
172
  required_rubygems_version: !ruby/object:Gem::Requirement
141
173
  requirements:
142
- - - '>='
174
+ - - ">="
143
175
  - !ruby/object:Gem::Version
144
176
  version: '0'
145
177
  requirements: []
146
178
  rubyforge_project:
147
- rubygems_version: 2.0.3
179
+ rubygems_version: 2.2.2
148
180
  signing_key:
149
181
  specification_version: 4
150
182
  summary: Create form objects from multiple Active Record and/or ActiveAttr models.
151
183
  test_files:
152
- - spec/lib/forminate/client_side_validations.rb
153
- - spec/lib/forminate/client_side_validations_spec.rb
154
- - spec/lib/forminate_spec.rb
184
+ - spec/forminate/association_builder_spec.rb
185
+ - spec/forminate/association_definition_spec.rb
186
+ - spec/forminate/client_side_validations.rb
187
+ - spec/forminate/client_side_validations_spec.rb
188
+ - spec/forminate_spec.rb
155
189
  - spec/spec_helper.rb
156
190
  - spec/support/dummy_book.rb
157
191
  - spec/support/dummy_credit_card.rb