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 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