dynamoid-moda 0.7.1
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 +15 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +118 -0
- data/Gemfile_activemodel4 +24 -0
- data/Gemfile_activemodel4.lock +88 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +360 -0
- data/Rakefile +93 -0
- data/VERSION +1 -0
- data/doc/.nojekyll +0 -0
- data/doc/Dynamoid.html +328 -0
- data/doc/Dynamoid/Adapter.html +1872 -0
- data/doc/Dynamoid/Adapter/AwsSdk.html +2101 -0
- data/doc/Dynamoid/Adapter/Local.html +1574 -0
- data/doc/Dynamoid/Associations.html +138 -0
- data/doc/Dynamoid/Associations/Association.html +847 -0
- data/doc/Dynamoid/Associations/BelongsTo.html +161 -0
- data/doc/Dynamoid/Associations/ClassMethods.html +766 -0
- data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +167 -0
- data/doc/Dynamoid/Associations/HasMany.html +167 -0
- data/doc/Dynamoid/Associations/HasOne.html +161 -0
- data/doc/Dynamoid/Associations/ManyAssociation.html +1684 -0
- data/doc/Dynamoid/Associations/SingleAssociation.html +627 -0
- data/doc/Dynamoid/Components.html +242 -0
- data/doc/Dynamoid/Config.html +412 -0
- data/doc/Dynamoid/Config/Options.html +638 -0
- data/doc/Dynamoid/Criteria.html +138 -0
- data/doc/Dynamoid/Criteria/Chain.html +1471 -0
- data/doc/Dynamoid/Criteria/ClassMethods.html +105 -0
- data/doc/Dynamoid/Dirty.html +424 -0
- data/doc/Dynamoid/Dirty/ClassMethods.html +174 -0
- data/doc/Dynamoid/Document.html +1033 -0
- data/doc/Dynamoid/Document/ClassMethods.html +1116 -0
- data/doc/Dynamoid/Errors.html +125 -0
- data/doc/Dynamoid/Errors/ConditionalCheckFailedException.html +141 -0
- data/doc/Dynamoid/Errors/DocumentNotValid.html +221 -0
- data/doc/Dynamoid/Errors/Error.html +137 -0
- data/doc/Dynamoid/Errors/InvalidField.html +141 -0
- data/doc/Dynamoid/Errors/InvalidQuery.html +131 -0
- data/doc/Dynamoid/Errors/MissingRangeKey.html +141 -0
- data/doc/Dynamoid/Fields.html +686 -0
- data/doc/Dynamoid/Fields/ClassMethods.html +438 -0
- data/doc/Dynamoid/Finders.html +135 -0
- data/doc/Dynamoid/Finders/ClassMethods.html +943 -0
- data/doc/Dynamoid/IdentityMap.html +492 -0
- data/doc/Dynamoid/IdentityMap/ClassMethods.html +534 -0
- data/doc/Dynamoid/Indexes.html +321 -0
- data/doc/Dynamoid/Indexes/ClassMethods.html +369 -0
- data/doc/Dynamoid/Indexes/Index.html +1142 -0
- data/doc/Dynamoid/Middleware.html +115 -0
- data/doc/Dynamoid/Middleware/IdentityMap.html +264 -0
- data/doc/Dynamoid/Persistence.html +892 -0
- data/doc/Dynamoid/Persistence/ClassMethods.html +836 -0
- data/doc/Dynamoid/Validations.html +415 -0
- data/doc/_index.html +506 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +338 -0
- data/doc/file.LICENSE.html +73 -0
- data/doc/file.README.html +416 -0
- data/doc/file_list.html +58 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +416 -0
- data/doc/js/app.js +214 -0
- data/doc/js/full_list.js +178 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +1144 -0
- data/doc/top-level-namespace.html +112 -0
- data/dynamoid-moda.gemspec +210 -0
- data/dynamoid.gemspec +208 -0
- data/lib/dynamoid.rb +46 -0
- data/lib/dynamoid/adapter.rb +267 -0
- data/lib/dynamoid/adapter/aws_sdk.rb +309 -0
- data/lib/dynamoid/associations.rb +106 -0
- data/lib/dynamoid/associations/association.rb +105 -0
- data/lib/dynamoid/associations/belongs_to.rb +44 -0
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +40 -0
- data/lib/dynamoid/associations/has_many.rb +39 -0
- data/lib/dynamoid/associations/has_one.rb +39 -0
- data/lib/dynamoid/associations/many_association.rb +191 -0
- data/lib/dynamoid/associations/single_association.rb +69 -0
- data/lib/dynamoid/components.rb +37 -0
- data/lib/dynamoid/config.rb +57 -0
- data/lib/dynamoid/config/options.rb +78 -0
- data/lib/dynamoid/criteria.rb +29 -0
- data/lib/dynamoid/criteria/chain.rb +326 -0
- data/lib/dynamoid/dirty.rb +47 -0
- data/lib/dynamoid/document.rb +199 -0
- data/lib/dynamoid/errors.rb +28 -0
- data/lib/dynamoid/fields.rb +138 -0
- data/lib/dynamoid/finders.rb +133 -0
- data/lib/dynamoid/identity_map.rb +96 -0
- data/lib/dynamoid/indexes.rb +69 -0
- data/lib/dynamoid/indexes/index.rb +103 -0
- data/lib/dynamoid/middleware/identity_map.rb +16 -0
- data/lib/dynamoid/persistence.rb +292 -0
- data/lib/dynamoid/validations.rb +36 -0
- data/spec/app/models/address.rb +13 -0
- data/spec/app/models/camel_case.rb +34 -0
- data/spec/app/models/car.rb +6 -0
- data/spec/app/models/magazine.rb +11 -0
- data/spec/app/models/message.rb +9 -0
- data/spec/app/models/nuclear_submarine.rb +5 -0
- data/spec/app/models/sponsor.rb +8 -0
- data/spec/app/models/subscription.rb +12 -0
- data/spec/app/models/tweet.rb +12 -0
- data/spec/app/models/user.rb +26 -0
- data/spec/app/models/vehicle.rb +7 -0
- data/spec/dynamoid/adapter/aws_sdk_spec.rb +376 -0
- data/spec/dynamoid/adapter_spec.rb +155 -0
- data/spec/dynamoid/associations/association_spec.rb +194 -0
- data/spec/dynamoid/associations/belongs_to_spec.rb +71 -0
- data/spec/dynamoid/associations/has_and_belongs_to_many_spec.rb +47 -0
- data/spec/dynamoid/associations/has_many_spec.rb +42 -0
- data/spec/dynamoid/associations/has_one_spec.rb +45 -0
- data/spec/dynamoid/associations_spec.rb +16 -0
- data/spec/dynamoid/config_spec.rb +27 -0
- data/spec/dynamoid/criteria/chain_spec.rb +210 -0
- data/spec/dynamoid/criteria_spec.rb +75 -0
- data/spec/dynamoid/dirty_spec.rb +57 -0
- data/spec/dynamoid/document_spec.rb +180 -0
- data/spec/dynamoid/fields_spec.rb +156 -0
- data/spec/dynamoid/finders_spec.rb +147 -0
- data/spec/dynamoid/identity_map_spec.rb +45 -0
- data/spec/dynamoid/indexes/index_spec.rb +104 -0
- data/spec/dynamoid/indexes_spec.rb +25 -0
- data/spec/dynamoid/persistence_spec.rb +301 -0
- data/spec/dynamoid/validations_spec.rb +36 -0
- data/spec/dynamoid_spec.rb +14 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/support/with_partitioning.rb +15 -0
- metadata +363 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Dynamoid
|
|
2
|
+
module Dirty
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
include ActiveModel::Dirty
|
|
5
|
+
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def from_database(*)
|
|
8
|
+
super.tap { |d| d.changed_attributes.clear }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def save(*)
|
|
13
|
+
clear_changes { super }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def update!(*)
|
|
17
|
+
ret = super
|
|
18
|
+
clear_changes #update! completely reloads all fields on the class, so any extant changes are wiped out
|
|
19
|
+
ret
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reload
|
|
23
|
+
super.tap { clear_changes }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear_changes
|
|
27
|
+
previous = changes
|
|
28
|
+
(block_given? ? yield : true).tap do |result|
|
|
29
|
+
unless result == false #failed validation; nil is OK.
|
|
30
|
+
@previously_changed = previous
|
|
31
|
+
changed_attributes.clear
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def write_attribute(name, value)
|
|
37
|
+
attribute_will_change!(name) unless self.read_attribute(name) == value
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def attribute_method?(attr)
|
|
44
|
+
super || self.class.attributes.has_key?(attr.to_sym)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Dynamoid #:nodoc:
|
|
3
|
+
|
|
4
|
+
# This is the base module for all domain objects that need to be persisted to
|
|
5
|
+
# the database as documents.
|
|
6
|
+
module Document
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
include Dynamoid::Components
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
class_attribute :options, :read_only_attributes, :base_class
|
|
12
|
+
self.options = {}
|
|
13
|
+
self.read_only_attributes = []
|
|
14
|
+
self.base_class = self
|
|
15
|
+
|
|
16
|
+
Dynamoid::Config.included_models << self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module ClassMethods
|
|
20
|
+
# Set up table options, including naming it whatever you want, setting the id key, and manually overriding read and
|
|
21
|
+
# write capacity.
|
|
22
|
+
#
|
|
23
|
+
# @param [Hash] options options to pass for this table
|
|
24
|
+
# @option options [Symbol] :name the name for the table; this still gets namespaced
|
|
25
|
+
# @option options [Symbol] :id id column for the table
|
|
26
|
+
# @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
|
|
27
|
+
# @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
|
|
28
|
+
#
|
|
29
|
+
# @since 0.4.0
|
|
30
|
+
def table(options = {})
|
|
31
|
+
self.options = options
|
|
32
|
+
super if defined? super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def attr_readonly(*read_only_attributes)
|
|
36
|
+
self.read_only_attributes.concat read_only_attributes.map(&:to_s)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the read_capacity for this table.
|
|
40
|
+
#
|
|
41
|
+
# @since 0.4.0
|
|
42
|
+
def read_capacity
|
|
43
|
+
options[:read_capacity] || Dynamoid::Config.read_capacity
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the write_capacity for this table.
|
|
47
|
+
#
|
|
48
|
+
# @since 0.4.0
|
|
49
|
+
def write_capacity
|
|
50
|
+
options[:write_capacity] || Dynamoid::Config.write_capacity
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the id field for this class.
|
|
54
|
+
#
|
|
55
|
+
# @since 0.4.0
|
|
56
|
+
def hash_key
|
|
57
|
+
options[:key] || :id
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the number of items for this class.
|
|
61
|
+
#
|
|
62
|
+
# @since 0.6.1
|
|
63
|
+
def count
|
|
64
|
+
Dynamoid::Adapter.adapter.count(table_name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Initialize a new object and immediately save it to the database.
|
|
68
|
+
#
|
|
69
|
+
# @param [Hash] attrs Attributes with which to create the object.
|
|
70
|
+
#
|
|
71
|
+
# @return [Dynamoid::Document] the saved document
|
|
72
|
+
#
|
|
73
|
+
# @since 0.2.0
|
|
74
|
+
def create(attrs = {})
|
|
75
|
+
attrs[:type] ? attrs[:type].constantize.new(attrs).tap(&:save) : new(attrs).tap(&:save)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Initialize a new object and immediately save it to the database. Raise an exception if persistence failed.
|
|
79
|
+
#
|
|
80
|
+
# @param [Hash] attrs Attributes with which to create the object.
|
|
81
|
+
#
|
|
82
|
+
# @return [Dynamoid::Document] the saved document
|
|
83
|
+
#
|
|
84
|
+
# @since 0.2.0
|
|
85
|
+
def create!(attrs = {})
|
|
86
|
+
attrs[:type] ? attrs[:type].constantize.new(attrs).tap(&:save!) : new(attrs).tap(&:save!)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Initialize a new object.
|
|
90
|
+
#
|
|
91
|
+
# @param [Hash] attrs Attributes with which to create the object.
|
|
92
|
+
#
|
|
93
|
+
# @return [Dynamoid::Document] the new document
|
|
94
|
+
#
|
|
95
|
+
# @since 0.2.0
|
|
96
|
+
def build(attrs = {})
|
|
97
|
+
attrs[:type] ? attrs[:type].constantize.new(attrs) : new(attrs)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Does this object exist?
|
|
101
|
+
#
|
|
102
|
+
# @param [Mixed] id_or_conditions the id of the object or a hash with the options to filter from.
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean] true/false
|
|
105
|
+
#
|
|
106
|
+
# @since 0.2.0
|
|
107
|
+
def exists?(id_or_conditions = {})
|
|
108
|
+
case id_or_conditions
|
|
109
|
+
when Hash then ! where(id_or_conditions).all.empty?
|
|
110
|
+
else !! find(id_or_conditions)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Initialize a new object.
|
|
116
|
+
#
|
|
117
|
+
# @param [Hash] attrs Attributes with which to create the object.
|
|
118
|
+
#
|
|
119
|
+
# @return [Dynamoid::Document] the new document
|
|
120
|
+
#
|
|
121
|
+
# @since 0.2.0
|
|
122
|
+
def initialize(attrs = {})
|
|
123
|
+
run_callbacks :initialize do
|
|
124
|
+
@new_record = true
|
|
125
|
+
@attributes ||= {}
|
|
126
|
+
@associations ||= {}
|
|
127
|
+
|
|
128
|
+
load(attrs)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def load(attrs)
|
|
133
|
+
self.class.undump(attrs).each {|key, value| send "#{key}=", value }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# An object is equal to another object if their ids are equal.
|
|
137
|
+
#
|
|
138
|
+
# @since 0.2.0
|
|
139
|
+
def ==(other)
|
|
140
|
+
if self.class.identity_map_on?
|
|
141
|
+
super
|
|
142
|
+
else
|
|
143
|
+
return false if other.nil?
|
|
144
|
+
other.is_a?(Dynamoid::Document) && self.hash_key == other.hash_key && self.range_value == other.range_value
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def eql?(other)
|
|
149
|
+
self == other
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def hash
|
|
153
|
+
hash_key.hash ^ range_value.hash
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Reload an object from the database -- if you suspect the object has changed in the datastore and you need those
|
|
157
|
+
# changes to be reflected immediately, you would call this method. This is a consistent read.
|
|
158
|
+
#
|
|
159
|
+
# @return [Dynamoid::Document] the document this method was called on
|
|
160
|
+
#
|
|
161
|
+
# @since 0.2.0
|
|
162
|
+
def reload
|
|
163
|
+
range_key_value = range_value ? dumped_range_value : nil
|
|
164
|
+
self.attributes = self.class.find(hash_key, :range_key => range_key_value, :consistent_read => true).attributes
|
|
165
|
+
@associations.values.each(&:reset)
|
|
166
|
+
self
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Return an object's hash key, regardless of what it might be called to the object.
|
|
170
|
+
#
|
|
171
|
+
# @since 0.4.0
|
|
172
|
+
def hash_key
|
|
173
|
+
self.send(self.class.hash_key)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Assign an object's hash key, regardless of what it might be called to the object.
|
|
177
|
+
#
|
|
178
|
+
# @since 0.4.0
|
|
179
|
+
def hash_key=(value)
|
|
180
|
+
self.send("#{self.class.hash_key}=", value)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def range_value
|
|
184
|
+
if range_key = self.class.range_key
|
|
185
|
+
self.send(range_key)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def range_value=(value)
|
|
190
|
+
self.send("#{self.class.range_key}=", value)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def dumped_range_value
|
|
196
|
+
dump_field(range_value, self.class.attributes[self.class.range_key])
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Dynamoid
|
|
3
|
+
|
|
4
|
+
# All the error specific to Dynamoid.
|
|
5
|
+
module Errors
|
|
6
|
+
|
|
7
|
+
# Generic error class.
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
# InvalidField is raised when an attribute is specified for an index, but the attribute does not exist.
|
|
11
|
+
class InvalidField < Error; end
|
|
12
|
+
|
|
13
|
+
# MissingRangeKey is raised when a table that requires a range key is quieried without one.
|
|
14
|
+
class MissingRangeKey < Error; end
|
|
15
|
+
|
|
16
|
+
# raised when the conditional check failed during update operation
|
|
17
|
+
class ConditionalCheckFailedException < Error; end
|
|
18
|
+
|
|
19
|
+
# DocumentNotValid is raised when the document fails validation.
|
|
20
|
+
class DocumentNotValid < Error
|
|
21
|
+
def initialize(document)
|
|
22
|
+
super("Validation failed: #{document.errors.full_messages.join(", ")}")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class InvalidQuery < Error; end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Dynamoid #:nodoc:
|
|
3
|
+
|
|
4
|
+
# All fields on a Dynamoid::Document must be explicitly defined -- if you have fields in the database that are not
|
|
5
|
+
# specified with field, then they will be ignored.
|
|
6
|
+
module Fields
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
# Initialize the attributes we know the class has, in addition to our magic attributes: id, created_at, and updated_at.
|
|
10
|
+
included do
|
|
11
|
+
class_attribute :attributes
|
|
12
|
+
class_attribute :range_key
|
|
13
|
+
|
|
14
|
+
self.attributes = {}
|
|
15
|
+
field :created_at, :datetime
|
|
16
|
+
field :updated_at, :datetime
|
|
17
|
+
|
|
18
|
+
field :id #Default primary key
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module ClassMethods
|
|
22
|
+
|
|
23
|
+
# Specify a field for a document. Its type determines how it is coerced when read in and out of the datastore:
|
|
24
|
+
# default is string, but you can also specify :integer, :float, :set, :array, :datetime, and :serialized.
|
|
25
|
+
#
|
|
26
|
+
# @param [Symbol] name the name of the field
|
|
27
|
+
# @param [Symbol] type the type of the field (one of :integer, :float, :set, :array, :datetime, or :serialized)
|
|
28
|
+
# @param [Hash] options any additional options for the field
|
|
29
|
+
#
|
|
30
|
+
# @since 0.2.0
|
|
31
|
+
def field(name, type = :string, options = {})
|
|
32
|
+
named = name.to_s
|
|
33
|
+
self.attributes = attributes.merge(name => {:type => type}.merge(options))
|
|
34
|
+
|
|
35
|
+
define_method(named) { read_attribute(named) }
|
|
36
|
+
define_method("#{named}?") { !read_attribute(named).nil? }
|
|
37
|
+
define_method("#{named}=") {|value| write_attribute(named, value) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def range(name, type = :string)
|
|
41
|
+
field(name, type)
|
|
42
|
+
self.range_key = name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def table(options)
|
|
46
|
+
#a default 'id' column is created when Dynamoid::Document is included
|
|
47
|
+
unless(attributes.has_key? hash_key)
|
|
48
|
+
remove_field :id
|
|
49
|
+
field(hash_key)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def remove_field(field)
|
|
54
|
+
field = field.to_sym
|
|
55
|
+
attributes.delete(field) or raise "No such field"
|
|
56
|
+
remove_method field
|
|
57
|
+
remove_method :"#{field}="
|
|
58
|
+
remove_method :"#{field}?"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# You can access the attributes of an object directly on its attributes method, which is by default an empty hash.
|
|
63
|
+
attr_accessor :attributes
|
|
64
|
+
alias :raw_attributes :attributes
|
|
65
|
+
|
|
66
|
+
# Write an attribute on the object. Also marks the previous value as dirty.
|
|
67
|
+
#
|
|
68
|
+
# @param [Symbol] name the name of the field
|
|
69
|
+
# @param [Object] value the value to assign to that field
|
|
70
|
+
#
|
|
71
|
+
# @since 0.2.0
|
|
72
|
+
def write_attribute(name, value)
|
|
73
|
+
if (size = value.to_s.size) > MAX_ITEM_SIZE
|
|
74
|
+
Dynamoid.logger.warn "DynamoDB can't store items larger than #{MAX_ITEM_SIZE} and the #{name} field has a length of #{size}."
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if association = @associations[name]
|
|
78
|
+
association.reset
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
attributes[name.to_sym] = value
|
|
82
|
+
end
|
|
83
|
+
alias :[]= :write_attribute
|
|
84
|
+
|
|
85
|
+
# Read an attribute from an object.
|
|
86
|
+
#
|
|
87
|
+
# @param [Symbol] name the name of the field
|
|
88
|
+
#
|
|
89
|
+
# @since 0.2.0
|
|
90
|
+
def read_attribute(name)
|
|
91
|
+
attributes[name.to_sym]
|
|
92
|
+
end
|
|
93
|
+
alias :[] :read_attribute
|
|
94
|
+
|
|
95
|
+
# Updates multiple attibutes at once, saving the object once the updates are complete.
|
|
96
|
+
#
|
|
97
|
+
# @param [Hash] attributes a hash of attributes to update
|
|
98
|
+
#
|
|
99
|
+
# @since 0.2.0
|
|
100
|
+
def update_attributes(attributes)
|
|
101
|
+
attributes.each {|attribute, value| self.write_attribute(attribute, value)} unless attributes.nil? || attributes.empty?
|
|
102
|
+
save
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Update a single attribute, saving the object afterwards.
|
|
106
|
+
#
|
|
107
|
+
# @param [Symbol] attribute the attribute to update
|
|
108
|
+
# @param [Object] value the value to assign it
|
|
109
|
+
#
|
|
110
|
+
# @since 0.2.0
|
|
111
|
+
def update_attribute(attribute, value)
|
|
112
|
+
write_attribute(attribute, value)
|
|
113
|
+
save
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# Automatically called during the created callback to set the created_at time.
|
|
119
|
+
#
|
|
120
|
+
# @since 0.2.0
|
|
121
|
+
def set_created_at
|
|
122
|
+
self.created_at = DateTime.now
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Automatically called during the save callback to set the updated_at time.
|
|
126
|
+
#
|
|
127
|
+
# @since 0.2.0
|
|
128
|
+
def set_updated_at
|
|
129
|
+
self.updated_at = DateTime.now
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def set_type
|
|
133
|
+
self.type ||= self.class.to_s if self.class.attributes[:type]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Dynamoid
|
|
3
|
+
|
|
4
|
+
# This module defines the finder methods that hang off the document at the
|
|
5
|
+
# class level, like find, find_by_id, and the method_missing style finders.
|
|
6
|
+
module Finders
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
|
|
11
|
+
# Find one or many objects, specified by one id or an array of ids.
|
|
12
|
+
#
|
|
13
|
+
# @param [Array/String] *id an array of ids or one single id
|
|
14
|
+
#
|
|
15
|
+
# @return [Dynamoid::Document] one object or an array of objects, depending on whether the input was an array or not
|
|
16
|
+
#
|
|
17
|
+
# @since 0.2.0
|
|
18
|
+
def find(*ids)
|
|
19
|
+
|
|
20
|
+
options = if ids.last.is_a? Hash
|
|
21
|
+
ids.slice!(-1)
|
|
22
|
+
else
|
|
23
|
+
{}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
ids = Array(ids.flatten.uniq)
|
|
27
|
+
if ids.count == 1
|
|
28
|
+
self.find_by_id(ids.first, options)
|
|
29
|
+
else
|
|
30
|
+
find_all(ids)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Return objects found by the given array of ids, either hash keys, or hash/range key combinations using BatchGet.
|
|
35
|
+
# Returns empty array if no results found.
|
|
36
|
+
#
|
|
37
|
+
# @param [Array<ID>] ids
|
|
38
|
+
# @param [Hash] options: Passed to the underlying query.
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# find all the user with hash key
|
|
42
|
+
# User.find_all(['1', '2', '3'])
|
|
43
|
+
#
|
|
44
|
+
# find all the tweets using hash key and range key with consistent read
|
|
45
|
+
# Tweet.find_all([['1', 'red'], ['1', 'green']], :consistent_read => true)
|
|
46
|
+
def find_all(ids, options = {})
|
|
47
|
+
items = Dynamoid::Adapter.read(self.table_name, ids, options)
|
|
48
|
+
items ? items[self.table_name].map{|i| from_database(i)} : []
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Find one object directly by id.
|
|
52
|
+
#
|
|
53
|
+
# @param [String] id the id of the object to find
|
|
54
|
+
#
|
|
55
|
+
# @return [Dynamoid::Document] the found object, or nil if nothing was found
|
|
56
|
+
#
|
|
57
|
+
# @since 0.2.0
|
|
58
|
+
def find_by_id(id, options = {})
|
|
59
|
+
if item = Dynamoid::Adapter.read(self.table_name, id, options)
|
|
60
|
+
from_database(item)
|
|
61
|
+
else
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Find one object directly by hash and range keys
|
|
67
|
+
#
|
|
68
|
+
# @param [String] hash_key of the object to find
|
|
69
|
+
# @param [String/Integer/Float] range_key of the object to find
|
|
70
|
+
#
|
|
71
|
+
def find_by_composite_key(hash_key, range_key, options = {})
|
|
72
|
+
find_by_id(hash_key, options.merge({:range_key => range_key}))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Find all objects by hash and range keys.
|
|
76
|
+
#
|
|
77
|
+
# @example find all ChamberTypes whose level is greater than 1
|
|
78
|
+
# class ChamberType
|
|
79
|
+
# include Dynamoid::Document
|
|
80
|
+
# field :chamber_type, :string
|
|
81
|
+
# range :level, :integer
|
|
82
|
+
# table :key => :chamber_type
|
|
83
|
+
# end
|
|
84
|
+
# ChamberType.find_all_by_composite_key('DustVault', range_greater_than: 1)
|
|
85
|
+
#
|
|
86
|
+
# @param [String] hash_key of the objects to find
|
|
87
|
+
# @param [Hash] options the options for the range key
|
|
88
|
+
# @option options [Range] :range_value find the range key within this range
|
|
89
|
+
# @option options [Number] :range_greater_than find range keys greater than this
|
|
90
|
+
# @option options [Number] :range_less_than find range keys less than this
|
|
91
|
+
# @option options [Number] :range_gte find range keys greater than or equal to this
|
|
92
|
+
# @option options [Number] :range_lte find range keys less than or equal to this
|
|
93
|
+
#
|
|
94
|
+
# @return [Array] an array of all matching items
|
|
95
|
+
#
|
|
96
|
+
def find_all_by_composite_key(hash_key, options = {})
|
|
97
|
+
Dynamoid::Adapter.query(self.table_name, options.merge({hash_value: hash_key})).collect do |item|
|
|
98
|
+
from_database(item)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Find using exciting method_missing finders attributes. Uses criteria chains under the hood to accomplish this neatness.
|
|
103
|
+
#
|
|
104
|
+
# @example find a user by a first name
|
|
105
|
+
# User.find_by_first_name('Josh')
|
|
106
|
+
#
|
|
107
|
+
# @example find all users by first and last name
|
|
108
|
+
# User.find_all_by_first_name_and_last_name('Josh', 'Symonds')
|
|
109
|
+
#
|
|
110
|
+
# @return [Dynamoid::Document/Array] the found object, or an array of found objects if all was somewhere in the method
|
|
111
|
+
#
|
|
112
|
+
# @since 0.2.0
|
|
113
|
+
def method_missing(method, *args)
|
|
114
|
+
if method =~ /find/
|
|
115
|
+
finder = method.to_s.split('_by_').first
|
|
116
|
+
attributes = method.to_s.split('_by_').last.split('_and_')
|
|
117
|
+
|
|
118
|
+
chain = Dynamoid::Criteria::Chain.new(self)
|
|
119
|
+
chain.query = Hash.new.tap {|h| attributes.each_with_index {|attr, index| h[attr.to_sym] = args[index]}}
|
|
120
|
+
|
|
121
|
+
if finder =~ /all/
|
|
122
|
+
return chain.all
|
|
123
|
+
else
|
|
124
|
+
return chain.first
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
super
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
end
|