dynamoid 1.1.0 → 1.2.0
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 +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
|