soulless 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -1
  3. data/Gemfile +15 -7
  4. data/README.md +223 -31
  5. data/lib/soulless/associations.rb +27 -0
  6. data/lib/soulless/locale/en.yml +30 -0
  7. data/lib/soulless/model.rb +8 -1
  8. data/lib/soulless/validations/associated_validator.rb +17 -0
  9. data/lib/soulless/validations/uniqueness_validator.rb +38 -0
  10. data/lib/soulless/validations.rb +10 -0
  11. data/lib/soulless/version.rb +1 -1
  12. data/lib/soulless.rb +10 -0
  13. data/soulless.gemspec +4 -2
  14. data/spec/associated_validator_spec.rb +31 -0
  15. data/spec/associations_spec.rb +45 -0
  16. data/spec/dummy/.gitignore +17 -0
  17. data/spec/dummy/README.rdoc +28 -0
  18. data/spec/dummy/Rakefile +6 -0
  19. data/spec/dummy/app/assets/images/.keep +0 -0
  20. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  23. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  24. data/spec/dummy/app/forms/dummy_form.rb +7 -0
  25. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  26. data/spec/dummy/app/mailers/.keep +0 -0
  27. data/spec/dummy/app/models/.keep +0 -0
  28. data/spec/dummy/app/models/concerns/.keep +0 -0
  29. data/spec/dummy/app/models/dummy_model.rb +3 -0
  30. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  31. data/spec/dummy/bin/bundle +3 -0
  32. data/spec/dummy/bin/rails +4 -0
  33. data/spec/dummy/bin/rake +4 -0
  34. data/spec/dummy/config/application.rb +28 -0
  35. data/spec/dummy/config/boot.rb +6 -0
  36. data/spec/dummy/config/database.yml +25 -0
  37. data/spec/dummy/config/environment.rb +5 -0
  38. data/spec/dummy/config/environments/development.rb +29 -0
  39. data/spec/dummy/config/environments/production.rb +80 -0
  40. data/spec/dummy/config/environments/test.rb +36 -0
  41. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  42. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  43. data/spec/dummy/config/initializers/inflections.rb +16 -0
  44. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  45. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  46. data/spec/dummy/config/initializers/session_store.rb +3 -0
  47. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/spec/dummy/config/locales/en.yml +23 -0
  49. data/spec/dummy/config/routes.rb +56 -0
  50. data/spec/dummy/config.ru +4 -0
  51. data/spec/dummy/db/migrate/20131106221200_create_dummy_models.rb +9 -0
  52. data/spec/dummy/db/schema.rb +22 -0
  53. data/spec/dummy/db/seeds.rb +7 -0
  54. data/spec/dummy/db/test.sqlite3 +0 -0
  55. data/spec/dummy/lib/assets/.keep +0 -0
  56. data/spec/dummy/lib/tasks/.keep +0 -0
  57. data/spec/dummy/log/.keep +0 -0
  58. data/spec/dummy/public/404.html +58 -0
  59. data/spec/dummy/public/422.html +58 -0
  60. data/spec/dummy/public/500.html +57 -0
  61. data/spec/dummy/public/favicon.ico +0 -0
  62. data/spec/dummy/public/robots.txt +5 -0
  63. data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
  64. data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
  65. data/spec/soulless_spec.rb +1 -1
  66. data/spec/spec_helper.rb +22 -3
  67. data/spec/support/dummy_association.rb +30 -0
  68. data/spec/support/en.yml +8 -0
  69. data/spec/uniqueness_validator_spec.rb +17 -0
  70. metadata +148 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed8e30f22d775b7646b996ff1a6ecf92ec96fe98
4
- data.tar.gz: 288d8bf2b2b3b077e0369477168f4ff6e1bdacd3
3
+ metadata.gz: b3a918d43aa402d0de13f3e8bcc28b1d4cbad074
4
+ data.tar.gz: a706a4c34294af699a1efdbda86dd649ffd278f5
5
5
  SHA512:
6
- metadata.gz: bd80dec834541eb84183a91486b1e18d0dd5d35385b01a0cce3bf096ea8fa6d85f22eaeb181d2432bf3d90d1ce67086c53440ea0257fa3de90e7f0456d555cec
7
- data.tar.gz: faa287251a4f8ceaf9ef077d02279ece8755c55517d685a1c57d4da3c9f441e5d24d2428a22994d18cb9efb011a5b9caab6b0a3799a3ecdd12f646a4609dee5f
6
+ metadata.gz: 0187eb34dda842f89d9be39593e3168c1189ae7abbe61343b1ec5e80e14bebbfeaaebfd357f0c1c2257edd643534795f901f91104b2765f5ed614005ec0dd94f
7
+ data.tar.gz: fab7de2ebac15f30943af023b644e93759131fc40e3a3a46523440e3da42980f88e55aaae551ee1aad1b00e64c705bd6d0e9931821f2b6a39340db62aa2abd1a
data/.travis.yml CHANGED
@@ -5,4 +5,11 @@ rvm:
5
5
  - ruby-head
6
6
  - jruby-19mode
7
7
  - jruby-head
8
- - rbx-19mode
8
+ - rbx-19mode
9
+ env:
10
+ - "RAILS_VERSION=3.2.0"
11
+ - "RAILS_VERSION=4.0.0"
12
+ - "RAILS_VERSION=master"
13
+ matrix:
14
+ allow_failures:
15
+ - env: "RAILS_VERSION=master"
data/Gemfile CHANGED
@@ -3,13 +3,21 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in ar_book_finder.gemspec
4
4
  gemspec
5
5
 
6
- group :development do
7
- gem 'bundler'
8
- gem 'coveralls', require: false
9
- gem 'rake'
6
+ rails_version = case ENV['RAILS_VERSION'] || 'default'
7
+ when 'master'
8
+ { git: 'https://github.com/rails/rails.git' }
9
+ when 'default'
10
+ '~> 4.0'
11
+ else
12
+ "~> #{ENV['RAILS_VERSION']}"
10
13
  end
11
14
 
12
- group :test do
13
- gem 'rspec'
14
- gem 'simplecov'
15
+ gem 'rails', rails_version
16
+
17
+ platforms :ruby do
18
+ gem 'sqlite3'
19
+ end
20
+
21
+ platforms :jruby do
22
+ gem "activerecord-jdbcsqlite3-adapter"
15
23
  end
data/README.md CHANGED
@@ -1,12 +1,10 @@
1
1
  # Soulless
2
2
 
3
- Rails models without the database (and Rails).
3
+ Rails models without the database (and Rails). Great for implementing the form object pattern.
4
4
 
5
5
  [![Build Status](https://travis-ci.org/anthonator/soulless.png?branch=master)](https://travis-ci.org/anthonator/soulless) [![Dependency Status](https://gemnasium.com/anthonator/soulless.png)](https://gemnasium.com/anthonator/soulless)
6
6
  [![Coverage Status](https://coveralls.io/repos/anthonator/soulless/badge.png?branch=master)](https://coveralls.io/r/anthonator/soulless?branch=master) [![Code Climate](https://codeclimate.com/github/anthonator/soulless.png)](https://codeclimate.com/github/anthonator/soulless)
7
7
 
8
- Great for implementing the form object pattern. [Check out #3](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/) of this great article by @brynary for more information on the form object pattern.
9
-
10
8
  ## Installation
11
9
 
12
10
  Add this line to your application's Gemfile:
@@ -23,77 +21,271 @@ Or install it yourself as:
23
21
 
24
22
  ## Usage
25
23
 
26
- Just define a plain-old-ruby-object and get crackin'!
24
+ Just define a plain-old-ruby-object, include Soulless and get crackin'!
27
25
 
28
26
  ```ruby
29
27
  class UserSignupForm
30
28
  include Soulless.model
31
29
 
32
- attribute :email, String
33
30
  attribute :name, String
34
- attribute :password
35
- attribute :super_awesome, Boolean, default: false
36
-
37
- validates :email, presence: true
31
+ attribute :email, String
32
+ attribute :password, String
38
33
 
39
34
  validates :name, presence: true
40
35
 
36
+ validates :email, presence: true,
37
+ uniqueness: { model: User }
38
+
41
39
  validates :password, presence: true,
42
40
  lenght: { is_at_least: 8 }
43
41
 
44
42
  private
45
43
  def persist!
46
- # You're gonna need this!
47
- # Define what to do with this form when ready to save here.
44
+ # Define what to do when this form is ready to be saved.
48
45
  end
49
46
  end
50
47
  ```
51
48
 
52
- Awesome. Now let's do something with it.
49
+ ### Default Values
50
+
51
+ When defining attributes it's possible to specify default values.
53
52
 
54
53
  ```ruby
55
- UsersController < ApplicationController
56
- def create
57
- @form = UserSignupForm.new(params.permit(user: [:email, :name, :password, :super_awesome]))
58
- if @form.save
59
- # do something here
60
- else
61
- render :new
62
- end
63
- end
54
+ class UserSignupForm
55
+
56
+ ...
57
+
58
+ attribute :name, String, default: 'Anthony'
59
+
60
+ ...
64
61
  end
65
62
  ```
66
63
 
67
- ### Configuration
64
+ ### Embedded Values
65
+
66
+ It's possible to use other Soulless objects as attribute types.
68
67
 
69
68
  ```ruby
69
+ class User
70
+ include Soulless.model
71
+
72
+ attribute :name, String
73
+ attribute :email, String
74
+ attribute :password, String
75
+ end
76
+
70
77
  class UserSignupForm
71
- # include attribute DSL + constructor + mass-assignment
72
78
  include Soulless.model
79
+
80
+ attribute :user, User
73
81
  end
74
82
  ```
75
83
 
84
+ ### Collection Coercions
85
+
86
+ Define collections with specific types.
87
+
88
+ ```ruby
89
+ class PhoneNumber
90
+ include Soulless.model
91
+
92
+ attribute :number, String
93
+ end
94
+
95
+ class Person
96
+ include Soulless.model
97
+
98
+ attribute :phone_numbers, Array[PhoneNumber]
99
+ end
100
+ ```
101
+
102
+ ### Processing an Object
103
+
104
+ Soulless let's _you_ define what happens when your object is ready to be processed.
105
+
76
106
  ```ruby
77
107
  class UserSignupForm
78
- # include attribute DSL + constructor
79
- include Soulless.model(mass_assignment: false)
108
+
109
+ ...
110
+
111
+ private
112
+ def persist!
113
+ user = User.create!(name: name, email: email, password: password)
114
+ UserMailer.send_activation_code(user).deliver
115
+ user.charge_card!
116
+ end
80
117
  end
81
118
  ```
82
119
 
120
+ Process your Soulless object by calling ```save```. Just like a Rails model!
121
+
122
+ ```ruby
123
+ form = UserSignupForm.new(name: name, email: email, password: passord)
124
+ if form.save
125
+ # Called persist! and all is good!
126
+ else
127
+ # Looks like a validation failed. Try again.
128
+ end
129
+ ```
130
+
131
+ ### Validations and Errors
132
+
133
+ Soulless lets you define your validations and manage your errors just like you did in Rails.
134
+
83
135
  ```ruby
84
136
  class UserSignupForm
85
- # include attribute DSL
86
- include Soulless.model(constructor: false, mass_assignment: false)
137
+
138
+ ...
139
+
140
+ validates :name, presence: true
141
+
142
+ validates :email, presence: true,
143
+ uniqueness: { model: User }
144
+
145
+ validates :password, presence: true,
146
+ lenght: { minimum: 8 }
147
+
148
+ ...
149
+
87
150
  end
88
151
  ```
89
152
 
90
- ### Batteries included...
153
+ Check to see if your object is valid by calling ```valid?```.
91
154
 
92
- The great thing about Soulless? Rails isn't required.
155
+ ```ruby
156
+ form = UserSignupForm.new(name: name, email: email)
157
+ form.valid? # => false
158
+ ```
159
+
160
+ See what errors are popping up using the ```errors``` attribute.
93
161
 
94
- ### This looks familir...
162
+ ```ruby
163
+ form = UserSignupForm.new(name: name, email: email)
164
+ form.valid?
165
+ form.errors[:password] # => ["is too short (minimum is 8 characters)"]
166
+ ```
95
167
 
96
- You noticed? We're using Virtus to power this gem. That means you get all the Virtus goodies for free. Check out [their GitHub page](https://github.com/solnic/virtus) to see what's up.
168
+ #### Uniqueness Validations
169
+
170
+ If you're using Soulless in Rails it's even possible to validate uniqueness.
171
+
172
+ ```ruby
173
+ class UserSignupForm
174
+
175
+ ...
176
+
177
+ validates :primary_email, presence: true,
178
+ uniqueness: { model: User, attribute: :email }
179
+
180
+ ...
181
+
182
+ end
183
+ ```
184
+
185
+ Just let the validator know what ActiveRecord model to use when performing the validation using the ```model``` option.
186
+
187
+ If your Soulless object attribute doesn't match up to the ActiveRecord model attribute just map it using the ```attribute``` option.
188
+
189
+ ### ```has_one``` and ```has_many``` Associations
190
+
191
+ When you need associations use ```has_one``` and ```has_many```. Look familiar?
192
+
193
+ ```ruby
194
+ class Person
195
+ include Soulless.model
196
+
197
+ attribute :name, String
198
+
199
+ validates :name, presence: true
200
+
201
+ has_one :spouse do
202
+ attribute :name, String
203
+ end
204
+
205
+ has_many :friends do
206
+ attribute :name, String
207
+ end
208
+ end
209
+ ```
210
+
211
+ You can set ```has_one``` and ```has_many``` attributes by setting their values hashes and hash arrays.
212
+
213
+ ```ruby
214
+ person = Person.new(name: 'Anthony')
215
+ person.spouse = { name: 'Megan' }
216
+ person.friends = [{ name: 'Yaw' }, { name: 'Biff' }]
217
+ ```
218
+
219
+ It's also possible for an association to inherit from a parent class and then extend functionality.
220
+
221
+ ```ruby
222
+ class Person
223
+ include Soulless.model
224
+
225
+ attribute :name, String
226
+
227
+ validates :name, presence: true
228
+
229
+ has_one :spouse, Person do # inherits 'name' and validation from Person
230
+ attribute :anniversary, DateTime
231
+
232
+ validates :anniversary, presence: true
233
+ end
234
+
235
+ has_many :friends, Person # just inherit from Person, don't extend
236
+ end
237
+ ```
238
+
239
+ When you need to make sure an association is valid before processing the object use ```validates_associated```.
240
+
241
+ ```ruby
242
+ class Person
243
+
244
+ ...
245
+
246
+ has_one :spouse do
247
+ attribute :name, String
248
+
249
+ validates :name, presence: true
250
+ end
251
+
252
+ ...
253
+
254
+ end
255
+
256
+ person = Person.new(name: 'Anthony')
257
+ person.spouse = { name: nil }
258
+ person.valid? # => false
259
+ person.errors[:spouse] # => ["is invalid"]
260
+ person.spouse.errors[:name] # => ["can't be blank"]
261
+ ```
262
+
263
+ ### I18n
264
+
265
+ Define locales similar to how you would define them in Rails.
266
+
267
+
268
+ ```yaml
269
+ en:
270
+ soulless:
271
+ errors:
272
+ models:
273
+ person:
274
+ name:
275
+ blank: "there's nothing here"
276
+ ```
277
+
278
+ For attributes defined as ```has_one``` and ```has_many``` associations use the enclosing class as the locale key's namespace.
279
+
280
+ ```yaml
281
+ en:
282
+ soulless:
283
+ errors:
284
+ models:
285
+ person/spouse:
286
+ name:
287
+ blank: "there's nothing here"
288
+ ```
97
289
 
98
290
  ## Contributing
99
291
 
@@ -0,0 +1,27 @@
1
+ module Soulless
2
+ module Associations
3
+ def self.included(base)
4
+ base.instance_eval do |object|
5
+ def has_one(name, superclass = Object, &block)
6
+ klass = define_virtus_class(name, superclass, &block)
7
+ send(:attribute, name, klass)
8
+ end
9
+
10
+ def has_many(name, superclass = Object, &block)
11
+ klass = define_virtus_class(name, superclass, &block)
12
+ send(:attribute, name, Array[klass])
13
+ end
14
+
15
+ private
16
+ def define_virtus_class(name, superclass, &block)
17
+ klass_name = name.to_s.singularize.classify + '_' + SecureRandom.hex
18
+ klass = const_set(klass_name, Class.new(superclass))
19
+ klass.send(:include, Soulless.model) unless klass.included_modules.include?(Model)
20
+ klass.instance_eval(&block) if block_given?
21
+ klass.model_name.instance_variable_set(:@i18n_key, klass.model_name.i18n_key.to_s.gsub(/_[^_]+$/, '').underscore.to_sym)
22
+ klass
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ en:
2
+ errors:
3
+ # The default format to use in full error messages.
4
+ format: "%{attribute} %{message}"
5
+
6
+ # The values :model, :attribute and :value are always available for interpolation
7
+ # The value :count is available when applicable. Can be used for pluralization.
8
+ messages:
9
+ inclusion: "is not included in the list"
10
+ exclusion: "is reserved"
11
+ invalid: "is invalid"
12
+ confirmation: "doesn't match %{attribute}"
13
+ accepted: "must be accepted"
14
+ empty: "can't be empty"
15
+ blank: "can't be blank"
16
+ present: "must be blank"
17
+ too_long: "is too long (maximum is %{count} characters)"
18
+ too_short: "is too short (minimum is %{count} characters)"
19
+ wrong_length: "is the wrong length (should be %{count} characters)"
20
+ not_a_number: "is not a number"
21
+ not_an_integer: "must be an integer"
22
+ greater_than: "must be greater than %{count}"
23
+ greater_than_or_equal_to: "must be greater than or equal to %{count}"
24
+ equal_to: "must be equal to %{count}"
25
+ less_than: "must be less than %{count}"
26
+ less_than_or_equal_to: "must be less than or equal to %{count}"
27
+ other_than: "must be other than %{count}"
28
+ odd: "must be odd"
29
+ even: "must be even"
30
+ taken: "has already been taken"
@@ -3,8 +3,15 @@ module Soulless
3
3
  def self.included(base)
4
4
  base.class_eval do
5
5
  extend ActiveModel::Naming
6
- include ActiveModel::Conversion
6
+ extend ActiveModel::Translation
7
7
  include ActiveModel::Validations
8
+ include ActiveModel::Conversion
9
+
10
+ class << self
11
+ def i18n_scope
12
+ :soulless
13
+ end
14
+ end
8
15
 
9
16
  def persisted?
10
17
  false
@@ -0,0 +1,17 @@
1
+ module Soulless
2
+ module Validations
3
+ class AssociatedValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attribute, value)
5
+ if Array.wrap(value).reject { |r| r.valid? }.any?
6
+ record.errors.add(attribute, :invalid, options.merge(value: value))
7
+ end
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def validates_associated(*attr_names)
13
+ validates_with(AssociatedValidator, _merge_attributes(attr_names))
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ module Soulless
2
+ module Validations
3
+ class UniquenessValidator < ActiveModel::EachValidator
4
+ def initialize(options)
5
+ raise 'ActiveRecord is not defined. The Soulless uniqueness validator cannot be used when ActiveRecord is not present.' unless Object.const_defined?('ActiveRecord')
6
+ @model = options[:model]
7
+ @attribute = options[:attribute]
8
+ options.merge!(class: @model) if ActiveModel::VERSION::STRING >= '4.1.0'
9
+ @validator = ActiveRecord::Validations::UniquenessValidator.new(options)
10
+ super(options)
11
+ end
12
+
13
+ def validate_each(record, attribute, value)
14
+ if !@model
15
+ raise ArgumentError, 'Missing required argument "model"'
16
+ else
17
+ record_orig, attribute_orig = record, attribute
18
+
19
+ attribute = @attribute if @attribute
20
+ record = @model.new(attribute => value)
21
+
22
+ @validator.setup(@model) if ActiveModel::VERSION::STRING < '4.1'
23
+ @validator.validate_each(record, attribute, value)
24
+
25
+ if record.errors.any?
26
+ record_orig.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(value: value))
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ def validates_uniqueness_of(*attr_name)
34
+ validates_with(UniquenessValidator, _merge_attributes(attr_name))
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,10 @@
1
+ require 'soulless/validations/associated_validator'
2
+ require 'soulless/validations/uniqueness_validator'
3
+
4
+ module Soulless
5
+ module Validations
6
+ def self.included(base)
7
+ base.send(:extend, ClassMethods)
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module Soulless
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/soulless.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  require 'virtus'
2
+ require 'securerandom'
3
+ require 'active_support/core_ext/array/wrap'
2
4
  require 'active_support/core_ext/class/attribute'
3
5
  require 'active_support/core_ext/module/delegation'
4
6
  require 'active_support/callbacks'
5
7
  require 'active_support/concern'
8
+ require 'active_support/inflector'
6
9
  require 'active_model/naming'
7
10
  require 'active_model/translation'
8
11
  require 'active_model/callbacks'
@@ -10,16 +13,23 @@ require 'active_model/validator'
10
13
  require 'active_model/errors'
11
14
  require 'active_model/validations'
12
15
  require 'active_model/conversion'
16
+ require 'active_model/version'
13
17
 
14
18
  require 'soulless/model'
19
+ require 'soulless/associations'
20
+ require 'soulless/validations'
15
21
  require 'soulless/version'
16
22
 
17
23
  module Soulless
24
+ I18n.load_path += Dir.glob('lib/soulless/locale/*.{rb,yml}')
25
+
18
26
  def self.model(options = {})
19
27
  mod = Module.new
20
28
  mod.define_singleton_method :included do |object|
21
29
  object.send(:include, Virtus.model(options))
22
30
  object.send(:include, Model)
31
+ object.send(:include, Associations)
32
+ object.send(:include, Validations)
23
33
  end
24
34
  mod
25
35
  end
data/soulless.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Soulless::VERSION
9
9
  spec.authors = ['Anthony Smith']
10
10
  spec.email = ['anthony@sticksnleaves.com']
11
- spec.description = %q{Models without a soul.}
11
+ spec.description = %q{Rails models without a database (and Rails).}
12
12
  spec.summary = %q{Create Rails style models without the database.}
13
13
  spec.homepage = ''
14
14
  spec.license = 'MIT'
@@ -22,6 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency 'activemodel', '>= 3.2'
23
23
  spec.add_runtime_dependency 'virtus', '>= 1.0'
24
24
 
25
- spec.add_development_dependency 'bundler', '~> 1.3'
25
+ spec.add_development_dependency 'bundler'
26
+ spec.add_development_dependency 'database_cleaner'
26
27
  spec.add_development_dependency 'rake'
28
+ spec.add_development_dependency 'rspec'
27
29
  end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Soulless::Validations::AssociatedValidator do
4
+ it 'should not be valid if has_one associations are invalid' do
5
+ dummy_association = DummyAssociation.new
6
+ dummy_association.spouse = { name: nil }
7
+ dummy_association.valid?.should be_false
8
+ dummy_association.errors[:spouse].should include('is invalid')
9
+ dummy_association.spouse.errors[:name].should include("can't be blank")
10
+ end
11
+
12
+ it 'should be valid if has_one associations are valid' do
13
+ dummy_association = DummyAssociation.new
14
+ dummy_association.spouse = { name: 'Megan' }
15
+ dummy_association.valid?.should be_true
16
+ end
17
+
18
+ it 'should not be valid if has_many associations are invalid' do
19
+ dummy_association = DummyAssociation.new
20
+ dummy_association.friends = [{ name: nil }, { name: nil }]
21
+ dummy_association.valid?.should be_false
22
+ dummy_association.errors[:friends].should include('is invalid')
23
+ dummy_association.friends[0].errors[:name].should include("can't be blank")
24
+ end
25
+
26
+ it 'should be valid if has_many associations are valid' do
27
+ dummy_association = DummyAssociation.new
28
+ dummy_association.friends = [{ name: 'Yaw' }, { name: 'Biff' }]
29
+ dummy_association.valid?.should be_true
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Soulless::Associations do
4
+ before(:each) do
5
+ @dummy_class = DummyAssociation.new
6
+ end
7
+
8
+ describe 'has_one' do
9
+ it 'should allow hash values' do
10
+ @dummy_class.spouse = { name: 'Megan' }
11
+ @dummy_class.spouse.name.should == 'Megan'
12
+ end
13
+
14
+ it 'should allow a class type to be defined' do
15
+ @dummy_class.dummy_clone = { name: 'Megan' }
16
+ @dummy_class.dummy_clone.class.name.should match(/\ADummyAssociation::DummyClone/)
17
+ end
18
+
19
+ it 'should properly pull down error translations' do
20
+ @dummy_class.dummy_clone = { name: nil }
21
+ @dummy_class.dummy_clone.save
22
+ @dummy_class.dummy_clone.errors[:name][0].should == "can't be blank"
23
+ end
24
+
25
+ it 'should properly pull down custom error translations' do
26
+ I18n.load_path += Dir.glob(File.dirname(__FILE__) + '/support/en.yml')
27
+ I18n.backend.load_translations
28
+ @dummy_class.dummy_clone = { name: nil }
29
+ @dummy_class.dummy_clone.save
30
+ @dummy_class.dummy_clone.errors[:name][0].should == 'this is a test'
31
+ end
32
+ end
33
+
34
+ describe 'has_many' do
35
+ it 'should allow array values' do
36
+ @dummy_class.friends = [{ name: 'Biff' }]
37
+ @dummy_class.friends[0].name.should == 'Biff'
38
+ end
39
+
40
+ it 'should allow a class type to be defined' do
41
+ @dummy_class.dummy_clones = [{ name: 'Biff' }]
42
+ @dummy_class.dummy_clones[0].class.name.should match(/\ADummyAssociation::DummyClone/)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile '~/.gitignore_global'
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore the default SQLite database.
11
+ /db/development.sqlite3
12
+ /db/production.sqlite3
13
+ /db/*.sqlite3-journal
14
+
15
+ # Ignore all logfiles and tempfiles.
16
+ /log/*.log
17
+ /tmp