rrod 1.0.0.alpha.7 → 1.0.0.alpha.8
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/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
|