dynamoid 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.document +5 -0
- data/.gitignore +67 -0
- data/.rspec +2 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +20 -2
- data/Gemfile +3 -1
- data/{README.markdown → README.md} +30 -6
- data/Rakefile +17 -20
- data/dynamoid.gemspec +45 -68
- data/lib/dynamoid.rb +20 -18
- data/lib/dynamoid/adapter.rb +11 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v2.rb +313 -48
- data/lib/dynamoid/associations/association.rb +11 -0
- data/lib/dynamoid/associations/many_association.rb +4 -2
- data/lib/dynamoid/associations/single_association.rb +1 -1
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config.rb +3 -0
- data/lib/dynamoid/criteria/chain.rb +12 -10
- data/lib/dynamoid/document.rb +5 -3
- data/lib/dynamoid/errors.rb +17 -3
- data/lib/dynamoid/fields.rb +19 -5
- data/lib/dynamoid/finders.rb +65 -0
- data/lib/dynamoid/indexes.rb +273 -0
- data/lib/dynamoid/persistence.rb +14 -3
- data/lib/dynamoid/validations.rb +27 -0
- data/lib/dynamoid/version.rb +3 -0
- metadata +41 -15
@@ -99,6 +99,17 @@ module Dynamoid #:nodoc:
|
|
99
99
|
source.send(source_attribute) || Set.new
|
100
100
|
end
|
101
101
|
|
102
|
+
# Create a new instance of the target class without trying to add it to the association. This creates a base, that caller can update before setting or adding it.
|
103
|
+
#
|
104
|
+
# @param [Hash] attribute hash for the new object
|
105
|
+
#
|
106
|
+
# @return [Dynamoid::Document] the newly-created object
|
107
|
+
#
|
108
|
+
# @since 1.1.1
|
109
|
+
def build(attributes = {})
|
110
|
+
target_class.build(attributes)
|
111
|
+
end
|
112
|
+
|
102
113
|
end
|
103
114
|
end
|
104
115
|
|
@@ -145,8 +145,10 @@ module Dynamoid #:nodoc:
|
|
145
145
|
#
|
146
146
|
# @since 0.2.0
|
147
147
|
def where(args)
|
148
|
-
|
149
|
-
|
148
|
+
filtered = clone
|
149
|
+
filtered.query = query.clone
|
150
|
+
args.each {|k, v| filtered.query[k] = v}
|
151
|
+
filtered
|
150
152
|
end
|
151
153
|
|
152
154
|
# Is this array equal to the association's records?
|
data/lib/dynamoid/components.rb
CHANGED
data/lib/dynamoid/config.rb
CHANGED
@@ -23,6 +23,9 @@ module Dynamoid
|
|
23
23
|
option :use_ssl, :default => true
|
24
24
|
option :port, :default => '443'
|
25
25
|
option :identity_map, :default => false
|
26
|
+
option :timestamps, :default => true
|
27
|
+
option :sync_retry_max_times, :default => 60 # a bit over 2 minutes
|
28
|
+
option :sync_retry_wait_seconds, :default => 2
|
26
29
|
|
27
30
|
# The default logger for Dynamoid: either the Rails logger or just stdout.
|
28
31
|
#
|
@@ -50,22 +50,22 @@ module Dynamoid #:nodoc:
|
|
50
50
|
#
|
51
51
|
def destroy_all
|
52
52
|
ids = []
|
53
|
-
|
53
|
+
|
54
54
|
if key_present?
|
55
55
|
ranges = []
|
56
56
|
Dynamoid.adapter.query(source.table_name, range_query).collect do |hash|
|
57
57
|
ids << hash[source.hash_key.to_sym]
|
58
58
|
ranges << hash[source.range_key.to_sym]
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
Dynamoid.adapter.delete(source.table_name, ids,{:range_key => ranges})
|
62
62
|
else
|
63
63
|
Dynamoid.adapter.scan(source.table_name, query, scan_opts).collect do |hash|
|
64
64
|
ids << hash[source.hash_key.to_sym]
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
Dynamoid.adapter.delete(source.table_name, ids)
|
68
|
-
end
|
68
|
+
end
|
69
69
|
end
|
70
70
|
|
71
71
|
def eval_limit(limit)
|
@@ -152,13 +152,15 @@ module Dynamoid #:nodoc:
|
|
152
152
|
|
153
153
|
case key.to_s.split('.').last
|
154
154
|
when 'gt'
|
155
|
-
{ :range_greater_than => val
|
155
|
+
{ :range_greater_than => val }
|
156
156
|
when 'lt'
|
157
|
-
{ :range_less_than => val
|
157
|
+
{ :range_less_than => val }
|
158
158
|
when 'gte'
|
159
|
-
{ :range_gte => val
|
159
|
+
{ :range_gte => val }
|
160
160
|
when 'lte'
|
161
|
-
{ :range_lte => val
|
161
|
+
{ :range_lte => val }
|
162
|
+
when 'between'
|
163
|
+
{ :range_between => val }
|
162
164
|
when 'begins_with'
|
163
165
|
{ :range_begins_with => val }
|
164
166
|
end
|
@@ -166,7 +168,7 @@ module Dynamoid #:nodoc:
|
|
166
168
|
|
167
169
|
def range_query
|
168
170
|
opts = { :hash_value => query[source.hash_key] }
|
169
|
-
|
171
|
+
query.keys.select { |k| k.to_s.include?('.') }.each do |key|
|
170
172
|
opts.merge!(range_hash(key))
|
171
173
|
end
|
172
174
|
opts.merge(query_opts).merge(consistent_opts)
|
@@ -197,7 +199,7 @@ module Dynamoid #:nodoc:
|
|
197
199
|
opts[:scan_index_forward] = @scan_index_forward
|
198
200
|
opts
|
199
201
|
end
|
200
|
-
|
202
|
+
|
201
203
|
def scan_opts
|
202
204
|
opts = {}
|
203
205
|
opts[:limit] = @eval_limit if @eval_limit
|
data/lib/dynamoid/document.rb
CHANGED
@@ -72,7 +72,7 @@ module Dynamoid #:nodoc:
|
|
72
72
|
#
|
73
73
|
# @since 0.2.0
|
74
74
|
def create(attrs = {})
|
75
|
-
|
75
|
+
build(attrs).tap(&:save)
|
76
76
|
end
|
77
77
|
|
78
78
|
# Initialize a new object and immediately save it to the database. Raise an exception if persistence failed.
|
@@ -83,7 +83,7 @@ module Dynamoid #:nodoc:
|
|
83
83
|
#
|
84
84
|
# @since 0.2.0
|
85
85
|
def create!(attrs = {})
|
86
|
-
|
86
|
+
build(attrs).tap(&:save!)
|
87
87
|
end
|
88
88
|
|
89
89
|
# Initialize a new object.
|
@@ -130,7 +130,9 @@ module Dynamoid #:nodoc:
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def load(attrs)
|
133
|
-
self.class.undump(attrs).each
|
133
|
+
self.class.undump(attrs).each do |key, value|
|
134
|
+
send("#{key}=", value) if self.respond_to?("#{key}=")
|
135
|
+
end
|
134
136
|
end
|
135
137
|
|
136
138
|
# An object is equal to another object if their ids are equal.
|
data/lib/dynamoid/errors.rb
CHANGED
@@ -1,14 +1,28 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Dynamoid
|
3
|
-
|
3
|
+
|
4
4
|
# All the errors specific to Dynamoid. The goal is to mimic ActiveRecord.
|
5
5
|
module Errors
|
6
|
-
|
6
|
+
|
7
7
|
# Generic Dynamoid error
|
8
8
|
class Error < StandardError; end
|
9
|
-
|
9
|
+
|
10
10
|
class MissingRangeKey < Error; end
|
11
11
|
|
12
|
+
class MissingIndex < Error; end
|
13
|
+
|
14
|
+
# InvalidIndex is raised when an invalid index is specified, for example if
|
15
|
+
# specified key attribute(s) or projected attributes do not exist.
|
16
|
+
class InvalidIndex < Error
|
17
|
+
def initialize(item)
|
18
|
+
if (item.is_a? String)
|
19
|
+
super(item)
|
20
|
+
else
|
21
|
+
super("Validation failed: #{item.errors.full_messages.join(", ")}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
12
26
|
# This class is intended to be private to Dynamoid.
|
13
27
|
class ConditionalCheckFailedException < Error
|
14
28
|
attr_reader :inner_exception
|
data/lib/dynamoid/fields.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Dynamoid #:nodoc:
|
3
|
-
|
4
3
|
# All fields on a Dynamoid::Document must be explicitly defined -- if you have fields in the database that are not
|
5
4
|
# specified with field, then they will be ignored.
|
6
5
|
module Fields
|
7
6
|
extend ActiveSupport::Concern
|
8
7
|
|
8
|
+
PERMITTED_KEY_TYPES = [
|
9
|
+
:number,
|
10
|
+
:integer,
|
11
|
+
:string,
|
12
|
+
:datetime
|
13
|
+
]
|
14
|
+
|
9
15
|
# Initialize the attributes we know the class has, in addition to our magic attributes: id, created_at, and updated_at.
|
10
16
|
included do
|
11
17
|
class_attribute :attributes
|
@@ -45,7 +51,15 @@ module Dynamoid #:nodoc:
|
|
45
51
|
self.attributes = attributes.merge(name => {:type => type}.merge(options))
|
46
52
|
|
47
53
|
define_method(named) { read_attribute(named) }
|
48
|
-
define_method("#{named}?")
|
54
|
+
define_method("#{named}?") do
|
55
|
+
value = read_attribute(named)
|
56
|
+
case value
|
57
|
+
when true then true
|
58
|
+
when false, nil then false
|
59
|
+
else
|
60
|
+
!value.nil?
|
61
|
+
end
|
62
|
+
end
|
49
63
|
define_method("#{named}=") {|value| write_attribute(named, value) }
|
50
64
|
end
|
51
65
|
|
@@ -131,14 +145,14 @@ module Dynamoid #:nodoc:
|
|
131
145
|
#
|
132
146
|
# @since 0.2.0
|
133
147
|
def set_created_at
|
134
|
-
self.created_at = DateTime.now
|
148
|
+
self.created_at = DateTime.now if Dynamoid::Config.timestamps
|
135
149
|
end
|
136
150
|
|
137
151
|
# Automatically called during the save callback to set the updated_at time.
|
138
152
|
#
|
139
153
|
# @since 0.2.0
|
140
154
|
def set_updated_at
|
141
|
-
self.updated_at = DateTime.now
|
155
|
+
self.updated_at = DateTime.now if Dynamoid::Config.timestamps
|
142
156
|
end
|
143
157
|
|
144
158
|
def set_type
|
@@ -147,4 +161,4 @@ module Dynamoid #:nodoc:
|
|
147
161
|
|
148
162
|
end
|
149
163
|
|
150
|
-
end
|
164
|
+
end
|
data/lib/dynamoid/finders.rb
CHANGED
@@ -6,6 +6,16 @@ module Dynamoid
|
|
6
6
|
module Finders
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
+
RANGE_MAP = {
|
10
|
+
'gt' => :range_greater_than,
|
11
|
+
'lt' => :range_less_than,
|
12
|
+
'gte' => :range_gte,
|
13
|
+
'lte' => :range_lte,
|
14
|
+
'begins_with' => :range_begins_with,
|
15
|
+
'between' => :range_between,
|
16
|
+
'eq' => :range_eq
|
17
|
+
}
|
18
|
+
|
9
19
|
module ClassMethods
|
10
20
|
|
11
21
|
# Find one or many objects, specified by one id or an array of ids.
|
@@ -100,6 +110,61 @@ module Dynamoid
|
|
100
110
|
end
|
101
111
|
end
|
102
112
|
|
113
|
+
# Find all objects by using local secondary or global secondary index
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# class User
|
117
|
+
# include Dynamoid::Document
|
118
|
+
# field :email, :string
|
119
|
+
# field :age, :integer
|
120
|
+
# field :gender, :string
|
121
|
+
# field :rank :number
|
122
|
+
# table :key => :email
|
123
|
+
# global_secondary_index :hash_key => :age, :range_key => :rank
|
124
|
+
# end
|
125
|
+
# # NOTE: the first param and the second param are both hashes,
|
126
|
+
# # so curly braces must be used on first hash param if sending both params
|
127
|
+
# User.find_all_by_secondary_index({:age => 5}, :range => {"rank.lte" => 10})
|
128
|
+
#
|
129
|
+
# @param [Hash] eg: {:age => 5}
|
130
|
+
# @param [Hash] eg: {"rank.lte" => 10}
|
131
|
+
# @param [Hash] options - @TODO support more options in future such as
|
132
|
+
# query filter, projected keys etc
|
133
|
+
# @return [Array] an array of all matching items
|
134
|
+
def find_all_by_secondary_index(hash, options = {})
|
135
|
+
range = options[:range] || {}
|
136
|
+
hash_key_field, hash_key_value = hash.first
|
137
|
+
range_key_field, range_key_value = range.first
|
138
|
+
range_op_mapped = nil
|
139
|
+
|
140
|
+
if range_key_field
|
141
|
+
range_key_field = range_key_field.to_s
|
142
|
+
range_key_op = "eq"
|
143
|
+
if range_key_field.include?(".")
|
144
|
+
range_key_field, range_key_op = range_key_field.split(".", 2)
|
145
|
+
end
|
146
|
+
range_op_mapped = RANGE_MAP.fetch(range_key_op)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Find the index
|
150
|
+
index = self.find_index(hash_key_field, range_key_field)
|
151
|
+
raise Dynamoid::Errors::MissingIndex if index.nil?
|
152
|
+
|
153
|
+
# query
|
154
|
+
opts = {
|
155
|
+
:hash_key => hash_key_field.to_s,
|
156
|
+
:hash_value => hash_key_value,
|
157
|
+
:index_name => index.name,
|
158
|
+
}
|
159
|
+
if range_key_field
|
160
|
+
opts[:range_key] = range_key_field
|
161
|
+
opts[range_op_mapped] = range_key_value
|
162
|
+
end
|
163
|
+
Dynamoid.adapter.query(self.table_name, opts).map do |item|
|
164
|
+
from_database(item)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
103
168
|
# Find using exciting method_missing finders attributes. Uses criteria chains under the hood to accomplish this neatness.
|
104
169
|
#
|
105
170
|
# @example find a user by a first name
|
@@ -0,0 +1,273 @@
|
|
1
|
+
module Dynamoid
|
2
|
+
module Indexes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :local_secondary_indexes
|
7
|
+
class_attribute :global_secondary_indexes
|
8
|
+
self.local_secondary_indexes = {}
|
9
|
+
self.global_secondary_indexes = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Defines a Global Secondary index on a table. Keys can be specified as
|
14
|
+
# hash-only, or hash & range.
|
15
|
+
#
|
16
|
+
# @param [Hash] options options to pass for this table
|
17
|
+
# @option options [Symbol] :name the name for the index; this still gets
|
18
|
+
# namespaced. If not specified, will use a default name.
|
19
|
+
# @option options [Symbol] :hash_key the index hash key column.
|
20
|
+
# @option options [Symbol] :range_key the index range key column (if
|
21
|
+
# applicable).
|
22
|
+
# @option options [Symbol, Array<Symbol>] :projected_attributes table
|
23
|
+
# attributes to project for this index. Can be :keys_only, :all
|
24
|
+
# or an array of included fields. If not specified, defaults to
|
25
|
+
# :keys_only.
|
26
|
+
# @option options [Integer] :read_capacity set the read capacity for the
|
27
|
+
# index; does not work on existing indexes.
|
28
|
+
# @option options [Integer] :write_capacity set the write capacity for
|
29
|
+
# the index; does not work on existing indexes.
|
30
|
+
def global_secondary_index(options={})
|
31
|
+
unless options.present?
|
32
|
+
raise Dynamoid::Errors::InvalidIndex.new('empty index definition')
|
33
|
+
end
|
34
|
+
|
35
|
+
unless options[:hash_key].present?
|
36
|
+
raise Dynamoid::Errors::InvalidIndex.new(
|
37
|
+
'A global secondary index requires a :hash_key to be specified'
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
index_opts = {
|
42
|
+
:read_capacity => Dynamoid::Config.read_capacity,
|
43
|
+
:write_capacity => Dynamoid::Config.write_capacity
|
44
|
+
}.merge(options)
|
45
|
+
|
46
|
+
index_opts[:dynamoid_class] = self
|
47
|
+
index_opts[:type] = :global_secondary
|
48
|
+
|
49
|
+
index = Dynamoid::Indexes::Index.new(index_opts)
|
50
|
+
gsi_key = index_key(options[:hash_key], options[:range_key])
|
51
|
+
self.global_secondary_indexes[gsi_key] = index
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Defines a local secondary index on a table. Will use the same primary
|
57
|
+
# hash key as the table.
|
58
|
+
#
|
59
|
+
# @param [Hash] options options to pass for this index.
|
60
|
+
# @option options [Symbol] :name the name for the index; this still gets
|
61
|
+
# namespaced. If not specified, a name is automatically generated.
|
62
|
+
# @option options [Symbol] :range_key the range key column for the index.
|
63
|
+
# @option options [Symbol, Array<Symbol>] :projected_attributes table
|
64
|
+
# attributes to project for this index. Can be :keys_only, :all
|
65
|
+
# or an array of included fields. If not specified, defaults to
|
66
|
+
# :keys_only.
|
67
|
+
def local_secondary_index(options={})
|
68
|
+
unless options.present?
|
69
|
+
raise Dynamoid::Errors::InvalidIndex.new('empty index definition')
|
70
|
+
end
|
71
|
+
|
72
|
+
primary_hash_key = self.hash_key
|
73
|
+
primary_range_key = self.range_key
|
74
|
+
index_range_key = options[:range_key]
|
75
|
+
|
76
|
+
unless index_range_key.present?
|
77
|
+
raise Dynamoid::Errors::InvalidIndex.new('A local secondary index '\
|
78
|
+
'requires a :range_key to be specified')
|
79
|
+
end
|
80
|
+
|
81
|
+
if primary_range_key.present? && index_range_key == primary_range_key
|
82
|
+
raise Dynamoid::Errors::InvalidIndex.new('A local secondary index'\
|
83
|
+
' must use a different :range_key than the primary key')
|
84
|
+
end
|
85
|
+
|
86
|
+
index_opts = options.merge({
|
87
|
+
:dynamoid_class => self,
|
88
|
+
:type => :local_secondary,
|
89
|
+
:hash_key => primary_hash_key
|
90
|
+
})
|
91
|
+
|
92
|
+
index = Dynamoid::Indexes::Index.new(index_opts)
|
93
|
+
key = index_key(primary_hash_key, index_range_key)
|
94
|
+
self.local_secondary_indexes[key] = index
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def find_index(hash, range=nil)
|
100
|
+
index = self.indexes[index_key(hash, range)]
|
101
|
+
index
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# Returns true iff the provided hash[,range] key combo is a local
|
106
|
+
# secondary index.
|
107
|
+
#
|
108
|
+
# @param [Symbol] hash hash key name.
|
109
|
+
# @param [Symbol] range range key name.
|
110
|
+
# @return [Boolean] true iff provided keys correspond to a local
|
111
|
+
# secondary index.
|
112
|
+
def is_local_secondary_index?(hash, range=nil)
|
113
|
+
self.local_secondary_indexes[index_key(hash, range)].present?
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# Returns true iff the provided hash[,range] key combo is a global
|
118
|
+
# secondary index.
|
119
|
+
#
|
120
|
+
# @param [Symbol] hash hash key name.
|
121
|
+
# @param [Symbol] range range key name.
|
122
|
+
# @return [Boolean] true iff provided keys correspond to a global
|
123
|
+
# secondary index.
|
124
|
+
def is_global_secondary_index?(hash, range=nil)
|
125
|
+
self.global_secondary_indexes[index_key(hash, range)].present?
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Generates a convenient lookup key name for a hash/range index.
|
130
|
+
# Should normally not be used directly.
|
131
|
+
#
|
132
|
+
# @param [Symbol] hash hash key name.
|
133
|
+
# @param [Symbol] range range key name.
|
134
|
+
# @return [String] returns "hash" if hash only, "hash_range" otherwise.
|
135
|
+
def index_key(hash, range=nil)
|
136
|
+
name = hash.to_s
|
137
|
+
if range.present?
|
138
|
+
name += "_#{range.to_s}"
|
139
|
+
end
|
140
|
+
name
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# Generates a default index name.
|
145
|
+
#
|
146
|
+
# @param [Symbol] hash hash key name.
|
147
|
+
# @param [Symbol] range range key name.
|
148
|
+
# @return [String] index name of the form "table_name_index_index_key".
|
149
|
+
def index_name(hash, range=nil)
|
150
|
+
"#{self.table_name}_index_#{self.index_key(hash, range)}"
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
# Convenience method to return all indexes on the table.
|
155
|
+
#
|
156
|
+
# @return [Hash<String, Object>] the combined hash of global and local
|
157
|
+
# secondary indexes.
|
158
|
+
def indexes
|
159
|
+
self.local_secondary_indexes.merge(self.global_secondary_indexes)
|
160
|
+
end
|
161
|
+
|
162
|
+
def indexed_hash_keys
|
163
|
+
self.global_secondary_indexes.map do |name, index|
|
164
|
+
index.hash_key.to_s
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Represents the attributes of a DynamoDB index.
|
171
|
+
class Index
|
172
|
+
include ActiveModel::Validations
|
173
|
+
|
174
|
+
PROJECTION_TYPES = [:keys_only, :all].to_set
|
175
|
+
DEFAULT_PROJECTION_TYPE = :keys_only
|
176
|
+
|
177
|
+
attr_accessor :name, :dynamoid_class, :type, :hash_key, :range_key,
|
178
|
+
:hash_key_schema, :range_key_schema, :projected_attributes,
|
179
|
+
:read_capacity, :write_capacity
|
180
|
+
|
181
|
+
|
182
|
+
validate do
|
183
|
+
validate_index_type
|
184
|
+
validate_hash_key
|
185
|
+
validate_range_key
|
186
|
+
validate_projected_attributes
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def initialize(attrs={})
|
191
|
+
unless attrs[:dynamoid_class].present?
|
192
|
+
raise Dynamoid::Errors::InvalidIndex.new(':dynamoid_class is required')
|
193
|
+
end
|
194
|
+
|
195
|
+
@dynamoid_class = attrs[:dynamoid_class]
|
196
|
+
@type = attrs[:type]
|
197
|
+
@hash_key = attrs[:hash_key]
|
198
|
+
@range_key = attrs[:range_key]
|
199
|
+
@name = attrs[:name] || @dynamoid_class.index_name(@hash_key, @range_key)
|
200
|
+
@projected_attributes =
|
201
|
+
attrs[:projected_attributes] || DEFAULT_PROJECTION_TYPE
|
202
|
+
@read_capacity = attrs[:read_capacity]
|
203
|
+
@write_capacity = attrs[:write_capacity]
|
204
|
+
|
205
|
+
raise Dynamoid::Errors::InvalidIndex.new(self) unless self.valid?
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
# Convenience method to determine the projection type for an index.
|
210
|
+
# Projection types are: :keys_only, :all, :include.
|
211
|
+
#
|
212
|
+
# @return [Symbol] the projection type.
|
213
|
+
def projection_type
|
214
|
+
if @projected_attributes.is_a? Array
|
215
|
+
:include
|
216
|
+
else
|
217
|
+
@projected_attributes
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def validate_projected_attributes
|
225
|
+
unless (@projected_attributes.is_a?(Array) ||
|
226
|
+
PROJECTION_TYPES.include?(@projected_attributes))
|
227
|
+
errors.add(:projected_attributes, 'Invalid projected attributes specified.')
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def validate_index_type
|
232
|
+
unless (@type.present? &&
|
233
|
+
[:local_secondary, :global_secondary].include?(@type))
|
234
|
+
errors.add(:type, 'Invalid index :type specified')
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def validate_range_key
|
239
|
+
if @range_key.present?
|
240
|
+
range_field_attributes = @dynamoid_class.attributes[@range_key]
|
241
|
+
if range_field_attributes.present?
|
242
|
+
range_key_type = range_field_attributes[:type]
|
243
|
+
if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(range_key_type)
|
244
|
+
@range_key_schema = {
|
245
|
+
@range_key => @dynamoid_class.dynamo_type(range_key_type)
|
246
|
+
}
|
247
|
+
else
|
248
|
+
errors.add(:range_key, 'Index :range_key is not a valid key type')
|
249
|
+
end
|
250
|
+
else
|
251
|
+
errors.add(:range_key, "No such field #{@range_key} defined on table")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def validate_hash_key
|
257
|
+
hash_field_attributes = @dynamoid_class.attributes[@hash_key]
|
258
|
+
if hash_field_attributes.present?
|
259
|
+
hash_field_type = hash_field_attributes[:type]
|
260
|
+
if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(hash_field_type)
|
261
|
+
@hash_key_schema = {
|
262
|
+
@hash_key => @dynamoid_class.dynamo_type(hash_field_type)
|
263
|
+
}
|
264
|
+
else
|
265
|
+
errors.add(:hash_key, 'Index :hash_key is not a valid key type')
|
266
|
+
end
|
267
|
+
else
|
268
|
+
errors.add(:hash_key, "No such field #{@hash_key} defined on table")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|