forminate 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 98167c9f1b4bb61b66458c8166c6c1406dc29426
4
- data.tar.gz: 5e6e5c08e0467967649dba74a28677b07353ef06
3
+ metadata.gz: f117e333af88c5257424675b2ba3b235a3a7e514
4
+ data.tar.gz: 6a5483b6310fdc7b46341d53f3dcd1f8382474df
5
5
  SHA512:
6
- metadata.gz: 9d82544a8edb6a3a87d70d7ae76313942a73ce0242a618dcd55937dee533cfe1d3870001ca9614b4db8bf8f4f138f9aba167f55438ac1d8470d0b9b59622a74d
7
- data.tar.gz: e156a1daa75023040fa9a04db1df13464d08a4afaba43718a9be20cbb7c35f0179e5e57bdb0d262b670a890a853d629be6890b5504591500fbfaef52cfad7c61
6
+ metadata.gz: 99a8d7568801c1732bbe22381569925e84da62555b8aa9edb1f2d3fa4fbb9bcd248c054db78414a868997d54dbff71cc2d5a9aa296585a9b63f7d238627bfeaa
7
+ data.tar.gz: 4eda18ed97e5a63be2fa985eafd15b403faf0640eefd5c2705dbc20c1a5c4d999f9e55ea1bf9d9de38182b650634c308e4c4369bb3f5dc5b6e319bb056d200ce
data/.travis.yml CHANGED
@@ -2,5 +2,10 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
- - jruby-19mode
6
- - rbx-19mode
5
+ - 2.1.0
6
+ - jruby
7
+ - rbx
8
+ addons:
9
+ code_climate:
10
+ repo_token:
11
+ secure: AeJjEZpQ3k0FFhsjvSce9k8VBOjed5Qe0wd6PlRhO8Uuv73VZQTFScvHSV/p0BUNUYMAvvPFHD8ha2yaC0cbqKzdZVbgp3OLmv553CymlmEGdOZxhg5X7thnheAMF/SMxoqpB9X+MHDbQxnf6hYoa6ntWSpzh4bb7ruyR03sGYM=
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in forminate.gemspec
4
4
  gemspec
5
+
6
+ gem 'codeclimate-test-reporter', group: :test, require: nil
data/README.md CHANGED
@@ -1,9 +1,19 @@
1
1
  # Forminate
2
2
 
3
- [![Build Status](https://travis-ci.org/molawson/forminate.png?branch=master)](https://travis-ci.org/molawson/forminate)
4
- [![Code Climate](https://codeclimate.com/github/molawson/forminate.png)](https://codeclimate.com/github/molawson/forminate)
3
+ [![Build Status](https://img.shields.io/travis/molawson/forminate.svg)](https://travis-ci.org/molawson/forminate)
4
+ [![Code Climate](https://img.shields.io/codeclimate/github/molawson/forminate.svg)](https://codeclimate.com/github/molawson/forminate)
5
+ [![Code Climate Coverage](https://img.shields.io/codeclimate/coverage/github/molawson/forminate.svg)](https://codeclimate.com/github/molawson/forminate)
5
6
 
6
- TODO: Write a gem description
7
+ Doing CRUD operations in Rails is pretty awesome. Just remember the first time you generated a Rails scaffold and almost immediately started creating and editing records in the database from a web form. I'd bet that hooked a lot of people. It certainly caught my attention.
8
+
9
+ Before too long, you need to create a page in a Rails app that has to update multiple models from a single form. Now, you feel the pain.
10
+
11
+ > Life is pain, Highness. Anyone who says differently is selling something.
12
+ _— Man in Black_
13
+
14
+ If you're at this point, let me introduce you to form objects. The general idea is that you create an object that represents the form you want to display in the view, and that form object handles aggregating and coordinating the various models that make up the form. For more information on the particulars of form objects and some example implementations in Rails, check out these great posts from [Code Climate](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/), [Thoughtbot](http://robots.thoughtbot.com/activemodel-form-objects), and [Pivotal Labs](http://pivotallabs.com/form-backing-objects-for-fun-and-profit/).
15
+
16
+ _Forminate gives you a handy way to create form objects that inherit behavior from the models you need and have just enough of the behavior you'd expect from an ActiveRecord or ActiveAttr model to make working with them feel very familiar._
7
17
 
8
18
  ## Installation
9
19
 
@@ -21,7 +31,203 @@ Or install it yourself as:
21
31
 
22
32
  ## Usage
23
33
 
24
- TODO: Write usage instructions here
34
+ ### Requirements
35
+
36
+ Currently, forminate only works with ActiveRecord and [ActiveAttr](https://github.com/cgriego/active_attr) models. I would love to extend it to support other models (and it may actually work with others), but only these two have been tested.
37
+
38
+ ### Example
39
+
40
+ To see how this works, lets take the classic example of a single page checkout process. We want to be able to have the user sign up and/or purchase a membership from a single form. We already have the following models in our system, all of which are needed on the checkout page.
41
+
42
+ ```ruby
43
+ class Membership < ActiveRecord::Base
44
+ # database columns: name, price
45
+ validates_presence_of :name
46
+ end
47
+
48
+ class User < ActiveRecord::Base
49
+ # database columns: first_name, last_name, email
50
+ validates_presence_of :email
51
+ attr_accessor :temporary_note
52
+ end
53
+
54
+ class CreditCard
55
+ include ActiveAttr::Model
56
+
57
+ attribute :number
58
+ attribute :expiration
59
+ attribute :cvv
60
+
61
+ validates_presence_of :number, :expiration, :cvv
62
+ validates_length_of :number, in: 12..19
63
+ end
64
+ ```
65
+
66
+ To better model what's actually happening on the checkout page, we create a Cart form object that includes a user, membership, and credit_card.
67
+
68
+ ```ruby
69
+ class Cart
70
+ include Forminate
71
+
72
+ attribute :total
73
+ attribute :tax
74
+
75
+ attributes_for :user
76
+ attributes_for :membership, validate: false
77
+ attributes_for :credit_card, validate: :require_credit_card?
78
+
79
+ validates_numericality_of :total
80
+
81
+ def require_credit_card?
82
+ membership.price && membership.price.to_f > 0.0
83
+ end
84
+ end
85
+ ```
86
+
87
+ This small class gives us a lot of nice features.
88
+
89
+ ### Attributes
90
+
91
+ The heart and soul of forminate is the `.attributes_for` method. Calling that method does a couple of things.
92
+
93
+ First, it sets up an association to an instance of the desired object, using the naming conventions you're used to in Rails, and exposes that object with reader and writer methods.
94
+
95
+ ```ruby
96
+ cart = Cart.new
97
+ cart.credit_card # => #<CreditCard number: nil, expiration: nil, cvv: nil>
98
+
99
+ payment_card = CreditCard.new(number: 4242424242424242, expiration: 0115, cvv: 123)
100
+ cart.credit_card = payment_card
101
+ cart.credit_card # => #<CreditCard number: 4242424242424242, expiration: 0115, cvv: 123>
102
+ ```
103
+
104
+ It also sets up reader and writer methods for all of the associated object's attributes To prevent method name conflicts, it prepends the underscore version of the model name.
105
+
106
+ ```ruby
107
+ cart = Cart.new
108
+ cart.credit_card_number # => nil
109
+ cart.credit_card_number = 4242424242424242
110
+ cart.credit_card_number # => 4242424242424242
111
+ ```
112
+
113
+ Using these new attribute names, you can initialize your form object with a hash of attributes, just like ActiveRecord models.
114
+
115
+ ```ruby
116
+ cart = Cart.new(credit_card_number: 4242424242424242, credit_card_expiration: 0115, credit_card_cvv: 123)
117
+ cart.credit_card_number # => 4242424242424242
118
+ cart.credit_card # => #<CreditCard number: 4242424242424242, expiration: 0115, cvv: 123>
119
+ ```
120
+
121
+ #### Supported methods
122
+
123
+ Forminate explicitly sets up reader and writer methods for accessing methods related to database columns for ActiveRecord models or attributes for ActiveAttr models.
124
+
125
+ Additionally, you can call any method on an associated object via the form object by prepending the object's name, just like you do with other attributes. For example, the `User` class above has defined an `attr_accessor` for `temporary_note`.
126
+
127
+ ```ruby
128
+ cart = Cart.new
129
+ cart.user_temporary_note # => nil
130
+ cart.user_temporary_note = "I won't be here long"
131
+ cart.user_temporary_note # => "I won't be here long"
132
+ ```
133
+
134
+ ### Rails Forms
135
+
136
+ Now that we've got all these handy methods defined, we can get back to building those Rails forms we all know and love.
137
+
138
+ In your controller, you can create an instance variable for your form object like you would do with a normal model.
139
+
140
+ ```ruby
141
+ class CartController < ApplicationController
142
+ def new
143
+ @cart = Cart.new
144
+ end
145
+ end
146
+ ```
147
+
148
+ Then, you can setup your form view just like you'd expect.
149
+
150
+ ```erb
151
+ <%= form_for @cart, url: cart_path, method: :post do |f| %>
152
+ <div class="field">
153
+ <%= f.label :user_email %>
154
+ <%= f.text_field :user_email %>
155
+ </div>
156
+ <div class="field">
157
+ <%= f.label :user_first_name %>
158
+ <%= f.text_field :user_first_name %>
159
+ </div>
160
+ <div class="field">
161
+ <%= f.label :user_last_name %>
162
+ <%= f.text_field :user_last_name %>
163
+ </div>
164
+ <div class="field">
165
+ <%= f.label :credit_card_number %>
166
+ <%= f.text_field :credit_card_number %>
167
+ </div>
168
+ <div class="field">
169
+ <%= f.label :credit_card_cvv %>
170
+ <%= f.text_field :credit_card_cvv %>
171
+ </div>
172
+ <%# etc., etc. %>
173
+ <% end %>
174
+ ```
175
+
176
+ ### Persistence
177
+
178
+ Forminate coordinates all the persistence for you. It includes a `#save` method that persists all the associated objects that also respond to `#save`. As long as ActiveRecord is available, forminate will wrap it's save in a single transaction, so if any of the associated models fails to save, it will roll everything back.
179
+
180
+ With this behavior, you can write your controller create actions just like you always have.
181
+
182
+ ```ruby
183
+ class CartController < ApplicationController
184
+ def new
185
+ @cart = Cart.new
186
+ end
187
+
188
+ def create
189
+ @cart = Cart.new(params[:cart])
190
+ if @cart.save
191
+ flash[:notice] = 'All good.'
192
+ redirect_to root_url
193
+ else
194
+ flash[:alert] = 'Something went terribly wrong.'
195
+ render :new
196
+ end
197
+ end
198
+ end
199
+ ```
200
+
201
+ Forminate also exposes a `#before_save` hook method that can be used in your form object if you need to do any extra work just before the models are saved.
202
+
203
+ ### Validations
204
+
205
+ By default, a forminate object will "inherit" all it's associated objects validations. Before saving it's associated objects, forminate will make sure that they're all valid. If not, it will return `false` and the form object will include an ActiveRecord-like errors object.
206
+
207
+ When calling `.attributes_for` to setup an associated object, you can pass a hash of options, which can include a `:validate` key. The value of the `:validate` key can be either, `true`, `false`, or a symbol that matches the name of a method that should be called to determine whether or not the association's validation should be checked (This is very similar to the `:if` option for the `.validates` methods in Rails).
208
+
209
+ From our example:
210
+
211
+ ```ruby
212
+ class Cart
213
+ include Forminate
214
+
215
+ attribute :total
216
+ attribute :tax
217
+
218
+ attributes_for :user
219
+ attributes_for :membership, validate: false
220
+ attributes_for :credit_card, validate: :require_credit_card?
221
+
222
+ validates_numericality_of :total
223
+
224
+ def require_credit_card?
225
+ membership.price && membership.price.to_f > 0.0
226
+ end
227
+ end
228
+ ```
229
+
230
+ In this case, if the membership that's being purchased is "free", we'll skip the credit card validations, and we won't bother with the membership validations at all.
25
231
 
26
232
  ## Contributing
27
233
 
data/Rakefile CHANGED
@@ -2,4 +2,4 @@ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
- task :default => :spec
5
+ task default: :spec
data/forminate.gemspec CHANGED
@@ -4,25 +4,31 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'forminate/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "forminate"
7
+ spec.name = 'forminate'
8
8
  spec.version = Forminate::VERSION
9
- spec.authors = ["Mo Lawson"]
10
- spec.email = ["moklawson@gmail.com"]
11
- spec.description = "Form objects for Rails applications"
12
- spec.summary = "Create form objects from multiple Active Record and/or ActiveAttr models."
13
- spec.homepage = "https://github.com/molawson/forminate"
14
- spec.license = "MIT"
9
+ spec.authors = ['Mo Lawson']
10
+ spec.email = ['mo@molawson.com']
11
+ spec.description = 'Form objects for Rails applications'
12
+ spec.summary = 'Create form objects from multiple Active Record and/or ActiveAttr models.'
13
+ spec.homepage = 'https://github.com/molawson/forminate'
14
+ spec.license = 'MIT'
15
15
 
16
- spec.files = `git ls-files`.split($/)
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency "active_attr", "~> 0.8"
22
- spec.add_dependency "activesupport", ">= 3.0.2", "< 4.1"
23
- spec.add_dependency "client_side_validations", "~> 3.2"
21
+ spec.add_dependency 'active_attr', '~> 0.8'
22
+ spec.add_dependency 'activesupport', '>= 3.0.2', '< 4.1'
23
+ spec.add_dependency 'client_side_validations', '~> 3.2'
24
24
 
25
- spec.add_development_dependency "bundler", "~> 1.3"
26
- spec.add_development_dependency "rake"
27
- spec.add_development_dependency "rspec", "~> 2.11"
25
+ spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'rake'
27
+ spec.add_development_dependency 'rspec', '~> 2.11'
28
+ spec.add_development_dependency 'activerecord', '~> 3.2'
29
+ if defined?(JRUBY_VERSION)
30
+ spec.add_development_dependency 'activerecord-jdbcsqlite3-adapter'
31
+ else
32
+ spec.add_development_dependency 'sqlite3'
33
+ end
28
34
  end
@@ -0,0 +1,44 @@
1
+ module Forminate
2
+ class AssociationBuilder
3
+ def initialize(name, attrs)
4
+ @name = name
5
+ @attrs = attrs
6
+ end
7
+
8
+ def build
9
+ if primary_key
10
+ object = klass.find(primary_key)
11
+ object.assign_attributes(association_attributes)
12
+ object
13
+ else
14
+ klass.new(association_attributes)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :name, :attrs
21
+
22
+ def klass
23
+ name.to_s.classify.constantize
24
+ end
25
+
26
+ def prefix
27
+ "#{name}_"
28
+ end
29
+
30
+ def primary_key
31
+ return unless klass.respond_to?(:primary_key)
32
+
33
+ attrs["#{name}_#{klass.primary_key}".to_sym]
34
+ end
35
+
36
+ def association_attributes
37
+ relevant_attributes = attrs.select { |k, _| k =~ /^#{prefix}/ }
38
+ relevant_attributes.each_with_object({}) do |(name, definition), hash|
39
+ new_key = name.to_s.sub(prefix, '').to_sym
40
+ hash[new_key] = definition
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ module Forminate
2
+ class AssociationDefinition
3
+ attr_reader :name
4
+
5
+ def initialize(name, options = {})
6
+ @name = name
7
+ @options = options
8
+ end
9
+
10
+ def attributes
11
+ name.to_s.classify.constantize.attribute_names
12
+ end
13
+
14
+ def validation_condition
15
+ options.fetch(:validate) { true }
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :options
21
+ end
22
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Forminate
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/forminate.rb CHANGED
@@ -1,8 +1,11 @@
1
- require "forminate/version"
1
+ require 'forminate/version'
2
2
 
3
3
  require 'active_support/concern'
4
4
  require 'active_attr'
5
5
 
6
+ require 'forminate/association_definition'
7
+ require 'forminate/association_builder'
8
+
6
9
  module Forminate
7
10
  extend ActiveSupport::Concern
8
11
  include ActiveAttr::Model
@@ -10,10 +13,10 @@ module Forminate
10
13
  included do
11
14
  validate do
12
15
  associations.each do |name, object|
13
- if should_validate_assoc?(name) && object.respond_to?(:invalid?) && object.invalid?
14
- object.errors.each do |field, messages|
15
- errors["#{name}_#{field}".to_sym] = messages
16
- end
16
+ next unless validate_assoc?(name) && object.respond_to?(:invalid?) && object.invalid?
17
+
18
+ object.errors.each do |field, messages|
19
+ errors["#{name}_#{field}".to_sym] = messages
17
20
  end
18
21
  end
19
22
  end
@@ -21,8 +24,7 @@ module Forminate
21
24
 
22
25
  module ClassMethods
23
26
  def attributes_for(name, options = {})
24
- define_attributes name
25
- define_association name, options
27
+ define_association(AssociationDefinition.new(name, options))
26
28
  end
27
29
 
28
30
  def association_names
@@ -35,42 +37,33 @@ module Forminate
35
37
 
36
38
  private
37
39
 
38
- def define_attributes(association_name)
39
- attributes = association_name.to_s.classify.constantize.attribute_names
40
- attributes.each { |attr| define_attribute(attr, association_name) }
40
+ def define_association(assoc)
41
+ assoc.attributes.each { |attr| define_attribute(attr, assoc.name) }
42
+ association_names << assoc.name
43
+ association_validations[assoc.name] = assoc.validation_condition
44
+ send(:attr_accessor, assoc.name)
41
45
  end
42
46
 
43
- def define_attribute(attr, assoc)
44
- ActiveAttr::AttributeDefinition.new("#{assoc}_#{attr}").tap do |attribute_definition|
47
+ def define_attribute(attr, assoc_name)
48
+ ActiveAttr::AttributeDefinition.new("#{assoc_name}_#{attr}").tap do |attribute_definition|
45
49
  attribute_name = attribute_definition.name.to_s
46
50
  attributes[attribute_name] = attribute_definition
47
51
  end
48
- define_attribute_reader(attr, assoc)
49
- define_attribute_writer(attr, assoc)
52
+ define_attribute_reader(attr, assoc_name)
53
+ define_attribute_writer(attr, assoc_name)
50
54
  end
51
55
 
52
- def define_attribute_reader(attr, assoc)
53
- define_method("#{assoc}_#{attr}") do
54
- send(assoc.to_sym).send(attr.to_sym)
56
+ def define_attribute_reader(attr, assoc_name)
57
+ define_method("#{assoc_name}_#{attr}") do
58
+ send(assoc_name.to_sym).send(attr.to_sym)
55
59
  end
56
60
  end
57
61
 
58
- def define_attribute_writer(attr, assoc)
59
- define_method("#{assoc}_#{attr}=") do |value|
60
- send(assoc.to_sym).send("#{attr}=".to_sym, value)
62
+ def define_attribute_writer(attr, assoc_name)
63
+ define_method("#{assoc_name}_#{attr}=") do |value|
64
+ send(assoc_name.to_sym).send("#{attr}=".to_sym, value)
61
65
  end
62
66
  end
63
-
64
- def define_association(name, options = {})
65
- association_names << name
66
- should_validate = if options.has_key?(:validate)
67
- options[:validate]
68
- else
69
- true
70
- end
71
- association_validations[name] = should_validate
72
- send(:attr_accessor, name)
73
- end
74
67
  end
75
68
 
76
69
  def initialize(attributes = {})
@@ -78,10 +71,6 @@ module Forminate
78
71
  super
79
72
  end
80
73
 
81
- def persisted?
82
- false
83
- end
84
-
85
74
  def association_names
86
75
  self.class.association_names
87
76
  end
@@ -128,69 +117,40 @@ module Forminate
128
117
 
129
118
  def build_associations(attributes)
130
119
  association_names.each do |association_name|
131
- association = build_association(association_name, attributes)
120
+ association = AssociationBuilder.new(association_name, attributes).build
132
121
  instance_variable_set("@#{association_name}".to_sym, association)
133
122
  end
134
123
  end
135
124
 
136
- def build_association(name, attributes)
137
- association_attributes = attributes_for_association(name, attributes)
138
- klass = name.to_s.classify.constantize
139
-
140
- if klass.respond_to? :primary_key
141
- primary_key = attributes["#{name}_#{klass.primary_key}".to_sym]
142
- end
143
-
144
- if primary_key
145
- object = klass.find primary_key
146
- object.assign_attributes association_attributes
147
- object
148
- else
149
- klass.new association_attributes
150
- end
151
- end
152
-
153
- def attributes_for_association(association_name, attributes)
154
- prefix = "#{association_name}_"
155
- relevant_attributes = attributes.select { |k, v| k =~ /#{prefix}/ }
156
- relevant_attributes.reduce({}) do |hash, (name, definition)|
157
- new_key = name.to_s.sub(prefix, '').to_sym
158
- hash[new_key] = definition
159
- hash
160
- end
161
- end
162
-
163
125
  def persist_associations
164
- associations.each do |name, object|
165
- object.save if object.respond_to? :save
166
- end
126
+ associations.each { |_, object| object.save if object.respond_to?(:save) }
167
127
  end
168
128
 
169
- def should_validate_assoc?(name)
170
- method_name = assoc_validation_filter_method(name)
171
- send(method_name)
129
+ def validate_assoc?(name)
130
+ send(assoc_validation_filter_method(name))
172
131
  end
173
132
 
174
133
  def assoc_validation_filter_method(name)
175
134
  filter = self.class.association_validations.fetch(name, true)
176
- method_name = "should_validate_#{name}?".to_sym
177
135
  case filter
178
136
  when Symbol
179
137
  filter
180
138
  when TrueClass, FalseClass
139
+ method_name = "validate_#{name}?".to_sym
181
140
  self.class.send(:define_method, method_name) { filter }
182
141
  method_name
183
142
  else
184
- raise NotImplementedError, "The attributes_for :validate option can only take a symbol, true, or false"
143
+ fail NotImplementedError, 'The attributes_for :validate option can only take a symbol, true, or false'
185
144
  end
186
145
  end
187
146
 
188
147
  def association_for_method(name)
189
- assoc_name = association_names.find { |an| name.match /^#{an}_/ }
190
- if assoc_name
191
- assoc_method_name = name.to_s.sub("#{assoc_name}_", '').to_sym
192
- assoc = send(assoc_name)
193
- return assoc, assoc_method_name
194
- end
148
+ assoc_name = association_names.find { |an| name.match(/^#{an}_/) }
149
+
150
+ return unless assoc_name
151
+
152
+ assoc_method_name = name.to_s.sub("#{assoc_name}_", '').to_sym
153
+ assoc = send(assoc_name)
154
+ return assoc, assoc_method_name
195
155
  end
196
156
  end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Forminate::AssociationBuilder do
4
+ let(:name) { 'dummy_book' }
5
+ let(:attrs) do
6
+ {
7
+ dummy_user_first_name: 'Mo',
8
+ dummy_book_title: 'The Hobbit'
9
+ }
10
+ end
11
+
12
+ subject { Forminate::AssociationBuilder.new(name, attrs) }
13
+
14
+ describe '#build' do
15
+ context 'non-ActiveRecord model' do
16
+ it 'populates the association with the given attributes' do
17
+ expect(subject.build.title).to eq('The Hobbit')
18
+ end
19
+ end
20
+
21
+ context 'ActiveRecord model' do
22
+ let(:name) { 'dummy_user' }
23
+
24
+ context 'primary key not present in attributes' do
25
+ it 'populates the association with the given attributes' do
26
+ expect(subject.build.first_name).to eq('Mo')
27
+ end
28
+ end
29
+
30
+ context 'primary key present in attributes' do
31
+ let(:user) do
32
+ DummyUser.create(
33
+ first_name: 'Mo',
34
+ last_name: 'Lawson',
35
+ email: 'mo@example.com'
36
+ )
37
+ end
38
+ let(:attrs) do
39
+ {
40
+ dummy_user_id: user.id,
41
+ dummy_user_first_name: 'Matthew',
42
+ dummy_book_title: 'The Hobbit'
43
+ }
44
+ end
45
+
46
+ it 'populates the association from the database' do
47
+ expect(subject.build.last_name).to eq('Lawson')
48
+ end
49
+
50
+ it 'overrides database values with passed in values' do
51
+ expect(subject.build.first_name).to eq('Matthew')
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Forminate::AssociationDefinition do
4
+ let(:options) { {} }
5
+
6
+ subject { Forminate::AssociationDefinition.new(DummyUser, options) }
7
+
8
+ describe '#attributes' do
9
+ it 'returns a list of attributes from the appropriate class' do
10
+ expect(subject.attributes).to eq(%w(id first_name last_name email))
11
+ end
12
+ end
13
+
14
+ describe '#validation_condition' do
15
+ context 'options hash does not contain :validate key' do
16
+ it 'returns true' do
17
+ expect(subject.validation_condition).to be_true
18
+ end
19
+ end
20
+
21
+ context 'options hash contains :validate key' do
22
+ let(:options) { { validate: :stars_aligned? } }
23
+
24
+ it 'returns the value' do
25
+ expect(subject.validation_condition).to eq(:stars_aligned?)
26
+ end
27
+ end
28
+ end
29
+ end