soulless 0.4.3 → 0.5.0.rc1

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.
data/.travis.yml CHANGED
@@ -2,14 +2,17 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
+ - 2.1.0
5
6
  - ruby-head
6
7
  - jruby-19mode
7
8
  - jruby-head
8
- - rbx-19mode
9
+ - rbx
9
10
  env:
10
11
  - "RAILS_VERSION=3.2.0"
11
12
  - "RAILS_VERSION=4.0.0"
12
13
  - "RAILS_VERSION=master"
13
14
  matrix:
14
15
  allow_failures:
15
- - env: "RAILS_VERSION=master"
16
+ - env: "RAILS_VERSION=master"
17
+ - rvm: ruby-head
18
+ - rvm: jruby-head
data/Gemfile CHANGED
@@ -22,6 +22,13 @@ platforms :jruby do
22
22
  gem "activerecord-jdbcsqlite3-adapter"
23
23
  end
24
24
 
25
+ platforms :rbx do
26
+ gem 'psych'
27
+ gem 'racc'
28
+ gem 'rubysl'
29
+ gem 'rubinius-coverage'
30
+ end
31
+
25
32
  group :development do
26
33
  gem 'coveralls', require: false
27
34
  end
data/README.md CHANGED
@@ -253,6 +253,82 @@ person.spouse.name_change # => ["Mary Jane Watson", "Gwen Stacy"]
253
253
  person.changed? # => false
254
254
  ```
255
255
 
256
+ ### Inheritance
257
+
258
+ One of the biggest pitfalls of the form object pattern is duplication of code. It's not uncommon for a form object to define attributes and validations that are identical to the model it represets.
259
+
260
+ To get rid of this annoying issue Soulless implements the ```#inherit_from(klass, options = {})``` method. This method will allow a Soulless model to inherit attributes and validations from any Rails model, Soulless model or Virtus object.
261
+
262
+ ```ruby
263
+ class User < ActiveRecord::Base
264
+ validates :name, presence: true
265
+
266
+ validates :email, presence: true,
267
+ uniqueness: { case_insensitive: true }
268
+ end
269
+ ```
270
+
271
+ ```ruby
272
+ class UserSignupForm
273
+ include Soulless.model
274
+
275
+ inherit_from(User)
276
+ end
277
+ ```
278
+
279
+ The ```UserSignupForm``` has automatically been defined with the ```name``` and ```email``` attributes and validations.
280
+
281
+ ```ruby
282
+ UserSignupForm.attributes # => name and email attributes
283
+ form = UserSignupForm.new
284
+ form.valid? # => false
285
+ form.errors.messages # => { name: ["can't be blank"], email: ["can't be blank"] }
286
+ ```
287
+
288
+ If your model is using the uniqueness validator it will automatically convert it to the [uniqueness validator provided by Soulless](https://github.com/anthonator/soulless#uniqueness-validations).
289
+
290
+ The ```#inherit_from(klass, options = {})``` method also allows you to provide options for managing inherited attributes.
291
+
292
+ If you don't want to inherit the ```email``` attribute define it using the ```exclude``` option.
293
+
294
+ ```ruby
295
+ class UserSignupForm
296
+ include Soulless.model
297
+
298
+ inherit_from(User, exclude: :email)
299
+ end
300
+
301
+ UserSignupForm.attributes # => email will not be inherited
302
+ form = UserSignupForm.new
303
+ form.valid? # => false
304
+ form.errors.messages # => { name: ["can't be blank"] }
305
+ ```
306
+
307
+ You can also flip it around if you only want the ```name``` attribute by using the ```only``` option.
308
+
309
+ ```ruby
310
+ class UserSignupForm
311
+ include Soulless.model
312
+
313
+ inherit_from(User, only: :name)
314
+ end
315
+
316
+ UserSignupForm.attributes # => email will not be inherited
317
+ form = UserSignupForm.new
318
+ form.valid? # => false
319
+ form.errors.messages # => { name: ["can't be blank"] }
320
+ ```
321
+
322
+ #### Available Options
323
+
324
+ ```only``` - Only inherit the attributes and validations for the provided attributes. Any attributes not specified will be ignored. Accepts strings, symbols and an array of strings or symbols.
325
+
326
+ ```exclude``` - Don't inherit the attributes and validations for the provided attributes. Accepts strings, symbols and an array of strings or symbols.
327
+
328
+ ```skip_validators``` - Only inherit attributes. Don't inherit any validators. Accepts a boolean.
329
+
330
+ ```use_database_default``` - Use the value of the ```default``` migration option as the default value for an attribute. Accepts either a boolean (for all attributes), a string or symbol for a single attribute or an array of strings and symbols for multiple attributes.
331
+
256
332
  ### I18n
257
333
 
258
334
  Define locales similar to how you would define them in Rails.
@@ -0,0 +1,111 @@
1
+ module Soulless
2
+ module Inheritance
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ def inherit_from(klass, options = {})
6
+ attributes = get_attributes(klass, options)
7
+
8
+ attributes.each do |attribute|
9
+ self.attribute(attribute[:name], attribute[:primitive], attribute[:options])
10
+ if Object.const_defined?('ActiveModel'.to_sym) && klass.ancestors.include?(ActiveModel::Validations)
11
+ setup_validators(attribute[:name], klass, options)
12
+ end
13
+ end
14
+ end
15
+
16
+ private
17
+ def get_attributes(klass, options)
18
+ if klass.ancestors.include?(Virtus::Model::Core)
19
+ get_virtus_attributes(klass, options)
20
+ elsif Object.const_defined?('ActiveRecord'.to_sym) && klass.ancestors.include?(ActiveRecord::Base)
21
+ get_active_record_attributes(klass, options)
22
+ end
23
+ end
24
+
25
+ def get_virtus_attributes(klass, options)
26
+ attributes = []
27
+ attribute_set = klass.attribute_set
28
+ attribute_set.each do |attribute|
29
+ attribute_name = attribute.options[:name].to_s
30
+ if include_attribute?(attribute_name, options)
31
+ attributes << {
32
+ name: attribute_name,
33
+ primitive: attribute.primitive,
34
+ options: {
35
+ default: attribute.default_value
36
+ }
37
+ }
38
+ end
39
+ end
40
+ attributes
41
+ end
42
+
43
+ def get_active_record_attributes(klass, options)
44
+ attributes = []
45
+ columns = klass.columns
46
+ columns.each do |column|
47
+ if include_attribute?(column.name, options)
48
+ attribute = {
49
+ name: column.name,
50
+ primitive: translate_primitive(column.type),
51
+ options: {}
52
+ }
53
+ attribute[:options] = { default: column.default } if options[:use_database_default] == true ||
54
+ options[:use_database_default] == column.name ||
55
+ (options[:use_database_default].kind_of?(Array) &&
56
+ options[:use_database_default].collect { |v| v.to_s }.include?(column.name))
57
+ attributes << attribute
58
+ end
59
+ end
60
+ attributes
61
+ end
62
+
63
+ def setup_validators(attribute_name, klass, options)
64
+ return if options[:skip_validators] || !include_attribute?(attribute_name, options)
65
+ klass.validators.each do |validator|
66
+ if validator.attributes.include?(attribute_name.to_sym)
67
+ validator_class = validator.class
68
+ validator_options = {}
69
+ validator_options.merge!(validator.options)
70
+ if validator_class == ActiveRecord::Validations::UniquenessValidator
71
+ validator_class = Validations::UniquenessValidator
72
+ validator_options.merge!(model: klass)
73
+ else
74
+ # Validators from models are ActiveRecord
75
+ # objects which aren't useable in an
76
+ # environment without a database. It's
77
+ # necessary to convert to an ActiveModel
78
+ # validation.
79
+ validator_class = "ActiveModel::Validations::#{validator_class.name.split('::').last}".constantize
80
+ end
81
+ validates_with(validator_class, { attributes: attribute_name }.merge(validator_options))
82
+ end
83
+ end
84
+ end
85
+
86
+ def translate_primitive(primitive)
87
+ translated_primitive = primitive.to_s.capitalize
88
+ translated_primitive = Virtus::Attribute::Boolean.name if translated_primitive == 'Boolean'
89
+ translated_primitive = DateTime.name if translated_primitive == 'Datetime'
90
+ translated_primitive.constantize
91
+ end
92
+
93
+ def include_attribute?(attribute_name, options)
94
+ # Attributes we don't want to inherit
95
+ exclude_attributes = ['id']
96
+ exclude_attributes << ['created_at', 'updated_at'] unless options[:include_timestamps]
97
+ exclude_attributes << options[:exclude] if options[:exclude]
98
+ exclude_attributes.flatten!
99
+ exclude_attributes.collect!{ |v| v.to_s }
100
+ # Attributes we only want to inherit
101
+ only_attributes = []
102
+ only_attributes << options[:only] if options[:only]
103
+ only_attributes.flatten!
104
+ only_attributes.collect!{ |v| v.to_s }
105
+
106
+ !exclude_attributes.include?(attribute_name) && (only_attributes.empty? || only_attributes.include?(attribute_name))
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,3 +1,3 @@
1
1
  module Soulless
2
- VERSION = "0.4.3"
2
+ VERSION = "0.5.0.rc1"
3
3
  end
data/lib/soulless.rb CHANGED
@@ -6,6 +6,7 @@ require 'active_model'
6
6
  require 'soulless/accessors'
7
7
  require 'soulless/associations'
8
8
  require 'soulless/dirty'
9
+ require 'soulless/inheritance'
9
10
  require 'soulless/model'
10
11
  require 'soulless/validations'
11
12
  require 'soulless/version'
@@ -22,6 +23,7 @@ module Soulless
22
23
  object.send(:include, Validations)
23
24
  object.send(:include, Dirty)
24
25
  object.send(:include, Accessors)
26
+ object.send(:include, Inheritance)
25
27
  end
26
28
  mod
27
29
  end
@@ -1,3 +1,6 @@
1
1
  class DummyModel < ActiveRecord::Base
2
- validates :name, uniqueness: true
3
- end
2
+ validates :name, presence: true,
3
+ uniqueness: true
4
+
5
+ validates :email, length: { minimum: 3, allow_blank: true }
6
+ end
@@ -1,7 +1,9 @@
1
1
  class CreateDummyModels < ActiveRecord::Migration
2
2
  def change
3
3
  create_table :dummy_models do |t|
4
- t.string :name
4
+ t.string :name, default: 'Anthony'
5
+ t.string :email
6
+ t.boolean :saved, default: false
5
7
 
6
8
  t.timestamps
7
9
  end
@@ -14,7 +14,9 @@
14
14
  ActiveRecord::Schema.define(version: 20131106221200) do
15
15
 
16
16
  create_table "dummy_models", force: true do |t|
17
- t.string "name"
17
+ t.string "name", default: "Anthony"
18
+ t.string "email"
19
+ t.boolean "saved", default: false
18
20
  t.datetime "created_at"
19
21
  t.datetime "updated_at"
20
22
  end
Binary file
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Soulless::Inheritance do
4
+ describe DummyModelInheritance do
5
+ before(:each) do
6
+ @dummy_inheritance = DummyModelInheritance.new
7
+ end
8
+
9
+ it 'should inherit the name attribute' do
10
+ @dummy_inheritance.attributes.keys.should include(:name)
11
+ end
12
+
13
+ it 'should inherit the default value for the name attribute' do
14
+ @dummy_inheritance.attributes[:name].should == 'Anthony'
15
+ end
16
+
17
+ it 'should not inhert the email attribute when added as exclude option' do
18
+ @dummy_inheritance.attributes.keys.should_not include(:email)
19
+ end
20
+
21
+ it 'should validate presence of name' do
22
+ @dummy_inheritance.name = nil
23
+ @dummy_inheritance.valid?
24
+ @dummy_inheritance.errors[:name].should include("can't be blank")
25
+ end
26
+
27
+ it 'should not validate email' do
28
+ @dummy_inheritance.class.validators.each do |validator|
29
+ validator.attributes.should_not include(:email)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe DummySoullessInheritance do
35
+ before(:each) do
36
+ @dummy_inheritance = DummySoullessInheritance.new
37
+ end
38
+
39
+ it 'should inherit the name attribute' do
40
+ @dummy_inheritance.attributes.keys.should include(:name)
41
+ end
42
+
43
+ it 'should inherit the default value for the name attribute' do
44
+ @dummy_inheritance.attributes[:name].should == 'Anthony'
45
+ end
46
+
47
+ it 'should not inhert the email attribute when added as exclude option' do
48
+ @dummy_inheritance.attributes.keys.should_not include(:email)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ class DummyModelInheritance
2
+ include Soulless.model
3
+
4
+ inherit_from(DummyModel, { use_database_default: [:name], exclude: :email, include_timestamps: true })
5
+
6
+ private
7
+ def persist!
8
+ self.saved = true
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class DummySoullessInheritance
2
+ include Soulless.model
3
+
4
+ inherit_from(::DummyClass, { exclude: :email })
5
+
6
+ private
7
+ def persist!
8
+ self.saved = true
9
+ end
10
+ end
metadata CHANGED
@@ -1,111 +1,126 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soulless
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.5.0.rc1
5
+ prerelease: 6
5
6
  platform: ruby
6
7
  authors:
7
8
  - Anthony Smith
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-11-14 00:00:00.000000000 Z
12
+ date: 2014-02-12 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: activesupport
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - '>='
19
+ - - ! '>='
18
20
  - !ruby/object:Gem::Version
19
21
  version: '3.2'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
- - - '>='
27
+ - - ! '>='
25
28
  - !ruby/object:Gem::Version
26
29
  version: '3.2'
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: activemodel
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
- - - '>='
35
+ - - ! '>='
32
36
  - !ruby/object:Gem::Version
33
37
  version: '3.2'
34
38
  type: :runtime
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
- - - '>='
43
+ - - ! '>='
39
44
  - !ruby/object:Gem::Version
40
45
  version: '3.2'
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: virtus
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
- - - '>='
51
+ - - ! '>='
46
52
  - !ruby/object:Gem::Version
47
53
  version: '1.0'
48
54
  type: :runtime
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
- - - '>='
59
+ - - ! '>='
53
60
  - !ruby/object:Gem::Version
54
61
  version: '1.0'
55
62
  - !ruby/object:Gem::Dependency
56
63
  name: bundler
57
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
58
66
  requirements:
59
- - - '>='
67
+ - - ! '>='
60
68
  - !ruby/object:Gem::Version
61
69
  version: '0'
62
70
  type: :development
63
71
  prerelease: false
64
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
- - - '>='
75
+ - - ! '>='
67
76
  - !ruby/object:Gem::Version
68
77
  version: '0'
69
78
  - !ruby/object:Gem::Dependency
70
79
  name: database_cleaner
71
80
  requirement: !ruby/object:Gem::Requirement
81
+ none: false
72
82
  requirements:
73
- - - '>='
83
+ - - ! '>='
74
84
  - !ruby/object:Gem::Version
75
85
  version: '0'
76
86
  type: :development
77
87
  prerelease: false
78
88
  version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
79
90
  requirements:
80
- - - '>='
91
+ - - ! '>='
81
92
  - !ruby/object:Gem::Version
82
93
  version: '0'
83
94
  - !ruby/object:Gem::Dependency
84
95
  name: rake
85
96
  requirement: !ruby/object:Gem::Requirement
97
+ none: false
86
98
  requirements:
87
- - - '>='
99
+ - - ! '>='
88
100
  - !ruby/object:Gem::Version
89
101
  version: '0'
90
102
  type: :development
91
103
  prerelease: false
92
104
  version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
93
106
  requirements:
94
- - - '>='
107
+ - - ! '>='
95
108
  - !ruby/object:Gem::Version
96
109
  version: '0'
97
110
  - !ruby/object:Gem::Dependency
98
111
  name: rspec
99
112
  requirement: !ruby/object:Gem::Requirement
113
+ none: false
100
114
  requirements:
101
- - - '>='
115
+ - - ! '>='
102
116
  - !ruby/object:Gem::Version
103
117
  version: '0'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
107
122
  requirements:
108
- - - '>='
123
+ - - ! '>='
109
124
  - !ruby/object:Gem::Version
110
125
  version: '0'
111
126
  description: Rails models without a database (and Rails).
@@ -125,6 +140,7 @@ files:
125
140
  - lib/soulless/accessors.rb
126
141
  - lib/soulless/associations.rb
127
142
  - lib/soulless/dirty.rb
143
+ - lib/soulless/inheritance.rb
128
144
  - lib/soulless/locale/en.yml
129
145
  - lib/soulless/model.rb
130
146
  - lib/soulless/validations.rb
@@ -184,35 +200,39 @@ files:
184
200
  - spec/dummy/public/robots.txt
185
201
  - spec/dummy/vendor/assets/javascripts/.keep
186
202
  - spec/dummy/vendor/assets/stylesheets/.keep
203
+ - spec/inheritance_spec.rb
187
204
  - spec/soulless_spec.rb
188
205
  - spec/spec_helper.rb
189
206
  - spec/support/dummy_association.rb
190
207
  - spec/support/dummy_class.rb
208
+ - spec/support/dummy_model_inheritance.rb
209
+ - spec/support/dummy_soulless_inheritance.rb
191
210
  - spec/support/en.yml
192
211
  - spec/uniqueness_validator_spec.rb
193
212
  homepage: ''
194
213
  licenses:
195
214
  - MIT
196
- metadata: {}
197
215
  post_install_message:
198
216
  rdoc_options: []
199
217
  require_paths:
200
218
  - lib
201
219
  required_ruby_version: !ruby/object:Gem::Requirement
220
+ none: false
202
221
  requirements:
203
- - - '>='
222
+ - - ! '>='
204
223
  - !ruby/object:Gem::Version
205
224
  version: '0'
206
225
  required_rubygems_version: !ruby/object:Gem::Requirement
226
+ none: false
207
227
  requirements:
208
- - - '>='
228
+ - - ! '>'
209
229
  - !ruby/object:Gem::Version
210
- version: '0'
230
+ version: 1.3.1
211
231
  requirements: []
212
232
  rubyforge_project:
213
- rubygems_version: 2.0.3
233
+ rubygems_version: 1.8.23
214
234
  signing_key:
215
- specification_version: 4
235
+ specification_version: 3
216
236
  summary: Create Rails style models without the database.
217
237
  test_files:
218
238
  - spec/associated_validator_spec.rb
@@ -267,9 +287,12 @@ test_files:
267
287
  - spec/dummy/public/robots.txt
268
288
  - spec/dummy/vendor/assets/javascripts/.keep
269
289
  - spec/dummy/vendor/assets/stylesheets/.keep
290
+ - spec/inheritance_spec.rb
270
291
  - spec/soulless_spec.rb
271
292
  - spec/spec_helper.rb
272
293
  - spec/support/dummy_association.rb
273
294
  - spec/support/dummy_class.rb
295
+ - spec/support/dummy_model_inheritance.rb
296
+ - spec/support/dummy_soulless_inheritance.rb
274
297
  - spec/support/en.yml
275
298
  - spec/uniqueness_validator_spec.rb
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: bb87770d995077463f68f4e6b80da470b5897368
4
- data.tar.gz: 8c5b6ff12f1f3cd329fc1ecaa12af79f567a53e3
5
- SHA512:
6
- metadata.gz: 432822f228e7c4c5111e21c4c3fd6d89de3cd134f598c055054d0198d0856553a85bec2febd324a19715756dad5b7710facdd7694ddb4bd6be2cc71efbd7a821
7
- data.tar.gz: 747fd977b5017cf8761fe866b6178d558fa297bb56ef36056d6c04b89c6fea81af3fa5e54d7ddca80274a147fe2b207804c122f6df88fd0e56cc6682884be5d2