rrod 1.0.0.alpha.1 → 1.0.0.alpha.2
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.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +11 -6
- data/lib/rrod/all.rb +4 -0
- data/lib/rrod/caster/nested_model.rb +1 -1
- data/lib/rrod/configuration.rb +2 -0
- data/lib/rrod/model.rb +5 -3
- data/lib/rrod/model/attribute.rb +8 -3
- data/lib/rrod/model/attribute_methods.rb +8 -3
- data/lib/rrod/model/callbacks.rb +26 -0
- data/lib/rrod/model/collection.rb +22 -4
- data/lib/rrod/model/finders.rb +37 -26
- data/lib/rrod/model/persistence.rb +5 -4
- data/lib/rrod/model/schema.rb +9 -3
- data/lib/rrod/model/serialization.rb +9 -0
- data/lib/rrod/model/timestamps.rb +19 -0
- data/lib/rrod/model/validations.rb +15 -0
- data/lib/rrod/model/validations/associated_validator.rb +29 -0
- data/lib/rrod/query.rb +1 -1
- data/lib/rrod/test_server/rspec.rb +3 -2
- data/lib/rrod/version.rb +1 -1
- data/spec/rrod/model/attribute_methods_spec.rb +15 -4
- data/spec/rrod/model/attribute_spec.rb +15 -2
- data/spec/rrod/model/callbacks_spec.rb +60 -0
- data/spec/rrod/model/collection_spec.rb +14 -5
- data/spec/rrod/model/finders_spec.rb +11 -10
- data/spec/rrod/model/schema_spec.rb +19 -1
- data/spec/rrod/model/timestamps_spec.rb +30 -0
- data/spec/rrod/model/validations/associated_validator_spec.rb +46 -0
- data/spec/rrod/model/validations_spec.rb +43 -45
- data/spec/rrod/query_spec.rb +1 -1
- data/spec/support/models/car.rb +3 -0
- data/spec/support/models/person.rb +25 -1
- data/spec/support/models/player.rb +6 -0
- data/spec/support/models/team.rb +5 -0
- metadata +39 -25
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rrod
|
2
|
+
module Model
|
3
|
+
module Validations
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveModel::Validations
|
8
|
+
end
|
9
|
+
|
10
|
+
def save(options={})
|
11
|
+
options.fetch(:validate, true) ? (valid? and super()) : super()
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rrod
|
2
|
+
module Model
|
3
|
+
module Validations
|
4
|
+
|
5
|
+
class AssociatedValidator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
return if Array(value).collect{ |r| r.nil? || r.valid? }.all?
|
8
|
+
record.errors.add(attribute, error_message_for(attribute, value))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def error_message_for(attribute, associated_records)
|
14
|
+
Array(associated_records).map(&:errors).reject(&:blank?).map(&:full_messages).map(&:to_sentence).flatten.join('; ')
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
def validates_associated(*attrs)
|
22
|
+
validates_with AssociatedValidator, _merge_attributes(attrs)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/rrod/query.rb
CHANGED
@@ -18,7 +18,7 @@ module Rrod
|
|
18
18
|
fail "Test server not working: #{Rrod::TestServer.fatal}"
|
19
19
|
end
|
20
20
|
|
21
|
-
if
|
21
|
+
if ::RSpec.current_example.metadata[:test_server] == false
|
22
22
|
Rrod::TestServer.stop
|
23
23
|
else
|
24
24
|
Rrod::TestServer.create unless Rrod::TestServer.exist?
|
@@ -32,9 +32,10 @@ module Rrod
|
|
32
32
|
|
33
33
|
config.after(:each, :integration => true) do
|
34
34
|
# i really don't understand this...
|
35
|
+
# well the 'example' needs to be accessed another way, Rspec.current_example.
|
35
36
|
if !Rrod::TestServer.fatal &&
|
36
37
|
Rrod::TestServer.started? &&
|
37
|
-
|
38
|
+
::RSpec.current_example.metadata[:test_server] != false
|
38
39
|
Rrod::TestServer.drop
|
39
40
|
end
|
40
41
|
end
|
data/lib/rrod/version.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
Rrod = Module.new unless defined? Rrod
|
2
|
-
Rrod::VERSION = '1.0.0.alpha.
|
2
|
+
Rrod::VERSION = '1.0.0.alpha.2'
|
@@ -10,7 +10,7 @@ describe Rrod::Model do
|
|
10
10
|
describe "instantiation" do
|
11
11
|
it "can create an object with an arbitrary hash" do
|
12
12
|
expect(instance.wheels).to eq 4
|
13
|
-
expect(instance.color).to eq :black
|
13
|
+
# @TODO expect(instance.color).to eq :black
|
14
14
|
expect(instance.make).to eq 'Jeep'
|
15
15
|
end
|
16
16
|
|
@@ -20,7 +20,7 @@ describe Rrod::Model do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it "manages attribute keys as strings" do
|
23
|
-
expect(instance.attributes).to eq hash.stringify_keys
|
23
|
+
expect(instance.attributes.keys).to eq hash.stringify_keys.keys
|
24
24
|
end
|
25
25
|
|
26
26
|
it "ignores modifications to the attribute hash" do
|
@@ -57,8 +57,19 @@ describe Rrod::Model do
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "defaults" do
|
63
|
+
let(:model) { Class.new(Car) { attribute :wheels, Integer, default: 4 } }
|
64
|
+
let(:instance) { model.new }
|
65
|
+
|
66
|
+
it "will return the default when reading a nil value" do
|
67
|
+
expect(instance.wheels).to eq 4
|
68
|
+
end
|
69
|
+
|
70
|
+
it "will set the default to the read value" do
|
71
|
+
instance.wheels
|
72
|
+
expect(instance.instance_variable_get(:@attributes)['wheels']).to eq 4
|
62
73
|
end
|
63
74
|
end
|
64
75
|
end
|
@@ -30,10 +30,13 @@ describe Rrod::Model::Attribute do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
describe "defaults" do
|
33
|
+
let(:default) { attribute.default(instance) }
|
34
|
+
|
33
35
|
context "when a value" do
|
34
36
|
let(:options) { {default: 'SOO fluffy!'} }
|
37
|
+
|
35
38
|
it "can provide a default value" do
|
36
|
-
expect(
|
39
|
+
expect(default).to eq 'SOO fluffy!'
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
@@ -41,7 +44,17 @@ describe Rrod::Model::Attribute do
|
|
41
44
|
let(:options) { {default: -> { 'alligator' }} }
|
42
45
|
|
43
46
|
it "can provide a default value if a proc" do
|
44
|
-
expect(
|
47
|
+
expect(default).to eq 'alligator'
|
48
|
+
end
|
49
|
+
|
50
|
+
context "given an instance" do
|
51
|
+
let(:model) { Class.new { include Rrod::Model; attr_accessor :foo } }
|
52
|
+
let(:options) { {default: -> { foo.upcase }} }
|
53
|
+
|
54
|
+
it "evaluates in the context of the given instance" do
|
55
|
+
instance.foo = 'whoah'
|
56
|
+
expect(default).to eq('WHOAH')
|
57
|
+
end
|
45
58
|
end
|
46
59
|
end
|
47
60
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/models/person'
|
3
|
+
|
4
|
+
describe Rrod::Model::Callbacks do
|
5
|
+
let(:instance) { Person.new }
|
6
|
+
|
7
|
+
describe "assignment" do
|
8
|
+
it "provides callbacks" do
|
9
|
+
expect(instance).to receive(:poke)
|
10
|
+
instance.attributes = {name: 'Pooka'}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "validation" do
|
15
|
+
it "provides callbacks" do
|
16
|
+
expect(instance).to receive(:stuffs)
|
17
|
+
instance.valid?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "saving" do
|
22
|
+
it "provides callbacks" do
|
23
|
+
expect(instance).to receive(:other_stuffs)
|
24
|
+
instance.save
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "create" do
|
28
|
+
it "runs when new" do
|
29
|
+
expect(instance).to receive(:created!)
|
30
|
+
expect(instance).not_to receive(:updated!)
|
31
|
+
instance.save
|
32
|
+
end
|
33
|
+
|
34
|
+
it "does not run when persisted" do
|
35
|
+
instance.instance_variable_set :@persisted, true
|
36
|
+
expect(instance).not_to receive(:created!)
|
37
|
+
instance.save
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "update" do
|
42
|
+
before :each do
|
43
|
+
instance.instance_variable_set :@persisted, true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "runs when persisted" do
|
47
|
+
expect(instance).to receive(:updated!)
|
48
|
+
expect(instance).not_to receive(:created!)
|
49
|
+
instance.save
|
50
|
+
end
|
51
|
+
|
52
|
+
it "does not run when new" do
|
53
|
+
instance.instance_variable_set :@persisted, false
|
54
|
+
expect(instance).not_to receive(:updated!)
|
55
|
+
instance.save
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -3,7 +3,7 @@ require 'support/models/person'
|
|
3
3
|
|
4
4
|
describe Rrod::Model::Collection do
|
5
5
|
let(:array) { [Pet.new] }
|
6
|
-
let(:collection) { described_class.new(array) }
|
6
|
+
let(:collection) { described_class.new(Pet, array) }
|
7
7
|
|
8
8
|
describe "initialization" do
|
9
9
|
it "takes a collection" do
|
@@ -11,9 +11,18 @@ describe Rrod::Model::Collection do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it "defaults to an empty collection" do
|
14
|
-
expect(described_class.new.collection).to be_an Array
|
14
|
+
expect(described_class.new(Pet).collection).to be_an Array
|
15
15
|
end
|
16
16
|
|
17
|
+
describe "#build" do
|
18
|
+
it "adds to the collection" do
|
19
|
+
collection.build(name: 'Lion')
|
20
|
+
expect(collection.collection.length).to eq 2
|
21
|
+
expect(collection.collection.last.name).to eq('Lion')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
17
26
|
describe "errors" do
|
18
27
|
describe "non enumerable" do
|
19
28
|
let(:array) { Object.new }
|
@@ -22,14 +31,14 @@ describe Rrod::Model::Collection do
|
|
22
31
|
end
|
23
32
|
end
|
24
33
|
|
25
|
-
describe "not all Rrod::Model" do
|
26
|
-
let(:array) { [Pet.new,
|
34
|
+
describe "not all same Rrod::Model" do
|
35
|
+
let(:array) { [Pet.new, Address.new] }
|
27
36
|
it "raises if not all `Rrod::Model`s" do
|
28
37
|
expect { collection }.to raise_error(Rrod::Model::Collection::InvalidMemberTypeError)
|
29
38
|
end
|
30
39
|
|
31
40
|
it "clears the collection" do
|
32
|
-
collection = described_class.new
|
41
|
+
collection = described_class.new(Pet)
|
33
42
|
begin; collection.collection = array; rescue; end
|
34
43
|
expect(collection.collection).to be_empty
|
35
44
|
end
|
@@ -31,29 +31,29 @@ describe Rrod::Model::Finders, integration: true do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
describe "finding by attributes in the hash" do
|
34
|
-
describe "
|
34
|
+
describe "find_by" do
|
35
35
|
it "can find one" do
|
36
|
-
found = model.
|
36
|
+
found = model.find_by(make: 'Jeep')
|
37
37
|
expect(found).to be_a model
|
38
38
|
expect(found.make).to eq "Jeep"
|
39
39
|
end
|
40
40
|
|
41
41
|
it "will work properly when finding by id" do
|
42
|
-
found = model.
|
42
|
+
found = model.find_by(id: instance.id)
|
43
43
|
expect(found).to be_a model
|
44
44
|
end
|
45
45
|
|
46
46
|
it "will return nil if one can't be found" do
|
47
|
-
found = model.
|
47
|
+
found = model.find_by(id: "id that is not there")
|
48
48
|
expect(found).to be nil
|
49
49
|
end
|
50
50
|
|
51
51
|
it "will raise an exception if one can't be found with a !" do
|
52
|
-
expect { model.
|
52
|
+
expect { model.find_by! zombies: true }.to raise_error(ArgumentError)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
describe "
|
56
|
+
describe "search" do
|
57
57
|
it "can find all" do
|
58
58
|
founds = model.find_all_by(make: 'Jeep', wheels: 4)
|
59
59
|
found = founds.first
|
@@ -62,17 +62,18 @@ describe Rrod::Model::Finders, integration: true do
|
|
62
62
|
expect(found.make).to eq "Jeep"
|
63
63
|
end
|
64
64
|
|
65
|
-
it "will raise an exception if
|
65
|
+
it "will raise an exception if searching by id" do
|
66
66
|
expect {model.find_all_by(id: instance.id)}.to raise_error(ArgumentError)
|
67
67
|
end
|
68
68
|
|
69
|
-
it "will return
|
70
|
-
expect
|
69
|
+
it "will return an exeception if none can be found" do
|
70
|
+
expect { model.find_all_by zombies: 'yes plz'}.to raise_error(ArgumentError)
|
71
71
|
end
|
72
72
|
|
73
73
|
it "will raise an exception if none can be found with a !" do
|
74
|
-
expect { model.find_all_by! brains: :none }.to raise_error(
|
74
|
+
expect { model.find_all_by! brains: :none }.to raise_error(ArgumentError)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
78
|
+
|
78
79
|
end
|
@@ -20,7 +20,7 @@ describe Rrod::Model::Schema do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it "is using a schema if an attribute is declared" do
|
23
|
-
expect(Person.schema?).to
|
23
|
+
expect(Person.schema?).to be_truthy
|
24
24
|
end
|
25
25
|
|
26
26
|
describe "associations", integration: true do
|
@@ -54,6 +54,24 @@ describe Rrod::Model::Schema do
|
|
54
54
|
expect(hash['address']).to eq 'street' => '123 Fancy Pants Lane'
|
55
55
|
end
|
56
56
|
|
57
|
+
it "will set the parent on a nested model when assigned" do
|
58
|
+
instance.address = Address.new
|
59
|
+
expect(instance.address._parent).to eq(instance)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "will alias the parent with method given to nested_in" do
|
63
|
+
instance.pets = [Pet.new]
|
64
|
+
expect(instance.pets.first.owner).to eq(instance)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
it "raises an UncastableObjectError if object is not castable" do
|
70
|
+
expect { Pet.rrod_cast(Object.new) }.to raise_error(Rrod::Model::UncastableObjectError)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "will not add ids to models instantiated via `rrod_cast`" do
|
74
|
+
expect(Pet.rrod_cast(name: 'Molle').attributes).to_not have_key('id')
|
57
75
|
end
|
58
76
|
|
59
77
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rrod::Model::Timestamps do
|
4
|
+
let(:klass) { Class.new {include Rrod::Model; timestamps!} }
|
5
|
+
let(:instance) { klass.new }
|
6
|
+
let(:attribute) { Rrod::Model::Attribute }
|
7
|
+
let(:now) { Time.now }
|
8
|
+
|
9
|
+
it "provides a created_at attribute" do
|
10
|
+
expect(klass.attributes[:created_at]).to be_an attribute
|
11
|
+
end
|
12
|
+
|
13
|
+
it "provides a updated_at attribute" do
|
14
|
+
expect(klass.attributes[:updated_at]).to be_an attribute
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "Time.now" do
|
18
|
+
before(:each) { Time.stub(:now).and_return(now) }
|
19
|
+
|
20
|
+
it "sets created_at upon instantiation" do
|
21
|
+
expect(instance.created_at).to eq now
|
22
|
+
end
|
23
|
+
|
24
|
+
it "sets updated_at when the object is updated" do
|
25
|
+
instance.stub(:persist).and_return(true)
|
26
|
+
expect(instance).to receive(:updated_at=).with(now)
|
27
|
+
instance.save
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/models/player'
|
3
|
+
require 'support/models/team'
|
4
|
+
|
5
|
+
describe Rrod::Model::Validations::AssociatedValidator do
|
6
|
+
context 'for a many association' do
|
7
|
+
# this is the same as a one association..
|
8
|
+
let(:team) { Team.new }
|
9
|
+
let(:ichiro) { Player.new(name: 'Ichiro', position: 'RF') }
|
10
|
+
let(:arod) { Player.new(position: '3B') }
|
11
|
+
let(:joeschmo) { Player.new }
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
team.players = [ichiro, arod, joeschmo]
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'is invalid when the associated records are invalid' do
|
18
|
+
expect(arod.valid?).to be false
|
19
|
+
expect(joeschmo.valid?).to be false
|
20
|
+
expect(team.valid?).to be false
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'includes the associated records validation error messages in the error message' do
|
24
|
+
team.valid?
|
25
|
+
expect(team.errors[:players].size).to be 1
|
26
|
+
# maybe change this to better error messages
|
27
|
+
error_message = team.errors[:players].first
|
28
|
+
expect(error_message).to eq "Name can't be blank; Name can't be blank and Position can't be blank"
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it 'is valid when the associated records are valid' do
|
33
|
+
expect(ichiro.valid?).to be true
|
34
|
+
|
35
|
+
arod.name = 'Alex Rodriguez'
|
36
|
+
expect(arod.valid?).to be true
|
37
|
+
|
38
|
+
joeschmo.name = 'Joe Schmo'
|
39
|
+
joeschmo.position = 'C'
|
40
|
+
expect(joeschmo.valid?).to be true
|
41
|
+
|
42
|
+
expect(team.valid?).to be true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -1,50 +1,48 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'support/models/person'
|
3
3
|
|
4
|
-
describe Rrod::Model do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
expect(instance.save(validate: false)).to be_true
|
48
|
-
end
|
4
|
+
describe Rrod::Model::Validations do
|
5
|
+
let(:klass) { Person }
|
6
|
+
let(:validators) { klass.validators }
|
7
|
+
let(:instance) { klass.new }
|
8
|
+
|
9
|
+
it "adds validations as class methods" do
|
10
|
+
expect(validators.any? { |v| ActiveModel::Validations::LengthValidator === v }).to be_truthy
|
11
|
+
end
|
12
|
+
|
13
|
+
it "allows describing validations in the attribute" do
|
14
|
+
expect(validators.any? { |v| ActiveModel::Validations::PresenceValidator === v }).to be_truthy
|
15
|
+
end
|
16
|
+
|
17
|
+
it "raises a no method error if no validation exists" do
|
18
|
+
expect {
|
19
|
+
Class.new {
|
20
|
+
include Rrod::Model
|
21
|
+
attribute :kittens, Integer, bunnies: true
|
22
|
+
}
|
23
|
+
}.to raise_error(ArgumentError, /BunniesValidator/)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
it "can determine an instances validity" do
|
28
|
+
expect(instance).not_to be_valid
|
29
|
+
end
|
30
|
+
|
31
|
+
it "will not save if invalid" do
|
32
|
+
expect(instance).not_to receive(:persist)
|
33
|
+
instance.save
|
34
|
+
end
|
35
|
+
|
36
|
+
it "will return false from saving if invalid" do
|
37
|
+
expect(instance.save).to be_falsey
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sets up errors properlies" do
|
41
|
+
instance.valid?
|
42
|
+
expect(instance.errors).to be_present
|
43
|
+
end
|
44
|
+
|
45
|
+
it "will allow saving when not validating" do
|
46
|
+
expect(instance.save(validate: false)).to be_truthy
|
49
47
|
end
|
50
48
|
end
|