synamoid 1.2.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.
@@ -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.hash_key])
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,58 @@
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_v2'
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 :endpoint, :default => nil
23
+ option :use_ssl, :default => true
24
+ option :port, :default => '443'
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
29
+ option :convert_big_decimal, :default => false
30
+
31
+ # The default logger for Dynamoid: either the Rails logger or just stdout.
32
+ #
33
+ # @since 0.2.0
34
+ def default_logger
35
+ defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : ::Logger.new($stdout)
36
+ end
37
+
38
+ # Returns the assigned logger instance.
39
+ #
40
+ # @since 0.2.0
41
+ def logger
42
+ @logger ||= default_logger
43
+ end
44
+
45
+ # If you want to, set the logger manually to any output you'd like. Or pass false or nil to disable logging entirely.
46
+ #
47
+ # @since 0.2.0
48
+ def logger=(logger)
49
+ case logger
50
+ when false, nil then @logger = nil
51
+ when true then @logger = default_logger
52
+ else
53
+ @logger = logger if logger.respond_to?(:info)
54
+ end
55
+ end
56
+
57
+ end
58
+ 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, :eval_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,214 @@
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 by a Query or Scan.
7
+ class Chain
8
+ attr_accessor :query, :source, :values, :consistent_read
9
+ include Enumerable
10
+
11
+ # Create a new criteria chain.
12
+ #
13
+ # @param [Class] source the class upon which the ultimate query will be performed.
14
+ def initialize(source)
15
+ @query = {}
16
+ @source = source
17
+ @consistent_read = false
18
+ @scan_index_forward = true
19
+ end
20
+
21
+ # The workhorse method of the criteria chain. Each key in the passed in hash will become another criteria that the
22
+ # ultimate query must match. A key can either be a symbol or a string, and should be an attribute name or
23
+ # an attribute name with a range operator.
24
+ #
25
+ # @example A simple criteria
26
+ # where(:name => 'Josh')
27
+ #
28
+ # @example A more complicated criteria
29
+ # where(:name => 'Josh', 'created_at.gt' => DateTime.now - 1.day)
30
+ #
31
+ # @since 0.2.0
32
+ def where(args)
33
+ args.each {|k, v| query[k.to_sym] = v}
34
+ self
35
+ end
36
+
37
+ def consistent
38
+ @consistent_read = true
39
+ self
40
+ end
41
+
42
+ # Returns all the records matching the criteria.
43
+ #
44
+ # @since 0.2.0
45
+ def all
46
+ records
47
+ end
48
+
49
+ # Destroys all the records matching the criteria.
50
+ #
51
+ def destroy_all
52
+ ids = []
53
+
54
+ if key_present?
55
+ ranges = []
56
+ Dynamoid.adapter.query(source.table_name, range_query).collect do |hash|
57
+ ids << hash[source.hash_key.to_sym]
58
+ ranges << hash[source.range_key.to_sym]
59
+ end
60
+
61
+ Dynamoid.adapter.delete(source.table_name, ids,{:range_key => ranges})
62
+ else
63
+ Dynamoid.adapter.scan(source.table_name, query, scan_opts).collect do |hash|
64
+ ids << hash[source.hash_key.to_sym]
65
+ end
66
+
67
+ Dynamoid.adapter.delete(source.table_name, ids)
68
+ end
69
+ end
70
+
71
+ def eval_limit(limit)
72
+ @eval_limit = limit
73
+ self
74
+ end
75
+
76
+ def batch(batch_size)
77
+ @batch_size = batch_size
78
+ self
79
+ end
80
+
81
+ def start(start)
82
+ @start = start
83
+ self
84
+ end
85
+
86
+ def scan_index_forward(scan_index_forward)
87
+ @scan_index_forward = scan_index_forward
88
+ self
89
+ end
90
+
91
+ # Allows you to use the results of a search as an enumerable over the results found.
92
+ #
93
+ # @since 0.2.0
94
+ def each(&block)
95
+ records.each(&block)
96
+ end
97
+
98
+ def consistent_opts
99
+ { :consistent_read => consistent_read }
100
+ end
101
+
102
+ private
103
+
104
+ # The actual records referenced by the association.
105
+ #
106
+ # @return [Enumerator] an iterator of the found records.
107
+ #
108
+ # @since 0.2.0
109
+ def records
110
+ results = if key_present?
111
+ records_via_query
112
+ else
113
+ records_via_scan
114
+ end
115
+ @batch_size ? results : Array(results)
116
+ end
117
+
118
+ def records_via_query
119
+ Enumerator.new do |yielder|
120
+ Dynamoid.adapter.query(source.table_name, range_query).each do |hash|
121
+ yielder.yield source.from_database(hash)
122
+ end
123
+ end
124
+ end
125
+
126
+ # If the query does not match an index, we'll manually scan the associated table to find results.
127
+ #
128
+ # @return [Enumerator] an iterator of the found records.
129
+ #
130
+ # @since 0.2.0
131
+ def records_via_scan
132
+ if Dynamoid::Config.warn_on_scan
133
+ Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
134
+ 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(', ')}]"
135
+ end
136
+
137
+ if @consistent_read
138
+ raise Dynamoid::Errors::InvalidQuery, 'Consistent read is not supported by SCAN operation'
139
+ end
140
+
141
+ Enumerator.new do |yielder|
142
+ Dynamoid.adapter.scan(source.table_name, query, scan_opts).each do |hash|
143
+ yielder.yield source.from_database(hash)
144
+ end
145
+ end
146
+ end
147
+
148
+ def range_hash(key)
149
+ val = query[key]
150
+
151
+ return { :range_value => query[key] } if query[key].is_a?(Range)
152
+
153
+ case key.to_s.split('.').last
154
+ when 'gt'
155
+ { :range_greater_than => val }
156
+ when 'lt'
157
+ { :range_less_than => val }
158
+ when 'gte'
159
+ { :range_gte => val }
160
+ when 'lte'
161
+ { :range_lte => val }
162
+ when 'between'
163
+ { :range_between => val }
164
+ when 'begins_with'
165
+ { :range_begins_with => val }
166
+ end
167
+ end
168
+
169
+ def range_query
170
+ opts = { :hash_value => query[source.hash_key] }
171
+ query.keys.select { |k| k.to_s.include?('.') }.each do |key|
172
+ opts.merge!(range_hash(key))
173
+ end
174
+ opts.merge(query_opts).merge(consistent_opts)
175
+ end
176
+
177
+ def query_keys
178
+ query.keys.collect{|k| k.to_s.split('.').first}
179
+ end
180
+
181
+ # [hash_key] or [hash_key, range_key] is specified in query keys.
182
+ def key_present?
183
+ query_keys == [source.hash_key.to_s] || (query_keys.to_set == [source.hash_key.to_s, source.range_key.to_s].to_set)
184
+ end
185
+
186
+ def start_key
187
+ key = { :hash_key_element => @start.hash_key }
188
+ if range_key = @start.class.range_key
189
+ key.merge!({:range_key_element => @start.send(range_key) })
190
+ end
191
+ key
192
+ end
193
+
194
+ def query_opts
195
+ opts = {}
196
+ opts[:select] = 'ALL_ATTRIBUTES'
197
+ opts[:limit] = @eval_limit if @eval_limit
198
+ opts[:next_token] = start_key if @start
199
+ opts[:scan_index_forward] = @scan_index_forward
200
+ opts
201
+ end
202
+
203
+ def scan_opts
204
+ opts = {}
205
+ opts[:limit] = @eval_limit if @eval_limit
206
+ opts[:next_token] = start_key if @start
207
+ opts[:batch_size] = @batch_size if @batch_size
208
+ opts
209
+ end
210
+ end
211
+
212
+ end
213
+
214
+ end