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,69 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Dynamoid #:nodoc:
|
|
3
|
+
|
|
4
|
+
module Associations
|
|
5
|
+
module SingleAssociation
|
|
6
|
+
include Association
|
|
7
|
+
|
|
8
|
+
delegate :class, :to => :target
|
|
9
|
+
|
|
10
|
+
def setter(object)
|
|
11
|
+
delete
|
|
12
|
+
source.update_attribute(source_attribute, Set[object.id])
|
|
13
|
+
self.send(:associate_target, object) if target_association
|
|
14
|
+
object
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def delete
|
|
18
|
+
source.update_attribute(source_attribute, nil)
|
|
19
|
+
self.send(:disassociate_target, target) if target && target_association
|
|
20
|
+
target
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create!(attributes = {})
|
|
24
|
+
setter(target_class.create!(attributes))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create(attributes = {})
|
|
28
|
+
setter(target_class.create!(attributes))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Is this object equal to the association's target?
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean] true/false
|
|
35
|
+
#
|
|
36
|
+
# @since 0.2.0
|
|
37
|
+
def ==(other)
|
|
38
|
+
target == other
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Delegate methods we don't find directly to the target.
|
|
42
|
+
#
|
|
43
|
+
# @since 0.2.0
|
|
44
|
+
def method_missing(method, *args)
|
|
45
|
+
if target.respond_to?(method)
|
|
46
|
+
target.send(method, *args)
|
|
47
|
+
else
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def nil?
|
|
53
|
+
target.nil?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Find the target of the has_one association.
|
|
59
|
+
#
|
|
60
|
+
# @return [Dynamoid::Document] the found target (or nil if nothing)
|
|
61
|
+
#
|
|
62
|
+
# @since 0.2.0
|
|
63
|
+
def find_target
|
|
64
|
+
return if source_ids.empty?
|
|
65
|
+
target_class.find(source_ids.first)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Dynamoid
|
|
3
|
+
|
|
4
|
+
# All modules that a Document is composed of are defined in this
|
|
5
|
+
# module, to keep the document class from getting too cluttered.
|
|
6
|
+
module Components
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
extend ActiveModel::Translation
|
|
11
|
+
extend ActiveModel::Callbacks
|
|
12
|
+
|
|
13
|
+
define_model_callbacks :create, :save, :destroy, :initialize, :update
|
|
14
|
+
|
|
15
|
+
before_create :set_created_at
|
|
16
|
+
before_save :set_updated_at
|
|
17
|
+
after_initialize :set_type
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
include ActiveModel::AttributeMethods
|
|
21
|
+
include ActiveModel::Conversion
|
|
22
|
+
include ActiveModel::MassAssignmentSecurity if defined?(ActiveModel::MassAssignmentSecurity)
|
|
23
|
+
include ActiveModel::Naming
|
|
24
|
+
include ActiveModel::Observing if defined?(ActiveModel::Observing)
|
|
25
|
+
include ActiveModel::Serializers::JSON
|
|
26
|
+
include ActiveModel::Serializers::Xml
|
|
27
|
+
include Dynamoid::Fields
|
|
28
|
+
include Dynamoid::Indexes
|
|
29
|
+
include Dynamoid::Persistence
|
|
30
|
+
include Dynamoid::Finders
|
|
31
|
+
include Dynamoid::Associations
|
|
32
|
+
include Dynamoid::Criteria
|
|
33
|
+
include Dynamoid::Validations
|
|
34
|
+
include Dynamoid::IdentityMap
|
|
35
|
+
include Dynamoid::Dirty
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require "uri"
|
|
3
|
+
require "dynamoid/config/options"
|
|
4
|
+
|
|
5
|
+
module Dynamoid
|
|
6
|
+
|
|
7
|
+
# Contains all the basic configuration information required for Dynamoid: both sensible defaults and required fields.
|
|
8
|
+
module Config
|
|
9
|
+
extend self
|
|
10
|
+
extend Options
|
|
11
|
+
include ActiveModel::Observing if defined?(ActiveModel::Observing)
|
|
12
|
+
|
|
13
|
+
# All the default options.
|
|
14
|
+
option :adapter, :default => 'aws-sdk'
|
|
15
|
+
option :namespace, :default => defined?(Rails) ? "dynamoid_#{Rails.application.class.parent_name}_#{Rails.env}" : "dynamoid"
|
|
16
|
+
option :logger, :default => defined?(Rails)
|
|
17
|
+
option :access_key
|
|
18
|
+
option :secret_key
|
|
19
|
+
option :read_capacity, :default => 100
|
|
20
|
+
option :write_capacity, :default => 20
|
|
21
|
+
option :warn_on_scan, :default => true
|
|
22
|
+
option :partitioning, :default => false
|
|
23
|
+
option :partition_size, :default => 200
|
|
24
|
+
option :endpoint, :default => 'dynamodb.us-east-1.amazonaws.com'
|
|
25
|
+
option :use_ssl, :default => true
|
|
26
|
+
option :port, :default => '443'
|
|
27
|
+
option :included_models, :default => []
|
|
28
|
+
option :identity_map, :default => false
|
|
29
|
+
|
|
30
|
+
# The default logger for Dynamoid: either the Rails logger or just stdout.
|
|
31
|
+
#
|
|
32
|
+
# @since 0.2.0
|
|
33
|
+
def default_logger
|
|
34
|
+
defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : ::Logger.new($stdout)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the assigned logger instance.
|
|
38
|
+
#
|
|
39
|
+
# @since 0.2.0
|
|
40
|
+
def logger
|
|
41
|
+
@logger ||= default_logger
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# If you want to, set the logger manually to any output you'd like. Or pass false or nil to disable logging entirely.
|
|
45
|
+
#
|
|
46
|
+
# @since 0.2.0
|
|
47
|
+
def logger=(logger)
|
|
48
|
+
case logger
|
|
49
|
+
when false, nil then @logger = nil
|
|
50
|
+
when true then @logger = default_logger
|
|
51
|
+
else
|
|
52
|
+
@logger = logger if logger.respond_to?(:info)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Shamelessly stolen from Mongoid!
|
|
2
|
+
module Dynamoid #:nodoc
|
|
3
|
+
module Config
|
|
4
|
+
|
|
5
|
+
# Encapsulates logic for setting options.
|
|
6
|
+
module Options
|
|
7
|
+
|
|
8
|
+
# Get the defaults or initialize a new empty hash.
|
|
9
|
+
#
|
|
10
|
+
# @example Get the defaults.
|
|
11
|
+
# options.defaults
|
|
12
|
+
#
|
|
13
|
+
# @return [ Hash ] The default options.
|
|
14
|
+
#
|
|
15
|
+
# @since 0.2.0
|
|
16
|
+
def defaults
|
|
17
|
+
@defaults ||= {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Define a configuration option with a default.
|
|
21
|
+
#
|
|
22
|
+
# @example Define the option.
|
|
23
|
+
# Options.option(:persist_in_safe_mode, :default => false)
|
|
24
|
+
#
|
|
25
|
+
# @param [ Symbol ] name The name of the configuration option.
|
|
26
|
+
# @param [ Hash ] options Extras for the option.
|
|
27
|
+
#
|
|
28
|
+
# @option options [ Object ] :default The default value.
|
|
29
|
+
#
|
|
30
|
+
# @since 0.2.0
|
|
31
|
+
def option(name, options = {})
|
|
32
|
+
defaults[name] = settings[name] = options[:default]
|
|
33
|
+
|
|
34
|
+
class_eval <<-RUBY
|
|
35
|
+
def #{name}
|
|
36
|
+
settings[#{name.inspect}]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def #{name}=(value)
|
|
40
|
+
settings[#{name.inspect}] = value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def #{name}?
|
|
44
|
+
#{name}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def reset_#{name}
|
|
48
|
+
settings[#{name.inspect}] = defaults[#{name.inspect}]
|
|
49
|
+
end
|
|
50
|
+
RUBY
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Reset the configuration options to the defaults.
|
|
54
|
+
#
|
|
55
|
+
# @example Reset the configuration options.
|
|
56
|
+
# config.reset
|
|
57
|
+
#
|
|
58
|
+
# @return [ Hash ] The defaults.
|
|
59
|
+
#
|
|
60
|
+
# @since 0.2.0
|
|
61
|
+
def reset
|
|
62
|
+
settings.replace(defaults)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get the settings or initialize a new empty hash.
|
|
66
|
+
#
|
|
67
|
+
# @example Get the settings.
|
|
68
|
+
# options.settings
|
|
69
|
+
#
|
|
70
|
+
# @return [ Hash ] The setting options.
|
|
71
|
+
#
|
|
72
|
+
# @since 0.2.0
|
|
73
|
+
def settings
|
|
74
|
+
@settings ||= {}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'dynamoid/criteria/chain'
|
|
3
|
+
|
|
4
|
+
module Dynamoid
|
|
5
|
+
|
|
6
|
+
# Allows classes to be queried by where, all, first, and each and return criteria chains.
|
|
7
|
+
module Criteria
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
|
|
12
|
+
[:where, :all, :first, :each, :limit, :start, :scan_index_forward].each do |meth|
|
|
13
|
+
# Return a criteria chain in response to a method that will begin or end a chain. For more information,
|
|
14
|
+
# see Dynamoid::Criteria::Chain.
|
|
15
|
+
#
|
|
16
|
+
# @since 0.2.0
|
|
17
|
+
define_method(meth) do |*args|
|
|
18
|
+
chain = Dynamoid::Criteria::Chain.new(self)
|
|
19
|
+
if args
|
|
20
|
+
chain.send(meth, *args)
|
|
21
|
+
else
|
|
22
|
+
chain.send(meth)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Dynamoid #:nodoc:
|
|
3
|
+
module Criteria
|
|
4
|
+
|
|
5
|
+
# The criteria chain is equivalent to an ActiveRecord relation (and realistically I should change the name from
|
|
6
|
+
# chain to relation). It is a chainable object that builds up a query and eventually executes it either on an index
|
|
7
|
+
# or by a full table scan.
|
|
8
|
+
class Chain
|
|
9
|
+
attr_accessor :query, :source, :index, :values, :limit, :start, :consistent_read
|
|
10
|
+
include Enumerable
|
|
11
|
+
|
|
12
|
+
# Create a new criteria chain.
|
|
13
|
+
#
|
|
14
|
+
# @param [Class] source the class upon which the ultimate query will be performed.
|
|
15
|
+
def initialize(source)
|
|
16
|
+
@query = {}
|
|
17
|
+
@source = source
|
|
18
|
+
@consistent_read = false
|
|
19
|
+
@scan_index_forward = true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The workhorse method of the criteria chain. Each key in the passed in hash will become another criteria that the
|
|
23
|
+
# ultimate query must match. A key can either be a symbol or a string, and should be an attribute name or
|
|
24
|
+
# an attribute name with a range operator.
|
|
25
|
+
#
|
|
26
|
+
# @example A simple criteria
|
|
27
|
+
# where(:name => 'Josh')
|
|
28
|
+
#
|
|
29
|
+
# @example A more complicated criteria
|
|
30
|
+
# where(:name => 'Josh', 'created_at.gt' => DateTime.now - 1.day)
|
|
31
|
+
#
|
|
32
|
+
# @since 0.2.0
|
|
33
|
+
def where(args)
|
|
34
|
+
args.each {|k, v| query[k.to_sym] = v}
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def consistent
|
|
39
|
+
@consistent_read = true
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns all the records matching the criteria.
|
|
44
|
+
#
|
|
45
|
+
# @since 0.2.0
|
|
46
|
+
def all(opts = {})
|
|
47
|
+
batch opts[:batch_size] if opts.has_key? :batch_size
|
|
48
|
+
records
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Destroys all the records matching the criteria.
|
|
52
|
+
#
|
|
53
|
+
def destroy_all
|
|
54
|
+
ids = []
|
|
55
|
+
|
|
56
|
+
if range?
|
|
57
|
+
ranges = []
|
|
58
|
+
Dynamoid::Adapter.query(source.table_name, range_query).collect do |hash|
|
|
59
|
+
ids << hash[source.hash_key.to_sym]
|
|
60
|
+
ranges << hash[source.range_key.to_sym]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Dynamoid::Adapter.delete(source.table_name, ids,{:range_key => ranges})
|
|
64
|
+
elsif index
|
|
65
|
+
#TODO: test this throughly and find a way to delete all index table records for one source record
|
|
66
|
+
if index.range_key?
|
|
67
|
+
results = Dynamoid::Adapter.query(index.table_name, index_query.merge(consistent_opts))
|
|
68
|
+
else
|
|
69
|
+
results = Dynamoid::Adapter.read(index.table_name, index_query[:hash_value], consistent_opts)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
results.collect do |hash|
|
|
73
|
+
ids << hash[source.hash_key.to_sym]
|
|
74
|
+
index_ranges << hash[source.range_key.to_sym]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
unless ids.nil? || ids.empty?
|
|
78
|
+
ids = ids.to_a
|
|
79
|
+
|
|
80
|
+
if @start
|
|
81
|
+
ids = ids.drop_while { |id| id != @start.hash_key }.drop(1)
|
|
82
|
+
index_ranges = index_ranges.drop_while { |range| range != @start.hash_key }.drop(1) unless index_ranges.nil?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if @limit
|
|
86
|
+
ids = ids.take(@limit)
|
|
87
|
+
index_ranges = index_ranges.take(@limit)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Dynamoid::Adapter.delete(source.table_name, ids)
|
|
91
|
+
|
|
92
|
+
if index.range_key?
|
|
93
|
+
Dynamoid::Adapter.delete(index.table_name, ids,{:range_key => index_ranges})
|
|
94
|
+
else
|
|
95
|
+
Dynamoid::Adapter.delete(index.table_name, ids)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
Dynamoid::Adapter.scan(source.table_name, query, scan_opts).collect do |hash|
|
|
101
|
+
ids << hash[source.hash_key.to_sym]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
Dynamoid::Adapter.delete(source.table_name, ids)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Returns the first record matching the criteria.
|
|
109
|
+
#
|
|
110
|
+
# @since 0.2.0
|
|
111
|
+
def first
|
|
112
|
+
limit(1).first
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def limit(limit)
|
|
116
|
+
@limit = limit
|
|
117
|
+
records
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def batch(batch_size)
|
|
121
|
+
raise 'Cannot batch calls when using partitioning' if Dynamoid::Config.partitioning?
|
|
122
|
+
@batch_size = batch_size
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def start(start)
|
|
127
|
+
@start = start
|
|
128
|
+
self
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def scan_index_forward(scan_index_forward)
|
|
132
|
+
@scan_index_forward = scan_index_forward
|
|
133
|
+
self
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Allows you to use the results of a search as an enumerable over the results found.
|
|
137
|
+
#
|
|
138
|
+
# @since 0.2.0
|
|
139
|
+
def each(&block)
|
|
140
|
+
records.each(&block)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def consistent_opts
|
|
144
|
+
{ :consistent_read => consistent_read }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# The actual records referenced by the association.
|
|
150
|
+
#
|
|
151
|
+
# @return [Enumerator] an iterator of the found records.
|
|
152
|
+
#
|
|
153
|
+
# @since 0.2.0
|
|
154
|
+
def records
|
|
155
|
+
results = if range?
|
|
156
|
+
records_with_range
|
|
157
|
+
elsif index
|
|
158
|
+
records_with_index
|
|
159
|
+
else
|
|
160
|
+
records_without_index
|
|
161
|
+
end
|
|
162
|
+
@batch_size ? results : Array(results)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# If the query matches an index on the associated class, then this method will retrieve results from the index table.
|
|
166
|
+
#
|
|
167
|
+
# @return [Enumerator] an iterator of the found records.
|
|
168
|
+
#
|
|
169
|
+
# @since 0.2.0
|
|
170
|
+
def records_with_index
|
|
171
|
+
ids = ids_from_index
|
|
172
|
+
if ids.nil? || ids.empty?
|
|
173
|
+
[].to_enum
|
|
174
|
+
else
|
|
175
|
+
ids = ids.to_a
|
|
176
|
+
|
|
177
|
+
if @start
|
|
178
|
+
ids = ids.drop_while { |id| id != @start.hash_key }.drop(1)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
ids = ids.take(@limit) if @limit
|
|
182
|
+
source.find(ids, consistent_opts)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Returns the Set of IDs from the index table.
|
|
187
|
+
#
|
|
188
|
+
# @return [Set] a Set containing the IDs from the index.
|
|
189
|
+
def ids_from_index
|
|
190
|
+
if index.range_key?
|
|
191
|
+
Dynamoid::Adapter.query(index.table_name, index_query.merge(consistent_opts)).inject(Set.new) do |all, record|
|
|
192
|
+
all + Set.new(record[:ids])
|
|
193
|
+
end
|
|
194
|
+
else
|
|
195
|
+
results = Dynamoid::Adapter.read(index.table_name, index_query[:hash_value], consistent_opts)
|
|
196
|
+
results ? results[:ids] : []
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def records_with_range
|
|
201
|
+
Enumerator.new do |yielder|
|
|
202
|
+
Dynamoid::Adapter.query(source.table_name, range_query).each do |hash|
|
|
203
|
+
yielder.yield source.from_database(hash)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# If the query does not match an index, we'll manually scan the associated table to find results.
|
|
209
|
+
#
|
|
210
|
+
# @return [Enumerator] an iterator of the found records.
|
|
211
|
+
#
|
|
212
|
+
# @since 0.2.0
|
|
213
|
+
def records_without_index
|
|
214
|
+
if Dynamoid::Config.warn_on_scan
|
|
215
|
+
Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
|
|
216
|
+
Dynamoid.logger.warn "You can index this query by adding this to #{source.to_s.downcase}.rb: index [#{source.attributes.sort.collect{|attr| ":#{attr}"}.join(', ')}]"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
if @consistent_read
|
|
220
|
+
raise Dynamoid::Errors::InvalidQuery, 'Consistent read is not supported by SCAN operation'
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
Enumerator.new do |yielder|
|
|
224
|
+
Dynamoid::Adapter.scan(source.table_name, query, scan_opts).each do |hash|
|
|
225
|
+
yielder.yield source.from_database(hash)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Format the provided query so that it can be used to query results from DynamoDB.
|
|
231
|
+
#
|
|
232
|
+
# @return [Hash] a hash with keys of :hash_value and :range_value
|
|
233
|
+
#
|
|
234
|
+
# @since 0.2.0
|
|
235
|
+
def index_query
|
|
236
|
+
values = index.values(query)
|
|
237
|
+
{}.tap do |hash|
|
|
238
|
+
hash[:hash_value] = values[:hash_value]
|
|
239
|
+
if index.range_key?
|
|
240
|
+
key = query.keys.find{|k| k.to_s.include?('.')}
|
|
241
|
+
if key
|
|
242
|
+
hash.merge!(range_hash(key))
|
|
243
|
+
else
|
|
244
|
+
raise Dynamoid::Errors::MissingRangeKey, 'This index requires a range key'
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def range_hash(key)
|
|
251
|
+
val = query[key]
|
|
252
|
+
|
|
253
|
+
return { :range_value => query[key] } if query[key].is_a?(Range)
|
|
254
|
+
|
|
255
|
+
case key.split('.').last
|
|
256
|
+
when 'gt'
|
|
257
|
+
{ :range_greater_than => val.to_f }
|
|
258
|
+
when 'lt'
|
|
259
|
+
{ :range_less_than => val.to_f }
|
|
260
|
+
when 'gte'
|
|
261
|
+
{ :range_gte => val.to_f }
|
|
262
|
+
when 'lte'
|
|
263
|
+
{ :range_lte => val.to_f }
|
|
264
|
+
when 'begins_with'
|
|
265
|
+
{ :range_begins_with => val }
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def range_query
|
|
270
|
+
opts = { :hash_value => query[source.hash_key] }
|
|
271
|
+
if key = query.keys.find { |k| k.to_s.include?('.') }
|
|
272
|
+
opts.merge!(range_hash(key))
|
|
273
|
+
end
|
|
274
|
+
opts.merge(query_opts).merge(consistent_opts)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Return an index that fulfills all the attributes the criteria is querying, or nil if none is found.
|
|
278
|
+
#
|
|
279
|
+
# @since 0.2.0
|
|
280
|
+
def index
|
|
281
|
+
index = source.find_index(query_keys)
|
|
282
|
+
return nil if index.blank?
|
|
283
|
+
index
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def query_keys
|
|
287
|
+
query.keys.collect{|k| k.to_s.split('.').first}
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Use range query only when [hash_key] or [hash_key, range_key] is specified in query keys.
|
|
291
|
+
def range?
|
|
292
|
+
return false unless query_keys.include?(source.hash_key.to_s) or query_keys.include?(source.range_key.to_s)
|
|
293
|
+
query_keys == [source.hash_key.to_s] || (query_keys.to_set == [source.hash_key.to_s, source.range_key.to_s].to_set)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def start_key
|
|
297
|
+
hash_key_type = @start.class.attributes[@start.class.hash_key][:type] == :string ? 'S' : 'N'
|
|
298
|
+
key = { :hash_key_element => { hash_key_type => @start.hash_key.to_s } }
|
|
299
|
+
if range_key = @start.class.range_key
|
|
300
|
+
range_key_type = @start.class.attributes[range_key][:type] == :string ? 'S' : 'N'
|
|
301
|
+
key.merge!({:range_key_element => { range_key_type => @start.send(range_key).to_s } })
|
|
302
|
+
end
|
|
303
|
+
key
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def query_opts
|
|
307
|
+
opts = {}
|
|
308
|
+
opts[:select] = :all
|
|
309
|
+
opts[:limit] = @limit if @limit
|
|
310
|
+
opts[:next_token] = start_key if @start
|
|
311
|
+
opts[:scan_index_forward] = @scan_index_forward
|
|
312
|
+
opts
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def scan_opts
|
|
316
|
+
opts = {}
|
|
317
|
+
opts[:limit] = @limit if @limit
|
|
318
|
+
opts[:next_token] = start_key if @start
|
|
319
|
+
opts[:batch_size] = @batch_size if @batch_size
|
|
320
|
+
opts
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
end
|