soulless 0.4.3 → 0.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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