rrod 1.0.0.alpha.7 → 1.0.0.alpha.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rrod/model/attribute.rb +5 -0
- data/lib/rrod/model/attribute_methods.rb +28 -8
- data/lib/rrod/model/collection.rb +6 -1
- data/lib/rrod/model/finders.rb +3 -1
- data/lib/rrod/model/persistence.rb +2 -3
- data/lib/rrod/model/schema.rb +9 -4
- data/lib/rrod/model/serialization.rb +1 -1
- data/lib/rrod/model.rb +14 -2
- data/lib/rrod/version.rb +1 -1
- data/spec/rrod/model/attribute_methods_spec.rb +42 -8
- data/spec/rrod/model/attribute_spec.rb +7 -0
- data/spec/rrod/model/finders_spec.rb +18 -7
- data/spec/rrod/model/persistence_spec.rb +8 -0
- data/spec/rrod/model/schema_spec.rb +11 -5
- data/spec/rrod/model/serialization_spec.rb +6 -1
- data/spec/rrod/model_spec.rb +11 -0
- data/spec/support/models/car.rb +19 -0
- data/spec/support/models/person.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21bcd59f68557a964682ed550e6f066b4b62d8c7
|
4
|
+
data.tar.gz: cb7eb976f82092d7013d48418a2de2dbcb1d4a7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ebced7c3a04e6efe2a2b31bae0ee25eadebad2f2120dae03cc6ff646f98f4c7ca4e1a575398ae5a8655d4d69b79757121b4d9b2d3f473386602f414b087e2101
|
7
|
+
data.tar.gz: 084cdd4c9358b4c0ef399667bb5126b59594cec7f806490911865b1ecc37ebd16fb81feedcaeba8509f50821b4e244d1240cc65554fd6eda3aefd9531c451cec
|
data/lib/rrod/model/attribute.rb
CHANGED
@@ -30,6 +30,7 @@ module Rrod
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def define
|
33
|
+
define_attribute_method
|
33
34
|
define_attribute
|
34
35
|
define_reader
|
35
36
|
define_writer
|
@@ -65,6 +66,10 @@ module Rrod
|
|
65
66
|
-> { Rrod::Model::Collection.new(self, model_class) }
|
66
67
|
end
|
67
68
|
|
69
|
+
def define_attribute_method
|
70
|
+
model.define_attribute_method name
|
71
|
+
end
|
72
|
+
|
68
73
|
def define_attribute
|
69
74
|
_self = self # i am a shadow of self
|
70
75
|
model.define_singleton_method(
|
@@ -2,29 +2,40 @@ module Rrod
|
|
2
2
|
module Model
|
3
3
|
module AttributeMethods
|
4
4
|
extend ActiveSupport::Concern
|
5
|
+
include ActiveModel::Dirty
|
5
6
|
|
6
7
|
attr_accessor :_parent
|
7
8
|
|
9
|
+
included do
|
10
|
+
define_attribute_method :id
|
11
|
+
end
|
12
|
+
|
8
13
|
def initialize(attributes = {})
|
9
14
|
@attributes = {}
|
10
15
|
self.magic_methods = attributes.keys
|
11
16
|
self.attributes = attributes
|
17
|
+
changes_applied
|
12
18
|
end
|
13
19
|
|
14
20
|
def id
|
15
|
-
|
21
|
+
robject.key
|
16
22
|
end
|
17
23
|
|
18
24
|
def id=(value)
|
19
|
-
|
25
|
+
id_will_change! unless id == value
|
26
|
+
robject.key = value
|
20
27
|
end
|
21
28
|
|
22
29
|
# Returns a new hash with all of the object's attributes.
|
23
30
|
# @return [Hash] the object's attributes
|
24
31
|
def attributes
|
25
|
-
|
26
|
-
|
27
|
-
|
32
|
+
self.class.attributes.keys.inject({}) { |acc, key|
|
33
|
+
# @attributes.keys.inject({}) { |acc, key|
|
34
|
+
acc.tap { |hash|
|
35
|
+
next hash unless self.class.attributes.keys.include?(key.to_s)
|
36
|
+
hash[key] = public_send(key)
|
37
|
+
}
|
38
|
+
}.tap { |hash| hash['id'] = id unless nested_model? }
|
28
39
|
end
|
29
40
|
|
30
41
|
# Mass assign the attributes of the object.
|
@@ -39,7 +50,7 @@ module Rrod
|
|
39
50
|
# @param [Symbol, String] the key of the attribute to read
|
40
51
|
# @return the attribute at the given key
|
41
52
|
def read_attribute(key)
|
42
|
-
@attributes[key.to_s] ||
|
53
|
+
@attributes[key.to_s] || read_default(key)
|
43
54
|
end
|
44
55
|
alias :[] :read_attribute
|
45
56
|
|
@@ -47,13 +58,22 @@ module Rrod
|
|
47
58
|
# @param [Symbol, String] the key of the attribute to write
|
48
59
|
# @param the value to write to attributes
|
49
60
|
def write_attribute(key, value)
|
50
|
-
|
61
|
+
public_send("#{key}_will_change!") unless read_attribute(key) == value
|
62
|
+
cast_attribute(key, value)
|
51
63
|
end
|
52
64
|
alias :[]= :write_attribute
|
53
65
|
|
66
|
+
def cast_attribute(key, value)
|
67
|
+
@attributes[key.to_s] = self.class.cast_attribute(key, value, self)
|
68
|
+
end
|
69
|
+
|
54
70
|
def read_default(key)
|
55
71
|
method = "#{key}_default"
|
56
|
-
|
72
|
+
cast_attribute(key, public_send(method)) if respond_to?(method)
|
73
|
+
end
|
74
|
+
|
75
|
+
def nested_model?
|
76
|
+
_parent.present?
|
57
77
|
end
|
58
78
|
|
59
79
|
private
|
@@ -20,7 +20,8 @@ module Rrod
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def collection=(collection)
|
23
|
-
|
23
|
+
message = "#{collection.inspect} does not respond to :each"
|
24
|
+
raise InvalidCollectionTypeError.new(message) unless collection.respond_to?(:each)
|
24
25
|
collection.map { |member| push member }
|
25
26
|
rescue InvalidMemberTypeError => e
|
26
27
|
clear and raise e
|
@@ -49,6 +50,10 @@ module Rrod
|
|
49
50
|
collection.all?(&:valid?)
|
50
51
|
end
|
51
52
|
|
53
|
+
def inspect
|
54
|
+
%Q[[#{map(&:inspect).join(', ')}]]
|
55
|
+
end
|
56
|
+
|
52
57
|
InvalidCollectionTypeError = Class.new(StandardError)
|
53
58
|
InvalidMemberTypeError = Class.new(StandardError)
|
54
59
|
end
|
data/lib/rrod/model/finders.rb
CHANGED
@@ -59,8 +59,10 @@ module Rrod
|
|
59
59
|
}
|
60
60
|
end
|
61
61
|
|
62
|
+
# when searching documents do not come back with robjects,
|
63
|
+
# we must set the key manually on it via `id=`
|
62
64
|
def instantiate(key, data)
|
63
|
-
new
|
65
|
+
new data.merge(id: key)
|
64
66
|
end
|
65
67
|
|
66
68
|
end
|
@@ -15,7 +15,7 @@ module Rrod
|
|
15
15
|
alias :new_record? :new?
|
16
16
|
|
17
17
|
def save
|
18
|
-
persist
|
18
|
+
persist.tap { changes_applied }
|
19
19
|
end
|
20
20
|
|
21
21
|
def update(attributes)
|
@@ -26,8 +26,7 @@ module Rrod
|
|
26
26
|
|
27
27
|
def persist
|
28
28
|
bucket.enable_index!
|
29
|
-
robject.raw_data = to_json
|
30
|
-
robject.key = id unless id.nil?
|
29
|
+
robject.raw_data = to_json(except: :id)
|
31
30
|
robject.store
|
32
31
|
self.id = robject.key
|
33
32
|
@persisted = true
|
data/lib/rrod/model/schema.rb
CHANGED
@@ -3,12 +3,13 @@ module Rrod
|
|
3
3
|
module Schema
|
4
4
|
|
5
5
|
RROD_ATTRIBUTE_PREFIX = "rrod_attribute_".freeze
|
6
|
+
ANONYMOUS = "anonymous".freeze
|
6
7
|
|
7
8
|
def attributes
|
8
9
|
@attributes ||= public_methods.map(&:to_s).reduce({}) { |acc, method|
|
9
10
|
next acc unless method.starts_with?(RROD_ATTRIBUTE_PREFIX)
|
10
11
|
key = method[RROD_ATTRIBUTE_PREFIX.length..-1]
|
11
|
-
acc.tap { |hash| hash[key] =
|
12
|
+
acc.tap { |hash| hash[key] = public_send(method) }
|
12
13
|
}.with_indifferent_access
|
13
14
|
end
|
14
15
|
|
@@ -18,8 +19,12 @@ module Rrod
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def cast_attribute(key, value, instance)
|
21
|
-
|
22
|
-
|
22
|
+
method = "#{RROD_ATTRIBUTE_PREFIX}#{key}"
|
23
|
+
if respond_to?(method)
|
24
|
+
public_send(method).cast(value, instance)
|
25
|
+
else
|
26
|
+
value
|
27
|
+
end
|
23
28
|
end
|
24
29
|
|
25
30
|
def nested_in(parent)
|
@@ -42,7 +47,7 @@ module Rrod
|
|
42
47
|
when Rrod::Model
|
43
48
|
value
|
44
49
|
when Hash
|
45
|
-
|
50
|
+
new(value)
|
46
51
|
else
|
47
52
|
raise UncastableObjectError.new("#{value.inspect} cannot be rrod_cast") unless Hash === value
|
48
53
|
end
|
data/lib/rrod/model.rb
CHANGED
@@ -23,9 +23,8 @@ module Rrod
|
|
23
23
|
@bucket ||= client[bucket_name]
|
24
24
|
end
|
25
25
|
|
26
|
-
# TODO make class attribute
|
27
26
|
def bucket_name
|
28
|
-
name.tableize
|
27
|
+
(name.presence || Rrod::Model::Schema::ANONYMOUS).tableize
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
@@ -36,5 +35,18 @@ module Rrod
|
|
36
35
|
def bucket
|
37
36
|
self.class.bucket
|
38
37
|
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
%Q[#<#{inspect_name} attributes: #{inspect_attributes} object_id: #{object_id}>]
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect_name
|
44
|
+
storage_name = nested_model? ? "(nested)" : "[#{bucket.name}]"
|
45
|
+
"#{self.class.name || self.class.to_s}#{storage_name}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect_attributes
|
49
|
+
%Q[{#{attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')}}]
|
50
|
+
end
|
39
51
|
end
|
40
52
|
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.8'
|
@@ -14,13 +14,36 @@ describe Rrod::Model do
|
|
14
14
|
expect(instance.make).to eq 'Jeep'
|
15
15
|
end
|
16
16
|
|
17
|
+
it "is not dirty when created" do
|
18
|
+
expect(instance).not_to be_changed
|
19
|
+
end
|
20
|
+
|
17
21
|
it "always has an id property" do
|
18
22
|
expect(instance).to respond_to :id
|
19
23
|
expect(instance.id).to be_nil
|
20
24
|
end
|
21
25
|
|
26
|
+
it "includes the id property in the attributes hash" do
|
27
|
+
instance.save
|
28
|
+
expect(instance.attributes.keys).to include('id')
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "nested models" do
|
32
|
+
let(:model) { BatMobile }
|
33
|
+
let(:hash) { {weapons: [{type: 'Rocket', damage: 12_345}]} }
|
34
|
+
let(:nested) { instance.weapons.first }
|
35
|
+
|
36
|
+
it "has a nil id" do
|
37
|
+
expect(nested.id).to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "excludes id from attributes" do
|
41
|
+
expect(nested.attributes.keys).not_to include('id')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
22
45
|
it "manages attribute keys as strings" do
|
23
|
-
expect(instance.attributes.keys).to eq hash.stringify_keys.keys
|
46
|
+
expect(instance.attributes.keys.sort).to eq hash.stringify_keys.keys.push('id').sort
|
24
47
|
end
|
25
48
|
|
26
49
|
it "ignores modifications to the attribute hash" do
|
@@ -28,12 +51,6 @@ describe Rrod::Model do
|
|
28
51
|
expect(instance.attributes[:model]).to be_nil
|
29
52
|
end
|
30
53
|
|
31
|
-
it "will return nil for an attribute that exists in the hash but does not have a corresponding method" do
|
32
|
-
instance.instance_variable_get(:@attributes)['foo'] = 'bar'
|
33
|
-
expect(instance).not_to respond_to(:foo)
|
34
|
-
expect(instance.attributes).to include('foo' => nil)
|
35
|
-
end
|
36
|
-
|
37
54
|
describe "mass assignment" do
|
38
55
|
it "will merge attributes when mass assigning" do
|
39
56
|
instance.attributes = {wheels: 5}
|
@@ -46,7 +63,9 @@ describe Rrod::Model do
|
|
46
63
|
end
|
47
64
|
|
48
65
|
describe "with schema" do
|
49
|
-
let(:model) { Class.new(Car) { attribute :wheels, Integer } }
|
66
|
+
# let(:model) { Class.new(Car) { attribute :wheels, Integer } }
|
67
|
+
# let(:model) { Car }
|
68
|
+
let(:model) { Class.new(Car) }
|
50
69
|
|
51
70
|
it "does not allow creating with arbitrary attributes" do
|
52
71
|
expect { model.new(model: 'Jeep') }.to raise_error(NoMethodError, /model=/)
|
@@ -72,4 +91,19 @@ describe Rrod::Model do
|
|
72
91
|
expect(instance.instance_variable_get(:@attributes)['wheels']).to eq 4
|
73
92
|
end
|
74
93
|
end
|
94
|
+
|
95
|
+
describe "dirty tracking" do
|
96
|
+
let(:model) { Class.new(Car) { attribute :wheels, Integer, default: 4 } }
|
97
|
+
let(:instance) { model.new }
|
98
|
+
|
99
|
+
it "will track if an attribute is changing" do
|
100
|
+
instance.wheels = 5
|
101
|
+
expect(instance.wheels_changed?).to be true
|
102
|
+
end
|
103
|
+
|
104
|
+
it "will not track if an attribute is not changing" do
|
105
|
+
instance.wheels = 4
|
106
|
+
expect(instance.wheels_changed?).to be false
|
107
|
+
end
|
108
|
+
end
|
75
109
|
end
|
@@ -65,6 +65,13 @@ describe Rrod::Model::Attribute do
|
|
65
65
|
expect(model.send("rrod_attribute_#{name}")).to eq attribute
|
66
66
|
end
|
67
67
|
end
|
68
|
+
|
69
|
+
describe "attribute methods" do
|
70
|
+
it "declares the attribute an ActiveModel attribute method" do
|
71
|
+
expect(model).to receive(:define_attribute_method).with(name)
|
72
|
+
attribute.define
|
73
|
+
end
|
74
|
+
end
|
68
75
|
end
|
69
76
|
|
70
77
|
describe "casting" do
|
@@ -16,18 +16,29 @@ describe Rrod::Model::Finders, integration: true do
|
|
16
16
|
expect(found).to be_a model
|
17
17
|
end
|
18
18
|
|
19
|
+
it "will have the id of the key it is found with" do
|
20
|
+
found = model.find(instance.id)
|
21
|
+
expect(found.id).to eq instance.id
|
22
|
+
end
|
23
|
+
|
19
24
|
it "will raise an error if it can't be found" do
|
20
25
|
expect { model.find("id that is not there") }.to raise_error(Rrod::Model::NotFound)
|
21
26
|
end
|
22
27
|
|
23
|
-
|
24
|
-
found
|
25
|
-
expect(found).to be_persisted
|
26
|
-
end
|
28
|
+
describe "when finding" do
|
29
|
+
let(:found) { model.find(instance.id) }
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
it "is persisted" do
|
32
|
+
expect(found).to be_persisted
|
33
|
+
end
|
34
|
+
|
35
|
+
it "is not changed" do
|
36
|
+
expect(found).not_to be_changed
|
37
|
+
end
|
38
|
+
|
39
|
+
it "sets the robject on the found instance" do
|
40
|
+
expect(found.robject).to be_a Riak::RObject
|
41
|
+
end
|
31
42
|
end
|
32
43
|
|
33
44
|
describe "finding by attributes in the hash" do
|
@@ -38,6 +38,14 @@ describe Rrod::Model::Persistence, integration: true do
|
|
38
38
|
it "has an id" do
|
39
39
|
expect(instance.id).not_to be_nil
|
40
40
|
end
|
41
|
+
|
42
|
+
it "does not store the id in the model json" do
|
43
|
+
expect(instance.robject.data).not_to have_key('id')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "does not have changes" do
|
47
|
+
expect(instance).not_to be_changed
|
48
|
+
end
|
41
49
|
end
|
42
50
|
|
43
51
|
describe "update models" do
|
@@ -75,13 +75,18 @@ describe Rrod::Model::Schema do
|
|
75
75
|
|
76
76
|
it "will properly serialize nested models" do
|
77
77
|
instance.address = Address.new(street: '123 Fancy Pants Lane')
|
78
|
-
instance.pets = [Pet.new(name: 'Molle')]
|
78
|
+
instance.pets = [Pet.new(name: 'Molle', friendly: true)]
|
79
79
|
instance.name = 'Zoolander'
|
80
80
|
hash = instance.serializable_hash
|
81
81
|
|
82
82
|
expect(hash).to be_a Hash
|
83
|
-
expect(hash['pets']).to eq [{'name' => 'Molle'
|
84
|
-
|
83
|
+
expect(hash['pets']).to eq [{'name' => 'Molle',
|
84
|
+
'species' => nil, 'friendly' => true, 'vaccinations' => [
|
85
|
+
'type' => 'rabies', 'when' => Date.today
|
86
|
+
]
|
87
|
+
}]
|
88
|
+
expect(hash['address']).to eq 'street' => '123 Fancy Pants Lane',
|
89
|
+
'city' => nil, 'state_abbr' => nil, 'zip' => nil
|
85
90
|
end
|
86
91
|
|
87
92
|
it "will set the parent on a nested model when assigned" do
|
@@ -108,8 +113,9 @@ describe Rrod::Model::Schema do
|
|
108
113
|
expect { Pet.rrod_cast(Object.new) }.to raise_error(Rrod::Model::UncastableObjectError)
|
109
114
|
end
|
110
115
|
|
111
|
-
it "will not add ids to models
|
112
|
-
|
116
|
+
it "will not add ids to models that are nested" do
|
117
|
+
instance.pets = [Pet.new(name: 'Molle')]
|
118
|
+
expect(instance.pets.first.attributes).not_to have_key('id')
|
113
119
|
end
|
114
120
|
|
115
121
|
end
|
@@ -7,11 +7,16 @@ describe Rrod::Model::Serialization do
|
|
7
7
|
let(:instance) { model.new(attributes) }
|
8
8
|
|
9
9
|
it "serializes its attributes for json" do
|
10
|
-
|
10
|
+
hash = attributes.stringify_keys.tap { |h| h['id'] = h['color'] = nil }
|
11
|
+
expect(instance.as_json).to eq hash
|
11
12
|
end
|
12
13
|
|
13
14
|
it "calls to_json on its as_json representation" do
|
14
15
|
expect(instance.as_json).to eq(JSON.parse instance.to_json)
|
15
16
|
end
|
16
17
|
|
18
|
+
it "includes the id in the standard json representation" do
|
19
|
+
expect(instance.as_json.keys).to include("id")
|
20
|
+
end
|
21
|
+
|
17
22
|
end
|
data/spec/rrod/model_spec.rb
CHANGED
@@ -25,6 +25,10 @@ describe Rrod::Model do
|
|
25
25
|
it "names the bucket based off the class" do
|
26
26
|
expect(model.bucket.name).to eq 'cars'
|
27
27
|
end
|
28
|
+
|
29
|
+
it "uses anonymous for the bucket name on unnamed classes" do
|
30
|
+
expect(Class.new { include Rrod::Model }.bucket.name).to eq Rrod::Model::Schema::ANONYMOUS
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
describe "instance methods" do
|
@@ -45,6 +49,13 @@ describe Rrod::Model do
|
|
45
49
|
it "has a bucket" do
|
46
50
|
expect(instance.bucket).to be_a Riak::Bucket
|
47
51
|
end
|
52
|
+
|
53
|
+
it "is inspectable" do
|
54
|
+
inspectable = BatMobile.new(wheels: 7, weapons: [{type: 'Gattling Gun', damage: 4_321}])
|
55
|
+
# used for human debugging of inspection output
|
56
|
+
# expect(inspectable.inspect).to eq("fancy string")
|
57
|
+
expect(inspectable.inspect).to be_a String
|
58
|
+
end
|
48
59
|
end
|
49
60
|
end
|
50
61
|
end
|
data/spec/support/models/car.rb
CHANGED
@@ -4,3 +4,22 @@ class Car
|
|
4
4
|
attribute :wheels, Integer
|
5
5
|
attribute :color, String
|
6
6
|
end
|
7
|
+
|
8
|
+
class Seat
|
9
|
+
include Rrod::Model
|
10
|
+
attribute :ejected, Boolean, default: false
|
11
|
+
end
|
12
|
+
|
13
|
+
class Weapon
|
14
|
+
include Rrod::Model
|
15
|
+
attribute :type, String
|
16
|
+
attribute :damage, Integer
|
17
|
+
end
|
18
|
+
|
19
|
+
class BatMobile < Car
|
20
|
+
include Rrod::Model
|
21
|
+
attribute :make, String, default: 'BatMobile'
|
22
|
+
attribute :color, String, default: 'Black'
|
23
|
+
attribute :seat, Seat, default: -> { Seat.new }
|
24
|
+
attribute :weapons, [Weapon]
|
25
|
+
end
|