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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f7c4fed6f4b07652c23c34383bad82edc55ef84f
4
- data.tar.gz: ded28382a99937aecdf3d0f02718abc0f2dfb65f
3
+ metadata.gz: 21bcd59f68557a964682ed550e6f066b4b62d8c7
4
+ data.tar.gz: cb7eb976f82092d7013d48418a2de2dbcb1d4a7d
5
5
  SHA512:
6
- metadata.gz: 70db13dd62184794148611c3fa4a1258ae1f560d52cb0cad10b6c677b1f4320bf6886cfb63d1a28c204d40c5549c4d01128633821a12ce2fb55672030d5e7369
7
- data.tar.gz: eef5ddd4e356cdaf0e07d9e785d62c5bcf27260d3256146e4f7be73b2dfb976e0fdbe9f2ea8f01b0073de175209dac32ad4d521bf0d079e157dd7eaa96323bf6
6
+ metadata.gz: ebced7c3a04e6efe2a2b31bae0ee25eadebad2f2120dae03cc6ff646f98f4c7ca4e1a575398ae5a8655d4d69b79757121b4d9b2d3f473386602f414b087e2101
7
+ data.tar.gz: 084cdd4c9358b4c0ef399667bb5126b59594cec7f806490911865b1ecc37ebd16fb81feedcaeba8509f50821b4e244d1240cc65554fd6eda3aefd9531c451cec
@@ -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
- read_attribute :id
21
+ robject.key
16
22
  end
17
23
 
18
24
  def id=(value)
19
- write_attribute :id, value
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
- @attributes.keys.inject({}) { |acc, key|
26
- acc.tap { |hash| hash[key] = respond_to?(key) ? public_send(key) : nil }
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] || write_attribute(key, read_default(key))
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
- @attributes[key.to_s] = self.class.cast_attribute(key, value, self)
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
- send(method) if respond_to?(method)
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
- raise InvalidCollectionTypeError.new unless collection.respond_to?(:each)
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
@@ -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(data).tap { |instance| instance.id = key if key }
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
@@ -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] = send(method) }
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
- attribute = attributes[key]
22
- attribute ? attribute.cast(value, instance) : value
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
- instantiate(nil, value)
50
+ new(value)
46
51
  else
47
52
  raise UncastableObjectError.new("#{value.inspect} cannot be rrod_cast") unless Hash === value
48
53
  end
@@ -13,7 +13,7 @@ module Rrod
13
13
  end
14
14
 
15
15
  def to_json(options={})
16
- MultiJson.dump serializable_hash(options)
16
+ MultiJson.dump as_json(options)
17
17
  end
18
18
 
19
19
  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.7'
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
- it "is persisted" do
24
- found = model.find(instance.id)
25
- expect(found).to be_persisted
26
- end
28
+ describe "when finding" do
29
+ let(:found) { model.find(instance.id) }
27
30
 
28
- it "sets the robject on the found instance" do
29
- found = model.find(instance.id)
30
- expect(found.robject).to be_a Riak::RObject
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
- expect(hash['address']).to eq 'street' => '123 Fancy Pants Lane'
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 instantiated via `rrod_cast`" do
112
- expect(Pet.rrod_cast(name: 'Molle').attributes).to_not have_key('id')
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
- expect(instance.as_json).to eq attributes.stringify_keys
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
@@ -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
@@ -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
@@ -24,7 +24,7 @@ class Pet
24
24
  attribute :friendly, Boolean
25
25
 
26
26
  attribute :vaccinations, [Vaccination], default: -> {
27
- Vaccination.new(type: :rabies, when: Date.today)
27
+ [Vaccination.new(type: :rabies, when: Date.today)]
28
28
  }
29
29
  end
30
30
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rrod
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha.7
4
+ version: 1.0.0.alpha.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Hunter