ocean-dynamo 0.3.10 → 0.3.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +46 -10
- data/lib/ocean-dynamo.rb +4 -3
- data/lib/ocean-dynamo/associations/belongs_to.rb +124 -0
- data/lib/ocean-dynamo/{collection_proxy.rb → associations/collection_proxy.rb} +0 -0
- data/lib/ocean-dynamo/attributes.rb +20 -1
- data/lib/ocean-dynamo/exceptions.rb +8 -2
- data/lib/ocean-dynamo/persistence.rb +12 -8
- data/lib/ocean-dynamo/queries.rb +5 -1
- data/lib/ocean-dynamo/schema.rb +9 -7
- data/lib/ocean-dynamo/tables.rb +16 -5
- data/lib/ocean-dynamo/version.rb +1 -1
- metadata +4 -4
- data/lib/ocean-dynamo/associations.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5fb4f095350a4a1cc43debd8405c550277ec7c1
|
4
|
+
data.tar.gz: e1684c9a9481b00b6ba44657076eeda1930e3628
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8673fa49c6af6b931103b669fc1a597e74b68a864354ab26cd5cddf3d1acc509f0d50c171f8deff2c63227b21aaa1eab2875b8684682e6558886d9cf3abc376
|
7
|
+
data.tar.gz: 6be8bab8ca04c7a5e3e3cf4364627fef8a0cac2894d480dda2a12cb31ac72c1b2773cc88fb1ac600e51d2ba6ff3db4a5f92aecc820d6b4f0ff7a44b0b678ff1d
|
data/README.rdoc
CHANGED
@@ -8,7 +8,7 @@ OceanDynamo requires Ruby 2.0 and Ruby on Rails 4.0.0 or later.
|
|
8
8
|
{<img src="https://badge.fury.io/rb/ocean-dynamo.png" alt="Gem Version" />}[http://badge.fury.io/rb/ocean-dynamo]
|
9
9
|
|
10
10
|
|
11
|
-
|
11
|
+
=== Features
|
12
12
|
|
13
13
|
As one important use case for OceanDynamo is to facilitate the conversion of SQL based
|
14
14
|
ActiveRecord models to DynamoDB based models, it is important that the syntax and semantics
|
@@ -19,7 +19,7 @@ is of course based on ActiveModel.
|
|
19
19
|
The attribute and persistence layer of OceanDynamo is modeled on that of ActiveRecord:
|
20
20
|
there's +save+, +save!+, +create+, +update+, +update!+, +update_attributes+, +find_each+,
|
21
21
|
and all the other methods you're used to. The design goal is always to implement as much
|
22
|
-
|
22
|
+
of the ActiveRecord interface as possible, without compromising scalability. This makes the
|
23
23
|
task of switching from SQL to no-SQL much easier.
|
24
24
|
|
25
25
|
Thanks to its structural similarity to ActiveRecord, OceanDynamo works with FactoryGirl.
|
@@ -28,7 +28,7 @@ To facilitate testing, future versions will keep track of and delete instances a
|
|
28
28
|
OceanDynamo uses primary and secondary indices to retrieve related table items,
|
29
29
|
which means it will scale without limits.
|
30
30
|
|
31
|
-
|
31
|
+
=== Example
|
32
32
|
|
33
33
|
The following example shows the syntax.
|
34
34
|
|
@@ -93,18 +93,54 @@ controllers. Furthermore, OceanDynamo implements much of the infrastructure of A
|
|
93
93
|
for instance, +read_attribute+, +write_attribute+, and much of the control logic and
|
94
94
|
parameters.
|
95
95
|
|
96
|
-
|
97
|
-
expected. Model.find_each and Model.find_in_batches are also available.
|
96
|
+
+Model.all+ and +.count+ work as expected. +Model.find_each+ is also available.
|
98
97
|
|
99
|
-
|
100
|
-
|
98
|
+
+belongs_to+ is now operational. The other side, +has_many+, is much simpler and should
|
99
|
+
follow very soon (probably today).
|
100
|
+
|
101
|
+
Restrictions for +belongs_to+ tables:
|
102
|
+
* * The hash key must be specified and must not be +:id+.
|
103
|
+
* * The range key must not be specified at all.
|
104
|
+
* * belongs_to can be specified only once in each class.
|
105
|
+
|
106
|
+
These restrictions allow OceanDynamo to implement the +has_many+ / +belongs_to+
|
107
|
+
relation in a very efficient and massively scalable way.
|
108
|
+
|
109
|
+
+belongs_to+ claims the range key and uses it to store its own UUID, which normally
|
110
|
+
would be stored in the hash key attribute. Instead, the hash key attribute holds the
|
111
|
+
UUID of the parent. We have thus reversed the roles of these two fields. As a result,
|
112
|
+
all children have their parent UUID as their hash key, and their own UUID in their
|
113
|
+
range key.
|
114
|
+
|
115
|
+
This type of association is even more efficient than its ActiveRecord counterpart as
|
116
|
+
it uses only the primary index of the child model for finds and scans in both directions
|
117
|
+
of the +has_many+ / +belongs_to+ association.
|
118
|
+
|
119
|
+
Furthermore, since DynamoDB has powerful primary index searches involving substrings
|
120
|
+
and matching, the fact that the range key is a string can be used to implement
|
121
|
+
wildcard matching of additional attributes. This gives, amongst other things, the
|
122
|
+
equivalent of an SQL GROUP BY request, again without requiring any secondary indices.
|
123
|
+
|
124
|
+
It's our goal to use a similar technique to implement has_and_belongs_to_many relations,
|
125
|
+
which means that secondary indices won't be necessary for the vast majority of
|
126
|
+
OceanDynamo use cases. This ultimately means reduced operational costs, as well as
|
127
|
+
reduced complexity.
|
128
|
+
|
129
|
+
+.has_belongs_to?+ returns true if the class has a belongs_to association.
|
130
|
+
|
131
|
+
+.belongs_to_class returns the class of the belongs_to association, or false if none.
|
132
|
+
|
133
|
+
+.find+ can now take an array arg.
|
134
|
+
|
135
|
+
+#hash_key+ and +#range_key+ readers added.
|
101
136
|
|
102
137
|
|
103
138
|
=== Future milestones
|
104
139
|
|
105
140
|
After the +has_many+ / +belongs_to+ association, the +has_and_belongs_to_many+ assocation
|
106
|
-
will be implemented. This will require secondary indices
|
107
|
-
|
141
|
+
will be implemented. This will require secondary indices, unlike +has_many+ / +belongs_to+.
|
142
|
+
|
143
|
+
OceanDynamo will use association proxies to achieve the same kind of
|
108
144
|
interface as ActiveRecord, e.g.: <code>blog_entry.comments.build(body: "Cool!").save!</code>
|
109
145
|
|
110
146
|
|
@@ -115,7 +151,7 @@ e.g. to implement critical job queues. It will be used increasingly as features
|
|
115
151
|
added to OceanDynamo and will eventually replace all ActiveRecord tables in Ocean.
|
116
152
|
|
117
153
|
|
118
|
-
|
154
|
+
== Installation
|
119
155
|
|
120
156
|
gem install ocean-dynamo
|
121
157
|
|
data/lib/ocean-dynamo.rb
CHANGED
@@ -9,11 +9,12 @@ require "ocean-dynamo/exceptions"
|
|
9
9
|
require "ocean-dynamo/class_variables"
|
10
10
|
require "ocean-dynamo/tables"
|
11
11
|
require "ocean-dynamo/schema"
|
12
|
-
require "ocean-dynamo/callbacks"
|
13
12
|
require "ocean-dynamo/attributes"
|
14
|
-
require "ocean-dynamo/
|
13
|
+
require "ocean-dynamo/callbacks"
|
15
14
|
require "ocean-dynamo/persistence"
|
16
|
-
require "ocean-dynamo/
|
15
|
+
require "ocean-dynamo/queries"
|
16
|
+
|
17
|
+
require "ocean-dynamo/associations/belongs_to"
|
17
18
|
|
18
19
|
|
19
20
|
module OceanDynamo
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
class Base
|
3
|
+
|
4
|
+
def self.belongs_to(target) # :master, "master", Master
|
5
|
+
target_attr = target.to_s.underscore # "master"
|
6
|
+
target_attr_id = "#{target_attr}_id" # "master_id"
|
7
|
+
target_class = target_attr.camelize.constantize # Master
|
8
|
+
|
9
|
+
assert_only_one_belongs_to!
|
10
|
+
|
11
|
+
self.table_range_key = table_hash_key # The RANGE KEY is variable
|
12
|
+
self.table_hash_key = target_attr_id.to_sym # The HASH KEY is the parent UUID
|
13
|
+
|
14
|
+
attribute table_hash_key, :string # Define :master_id
|
15
|
+
define_attribute_accessors(table_hash_key) # Define master_id, master_id=, master_id?
|
16
|
+
|
17
|
+
# Make sure there always is a parent
|
18
|
+
validates(table_hash_key, presence: true) # Can't save without a parent_id
|
19
|
+
|
20
|
+
# Define the range attribute (our unique UUID)
|
21
|
+
attribute(table_range_key, :string, default: "") # Define :uuid
|
22
|
+
define_attribute_accessors(table_range_key) # define uuid, uuid=, uuid?
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
# Define the parent id attribute
|
27
|
+
attribute target_attr_id, :reference, default: nil, target_class: target_class,
|
28
|
+
association: :belongs_to
|
29
|
+
|
30
|
+
self.class_eval "def #{target_attr}
|
31
|
+
read_and_maybe_load_pointer('#{target_attr_id}')
|
32
|
+
end"
|
33
|
+
|
34
|
+
self.class_eval "def #{target_attr}=(value)
|
35
|
+
write_attribute('#{target_attr_id}', value)
|
36
|
+
@#{target_attr} = value
|
37
|
+
end"
|
38
|
+
|
39
|
+
self.class_eval "def #{target_attr_id}
|
40
|
+
get_pointer_id(@#{target_attr})
|
41
|
+
end"
|
42
|
+
|
43
|
+
self.class_eval "def #{target_attr_id}=(value)
|
44
|
+
write_attribute('#{target_attr_id}', value)
|
45
|
+
@#{target_attr} = value
|
46
|
+
end"
|
47
|
+
# TODO: "?" methods
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
#
|
52
|
+
# Returns true if the class has a belongs_to association.
|
53
|
+
#
|
54
|
+
def self.has_belongs_to?
|
55
|
+
fields[table_hash_key]['association'] == :belongs_to
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
#
|
61
|
+
# Returns the class of the belongs_to association, or false if none.
|
62
|
+
#
|
63
|
+
def self.belongs_to_class
|
64
|
+
has_belongs_to? && fields[table_hash_key]['target_class']
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
#
|
72
|
+
# belongs_to can be specified only once in each model, since we use the range key to
|
73
|
+
# store its UUID and the hash key to store the UUID of the parent, as in
|
74
|
+
# ["parent_uuid", "child_uuid"]. This allows the parent to find all its children
|
75
|
+
# extremely efficiently by using only the primary index. It also allows the child
|
76
|
+
# to find its parent using only its own hash key. Presto: scalability without any
|
77
|
+
# secondary indices in the has_many/belongs_to association.
|
78
|
+
#
|
79
|
+
# Caveat: the parent must have a simple primary key, not a composite one. It *is*
|
80
|
+
# possible to use a composite key, but then the children must use scans to find
|
81
|
+
# their parents. We could conditionalise this, of course, so that the lookup
|
82
|
+
# strategy is transparent to the user.
|
83
|
+
#
|
84
|
+
def self.assert_only_one_belongs_to! # :nodoc:
|
85
|
+
if has_belongs_to?
|
86
|
+
raise OceanDynamo::AssociationMustBeUnique,
|
87
|
+
"#{self} already belongs_to #{belongs_to_class}"
|
88
|
+
end
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
#
|
94
|
+
# This is run by #initialize and by #assign_attributes to set the
|
95
|
+
# association variables (@master, for instance) and its associated attribute
|
96
|
+
# (such as master_id) to the value given.
|
97
|
+
#
|
98
|
+
def assign_associations(attrs) # :nodoc:
|
99
|
+
if attrs && attrs.include?(:master)
|
100
|
+
@master = attrs[:master]
|
101
|
+
write_attribute('master_id', @master)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def read_and_maybe_load_pointer(name) # :nodoc:
|
107
|
+
ptr = read_attribute(name)
|
108
|
+
return nil if ptr.blank?
|
109
|
+
if persisted? && ptr.is_a?(String)
|
110
|
+
parent = fields[name][:target_class].find(ptr, consistent: true) # TODO: true?
|
111
|
+
write_attribute(name, parent) # Keep the instance we've just read
|
112
|
+
else
|
113
|
+
ptr
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def get_pointer_id(ptr) # :nodoc:
|
119
|
+
return nil if ptr.blank?
|
120
|
+
ptr.is_a?(String) ? ptr : ptr.id
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
File without changes
|
@@ -11,6 +11,23 @@ module OceanDynamo
|
|
11
11
|
attr_reader :dynamo_item # :nodoc:
|
12
12
|
|
13
13
|
|
14
|
+
#
|
15
|
+
# Returns the value of the hash key attribute
|
16
|
+
#
|
17
|
+
def hash_key
|
18
|
+
read_attribute(table_hash_key)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
#
|
23
|
+
# Returns the value of the range key attribute or false if the
|
24
|
+
# table doesn't have a range_key.
|
25
|
+
#
|
26
|
+
def range_key
|
27
|
+
table_range_key && read_attribute(table_range_key)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
14
31
|
def initialize(attrs={})
|
15
32
|
run_callbacks :initialize do
|
16
33
|
@attributes = Hash.new
|
@@ -22,6 +39,7 @@ module OceanDynamo
|
|
22
39
|
@new_record = true
|
23
40
|
raise UnknownPrimaryKey unless table_hash_key
|
24
41
|
end
|
42
|
+
assign_associations(attrs)
|
25
43
|
attrs && attrs.delete_if { |k, v| !fields.has_key?(k) }
|
26
44
|
super(attrs)
|
27
45
|
end
|
@@ -89,6 +107,7 @@ module OceanDynamo
|
|
89
107
|
def assign_attributes(values, without_protection: false)
|
90
108
|
return if values.blank?
|
91
109
|
values = values.stringify_keys
|
110
|
+
assign_associations(values)
|
92
111
|
# if values.respond_to?(:permitted?)
|
93
112
|
# unless values.permitted?
|
94
113
|
# raise ActiveModel::ForbiddenAttributesError
|
@@ -140,7 +159,7 @@ module OceanDynamo
|
|
140
159
|
if respond_to?("#{k}=")
|
141
160
|
raise
|
142
161
|
else
|
143
|
-
raise UnknownAttributeError, "unknown attribute:
|
162
|
+
raise UnknownAttributeError, "unknown attribute: `#{k}'"
|
144
163
|
end
|
145
164
|
end
|
146
165
|
|
@@ -23,7 +23,7 @@ module OceanDynamo
|
|
23
23
|
def initialize(record) # :nodoc:
|
24
24
|
@record = record
|
25
25
|
errors = @record.errors.full_messages.join(", ")
|
26
|
-
super(
|
26
|
+
super(:"#{@record.class}.errors.messages.record_invalid")
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -38,7 +38,7 @@ module OceanDynamo
|
|
38
38
|
def initialize(record) # :nodoc:
|
39
39
|
@record = record
|
40
40
|
errors = @record.errors.full_messages.join(", ")
|
41
|
-
super(
|
41
|
+
super(:"#{@record.class}.errors.messages.record_invalid")
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -54,4 +54,10 @@ module OceanDynamo
|
|
54
54
|
|
55
55
|
class AssociationTypeMismatch < DynamoError; end
|
56
56
|
|
57
|
+
|
58
|
+
class BelongsToError < DynamoError; end
|
59
|
+
class AssociationMustBeUnique < BelongsToError; end
|
60
|
+
class RangeKeyMustNotBeSpecified < BelongsToError; end
|
61
|
+
class HashKeyMayNotBeNamedId < BelongsToError; end
|
62
|
+
|
57
63
|
end
|
@@ -122,8 +122,13 @@ module OceanDynamo
|
|
122
122
|
run_callbacks :commit do
|
123
123
|
run_callbacks :save do
|
124
124
|
run_callbacks :create do
|
125
|
-
|
126
|
-
|
125
|
+
# Default the correct hash key to a UUID
|
126
|
+
if self.class.has_belongs_to?
|
127
|
+
write_attribute(table_range_key, SecureRandom.uuid) if range_key.blank?
|
128
|
+
else
|
129
|
+
write_attribute(table_hash_key, SecureRandom.uuid) if hash_key.blank?
|
130
|
+
end
|
131
|
+
|
127
132
|
set_timestamps
|
128
133
|
dynamo_persist
|
129
134
|
true
|
@@ -171,8 +176,8 @@ module OceanDynamo
|
|
171
176
|
|
172
177
|
|
173
178
|
def reload(**keywords)
|
174
|
-
|
175
|
-
new_instance = self.class.find(
|
179
|
+
raise "HELLISHNESS" if id == range_key
|
180
|
+
new_instance = self.class.find(hash_key, range_key, **keywords)
|
176
181
|
assign_attributes(new_instance.attributes)
|
177
182
|
self
|
178
183
|
end
|
@@ -295,16 +300,15 @@ module OceanDynamo
|
|
295
300
|
|
296
301
|
def serialize_attribute(attribute, value, metadata=fields[attribute],
|
297
302
|
target_class: metadata[:target_class],
|
298
|
-
type: metadata[:type]
|
299
|
-
no_save: metadata[:no_save]
|
303
|
+
type: metadata[:type]
|
300
304
|
)
|
301
305
|
return nil if value == nil
|
306
|
+
#value = value.id if value.kind_of?(target_class)
|
302
307
|
case type
|
303
308
|
when :reference
|
304
|
-
return nil if no_save
|
305
309
|
raise DynamoError, ":reference must always have a :target_class" unless target_class
|
306
310
|
return value if value.is_a?(String)
|
307
|
-
return value.id if value.
|
311
|
+
return value.id if value.kind_of?(target_class)
|
308
312
|
raise AssociationTypeMismatch, "can't save a #{value.class} in a #{target_class} :reference"
|
309
313
|
when :string
|
310
314
|
return nil if ["", []].include?(value)
|
data/lib/ocean-dynamo/queries.rb
CHANGED
@@ -2,9 +2,13 @@ module OceanDynamo
|
|
2
2
|
class Base
|
3
3
|
|
4
4
|
def self.find(hash, range=nil, consistent: false)
|
5
|
+
return hash.collect {|elem| find elem, range, consistent: consistent } if hash.is_a?(Array)
|
5
6
|
_late_connect?
|
7
|
+
hash = hash.id if hash.kind_of?(Base) # TODO: We have (innocuous) leakage, fix!
|
6
8
|
item = dynamo_items[hash, range]
|
7
|
-
|
9
|
+
unless item.exists?
|
10
|
+
raise RecordNotFound, "can't find a #{self} with primary key ['#{hash}', #{range.inspect}]"
|
11
|
+
end
|
8
12
|
new._setup_from_dynamo(item, consistent: consistent)
|
9
13
|
end
|
10
14
|
|
data/lib/ocean-dynamo/schema.rb
CHANGED
@@ -41,13 +41,7 @@ module OceanDynamo
|
|
41
41
|
attribute(lock_attribute, :integer, default: 0) if locking
|
42
42
|
block.call
|
43
43
|
# Define attribute accessors
|
44
|
-
fields.each
|
45
|
-
name = name.to_s
|
46
|
-
# We define accessors even if the name is 'id' (for which we already have methods)
|
47
|
-
self.class_eval "def #{name}; read_attribute('#{name}'); end"
|
48
|
-
self.class_eval "def #{name}=(value); write_attribute('#{name}', value); end"
|
49
|
-
self.class_eval "def #{name}?; read_attribute('#{name}').present?; end"
|
50
|
-
end
|
44
|
+
fields.each { |name, md| define_attribute_accessors(name) }
|
51
45
|
# Connect to AWS
|
52
46
|
establish_db_connection if connect == true
|
53
47
|
# Finally return the full table name
|
@@ -55,6 +49,14 @@ module OceanDynamo
|
|
55
49
|
end
|
56
50
|
|
57
51
|
|
52
|
+
def self.define_attribute_accessors(name)
|
53
|
+
name = name.to_s
|
54
|
+
self.class_eval "def #{name}; read_attribute('#{name}'); end"
|
55
|
+
self.class_eval "def #{name}=(value); write_attribute('#{name}', value); end"
|
56
|
+
self.class_eval "def #{name}?; read_attribute('#{name}').present?; end"
|
57
|
+
end
|
58
|
+
|
59
|
+
|
58
60
|
def self.compute_table_name
|
59
61
|
name.pluralize.underscore
|
60
62
|
end
|
data/lib/ocean-dynamo/tables.rb
CHANGED
@@ -43,19 +43,30 @@ module OceanDynamo
|
|
43
43
|
|
44
44
|
|
45
45
|
def self.set_dynamo_table_keys
|
46
|
-
|
46
|
+
hash_key_type = fields[table_hash_key][:type]
|
47
|
+
hash_key_type = :string if hash_key_type == :reference
|
48
|
+
dynamo_table.hash_key = [table_hash_key, hash_key_type]
|
49
|
+
|
47
50
|
if table_range_key
|
48
|
-
|
51
|
+
range_key_type = fields[table_range_key][:type]
|
52
|
+
#range_key_type = :string if range_key_type == :reference
|
53
|
+
dynamo_table.range_key = [table_range_key, range_key_type]
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
52
57
|
|
53
58
|
def self.create_table
|
59
|
+
hash_key_type = fields[table_hash_key][:type]
|
60
|
+
hash_key_type = :string if hash_key_type == :reference
|
61
|
+
|
62
|
+
range_key_type = table_range_key && fields[table_range_key][:type]
|
63
|
+
#range_key_type = :string if range_key_type == :reference
|
64
|
+
|
54
65
|
self.dynamo_table = dynamo_client.tables.create(table_full_name,
|
55
66
|
table_read_capacity_units, table_write_capacity_units,
|
56
|
-
hash_key: { table_hash_key =>
|
57
|
-
range_key: table_range_key && { table_range_key =>
|
58
|
-
|
67
|
+
hash_key: { table_hash_key => hash_key_type},
|
68
|
+
range_key: table_range_key && { table_range_key => range_key_type }
|
69
|
+
)
|
59
70
|
sleep 1 until dynamo_table.status == :active
|
60
71
|
setup_dynamo
|
61
72
|
true
|
data/lib/ocean-dynamo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ocean-dynamo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Bengtson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
@@ -160,12 +160,12 @@ extensions: []
|
|
160
160
|
extra_rdoc_files: []
|
161
161
|
files:
|
162
162
|
- config/routes.rb
|
163
|
-
- lib/ocean-dynamo/associations.rb
|
163
|
+
- lib/ocean-dynamo/associations/belongs_to.rb
|
164
|
+
- lib/ocean-dynamo/associations/collection_proxy.rb
|
164
165
|
- lib/ocean-dynamo/attributes.rb
|
165
166
|
- lib/ocean-dynamo/base.rb
|
166
167
|
- lib/ocean-dynamo/callbacks.rb
|
167
168
|
- lib/ocean-dynamo/class_variables.rb
|
168
|
-
- lib/ocean-dynamo/collection_proxy.rb
|
169
169
|
- lib/ocean-dynamo/engine.rb
|
170
170
|
- lib/ocean-dynamo/exceptions.rb
|
171
171
|
- lib/ocean-dynamo/persistence.rb
|
@@ -1,51 +0,0 @@
|
|
1
|
-
module OceanDynamo
|
2
|
-
class Base
|
3
|
-
|
4
|
-
def self.belongs_to(target) # :api_user, "api_user", ApiUser
|
5
|
-
target_attr = target.to_s.underscore # "api_user"
|
6
|
-
target_attr_id = "#{target_attr}_id" # "api_user_id"
|
7
|
-
target_class = target_attr.camelize.constantize # ApiUser
|
8
|
-
attribute target_attr_id, :reference, default: nil, target_class: target_class
|
9
|
-
attribute target_attr, :reference, default: nil, target_class: target_class, no_save: true
|
10
|
-
|
11
|
-
self.class_eval "def #{target_attr}
|
12
|
-
read_and_maybe_load_pointer('#{target_attr_id}')
|
13
|
-
end"
|
14
|
-
|
15
|
-
self.class_eval "def #{target_attr}=(value)
|
16
|
-
write_attribute('#{target_attr_id}', value)
|
17
|
-
write_attribute('#{target_attr}', value)
|
18
|
-
end"
|
19
|
-
|
20
|
-
self.class_eval "def #{target_attr_id}
|
21
|
-
read_pointer_id('#{target_attr}')
|
22
|
-
end"
|
23
|
-
|
24
|
-
self.class_eval "def #{target_attr_id}=(value)
|
25
|
-
write_attribute('#{target_attr_id}', value)
|
26
|
-
write_attribute('#{target_attr}', value)
|
27
|
-
end"
|
28
|
-
# TODO: "?" methods
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
protected
|
33
|
-
|
34
|
-
def read_and_maybe_load_pointer(name) # :nodoc:
|
35
|
-
ptr = read_attribute(name)
|
36
|
-
return nil if ptr.blank?
|
37
|
-
if persisted? && ptr.is_a?(String)
|
38
|
-
write_attribute(name, fields[name][:target_class].find(ptr)) # Keep the instance we've just read
|
39
|
-
else
|
40
|
-
ptr
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def read_pointer_id(name) # :nodoc:
|
45
|
-
ptr = read_attribute(name)
|
46
|
-
return nil if ptr.blank?
|
47
|
-
ptr.is_a?(String) ? ptr : ptr.id
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
end
|