Skip to content
This repository

range, limit, start #29

Merged
merged 4 commits into from about 2 years ago

2 participants

Anantha Kumaran Josh Symonds
Anantha Kumaran

This patch adds range, limit, exclusive_start_key support. It moves the table_creation logic back into the save method.

Josh Symonds
Owner

Wow, great patch, Anantha.

Josh Symonds Veraticus merged commit b87ea3f into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
6 lib/dynamoid/adapter.rb
@@ -110,12 +110,12 @@ def delete(table, id, range_key = nil)
110 110 # @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan
111 111 #
112 112 # @since 0.2.0
113   - def scan(table, query)
  113 + def scan(table, query, opts = {})
114 114 if Dynamoid::Config.partitioning?
115   - results = benchmark('Scan', table, query) {adapter.scan(table, query)}
  115 + results = benchmark('Scan', table, query) {adapter.scan(table, query, opts)}
116 116 result_for_partition(results)
117 117 else
118   - adapter.scan(table, query)
  118 + adapter.scan(table, query, opts)
119 119 end
120 120 end
121 121
5 lib/dynamoid/adapter/aws_sdk.rb
@@ -64,7 +64,6 @@ def batch_get_item(options)
64 64 # @since 0.2.0
65 65 def create_table(table_name, key = :id, options = {})
66 66 options[:hash_key] ||= {key.to_sym => :string}
67   - options[:range_key] = {options[:range_key].to_sym => :number} if options[:range_key]
68 67 read_capacity = options[:read_capacity] || Dynamoid::Config.read_capacity
69 68 write_capacity = options[:write_capacity] || Dynamoid::Config.write_capacity
70 69 table = @@connection.tables.create(table_name, read_capacity, write_capacity, options)
@@ -180,10 +179,10 @@ def query(table_name, opts = {})
180 179 # @return [Array] an array of all matching items
181 180 #
182 181 # @since 0.2.0
183   - def scan(table_name, scan_hash)
  182 + def scan(table_name, scan_hash, select_opts)
184 183 table = get_table(table_name)
185 184 results = []
186   - table.items.where(scan_hash).select do |data|
  185 + table.items.where(scan_hash).select(select_opts) do |data|
187 186 results << data.attributes.symbolize_keys!
188 187 end
189 188 results
32 lib/dynamoid/adapter/local.rb
@@ -57,7 +57,8 @@ def batch_get_item(options)
57 57 #
58 58 # @since 0.2.0
59 59 def create_table(table_name, key, options = {})
60   - data[table_name] = {:hash_key => key, :range_key => options[:range_key], :data => {}}
  60 + range_key = options[:range_key] && options[:range_key].keys.first
  61 + data[table_name] = {:hash_key => key, :range_key => range_key, :data => {}}
61 62 end
62 63
63 64 # Removes an item from the hash.
@@ -135,7 +136,8 @@ def put_item(table_name, object)
135 136 def query(table_name, opts = {})
136 137 id = opts[:hash_value]
137 138 range_key = data[table_name][:range_key]
138   - if opts[:range_value]
  139 +
  140 + results = if opts[:range_value]
139 141 data[table_name][:data].values.find_all{|v| v[:id] == id && !v[range_key].nil? && opts[:range_value].include?(v[range_key])}
140 142 elsif opts[:range_greater_than]
141 143 data[table_name][:data].values.find_all{|v| v[:id] == id && !v[range_key].nil? && v[range_key] > opts[:range_greater_than]}
@@ -146,8 +148,12 @@ def query(table_name, opts = {})
146 148 elsif opts[:range_lte]
147 149 data[table_name][:data].values.find_all{|v| v[:id] == id && !v[range_key].nil? && v[range_key] <= opts[:range_lte]}
148 150 else
149   - get_item(table_name, id)
  151 + data[table_name][:data].values.find_all{|v| v[:id] == id}
150 152 end
  153 +
  154 + results = drop_till_start(results, opts[:next_token], range_key)
  155 + results = results.take(opts[:limit]) if opts[:limit]
  156 + results
151 157 end
152 158
153 159 # Scan the hash.
@@ -158,9 +164,23 @@ def query(table_name, opts = {})
158 164 # @return [Array] an array of all matching items
159 165 #
160 166 # @since 0.2.0
161   - def scan(table_name, scan_hash)
  167 + def scan(table_name, scan_hash, opts = {})
162 168 return [] if data[table_name].nil?
163   - data[table_name][:data].values.flatten.select{|d| scan_hash.all?{|k, v| !d[k].nil? && d[k] == v}}
  169 + results = data[table_name][:data].values.flatten.select{|d| scan_hash.all?{|k, v| !d[k].nil? && d[k] == v}}
  170 + results = drop_till_start(results, opts[:next_token], data[table_name][:range_key])
  171 + results = results.take(opts[:limit]) if opts[:limit]
  172 + results
  173 + end
  174 +
  175 + def drop_till_start(results, next_token, range_key)
  176 + return results unless next_token
  177 +
  178 + hash_value = next_token[:hash_key_element].values.first
  179 + range_value = next_token[:range_key_element].values.first if next_token[:range_key_element]
  180 +
  181 + results = results.drop_while do |r|
  182 + (r[:id] != hash_value or r[range_key] != range_value)
  183 + end.drop(1)
164 184 end
165 185
166 186 # @todo Add an UpdateItem method.
@@ -168,4 +188,4 @@ def scan(table_name, scan_hash)
168 188 # @todo Add an UpdateTable method.
169 189 end
170 190 end
171   -end
  191 +end
4 lib/dynamoid/criteria.rb
@@ -9,7 +9,7 @@ module Criteria
9 9
10 10 module ClassMethods
11 11
12   - [:where, :all, :first, :each].each do |meth|
  12 + [:where, :all, :first, :each, :limit, :start].each do |meth|
13 13 # Return a criteria chain in response to a method that will begin or end a chain. For more information,
14 14 # see Dynamoid::Criteria::Chain.
15 15 #
@@ -26,4 +26,4 @@ module ClassMethods
26 26 end
27 27 end
28 28
29   -end
  29 +end
147 lib/dynamoid/criteria/chain.rb
@@ -6,9 +6,9 @@ module Criteria
6 6 # chain to relation). It is a chainable object that builds up a query and eventually executes it either on an index
7 7 # or by a full table scan.
8 8 class Chain
9   - attr_accessor :query, :source, :index, :values
  9 + attr_accessor :query, :source, :index, :values, :limit, :start
10 10 include Enumerable
11   -
  11 +
12 12 # Create a new criteria chain.
13 13 #
14 14 # @param [Class] source the class upon which the ultimate query will be performed.
@@ -16,10 +16,10 @@ def initialize(source)
16 16 @query = {}
17 17 @source = source
18 18 end
19   -
20   - # The workhorse method of the criteria chain. Each key in the passed in hash will become another criteria that the
21   - # ultimate query must match. A key can either be a symbol or a string, and should be an attribute name or
22   - # an attribute name with a range operator.
  19 +
  20 + # The workhorse method of the criteria chain. Each key in the passed in hash will become another criteria that the
  21 + # ultimate query must match. A key can either be a symbol or a string, and should be an attribute name or
  22 + # an attribute name with a range operator.
23 23 #
24 24 # @example A simple criteria
25 25 # where(:name => 'Josh')
@@ -32,7 +32,7 @@ def where(args)
32 32 args.each {|k, v| query[k] = v}
33 33 self
34 34 end
35   -
  35 +
36 36 # Returns all the records matching the criteria.
37 37 #
38 38 # @since 0.2.0
@@ -42,35 +42,50 @@ def all
42 42
43 43 # Returns the first record matching the criteria.
44 44 #
45   - # @since 0.2.0
  45 + # @since 0.2.0
46 46 def first
47   - records.first
  47 + limit(1).first
  48 + end
  49 +
  50 + def limit(limit)
  51 + @limit = limit
  52 + records
  53 + end
  54 +
  55 + def start(start)
  56 + @start = start
  57 + self
48 58 end
49 59
50 60 # Allows you to use the results of a search as an enumerable over the results found.
51 61 #
52   - # @since 0.2.0
  62 + # @since 0.2.0
53 63 def each(&block)
54 64 records.each(&block)
55 65 end
56   -
  66 +
57 67 private
58   -
  68 +
59 69 # The actual records referenced by the association.
60 70 #
61 71 # @return [Array] an array of the found records.
62 72 #
63 73 # @since 0.2.0
64 74 def records
65   - return records_with_index if index
66   - records_without_index
  75 + if range?
  76 + records_with_range
  77 + elsif index
  78 + records_with_index
  79 + else
  80 + records_without_index
  81 + end
67 82 end
68 83
69 84 # If the query matches an index on the associated class, then this method will retrieve results from the index table.
70 85 #
71 86 # @return [Array] an array of the found records.
72 87 #
73   - # @since 0.2.0
  88 + # @since 0.2.0
74 89 def records_with_index
75 90 ids = if index.range_key?
76 91 Dynamoid::Adapter.query(index.table_name, index_query).collect{|r| r[:ids]}.inject(Set.new) {|set, result| set + result}
@@ -85,28 +100,40 @@ def records_with_index
85 100 if ids.nil? || ids.empty?
86 101 []
87 102 else
88   - Array(source.find(ids.to_a))
  103 + ids = ids.to_a
  104 +
  105 + if @start
  106 + ids = ids.drop_while { |id| id != @start.id }.drop(1)
  107 + end
  108 +
  109 + ids = ids.take(@limit) if @limit
  110 + Array(source.find(ids))
89 111 end
90 112 end
91   -
  113 +
  114 + def records_with_range
  115 + Dynamoid::Adapter.query(source.table_name, range_query).collect {|hash| source.new(hash).tap { |r| r.new_record = false } }
  116 + end
  117 +
92 118 # If the query does not match an index, we'll manually scan the associated table to manually find results.
93 119 #
94 120 # @return [Array] an array of the found records.
95 121 #
96   - # @since 0.2.0
  122 + # @since 0.2.0
97 123 def records_without_index
98 124 if Dynamoid::Config.warn_on_scan
99 125 Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
100 126 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(', ')}]"
101 127 end
102   - Dynamoid::Adapter.scan(source.table_name, query).collect {|hash| source.new(hash).tap { |r| r.new_record = false } }
  128 +
  129 + Dynamoid::Adapter.scan(source.table_name, query, query_opts).collect {|hash| source.new(hash).tap { |r| r.new_record = false } }
103 130 end
104   -
105   - # Format the provided query so that it can be used to query results from DynamoDB.
  131 +
  132 + # Format the provided query so that it can be used to query results from DynamoDB.
106 133 #
107 134 # @return [Hash] a hash with keys of :hash_value and :range_value
108 135 #
109   - # @since 0.2.0
  136 + # @since 0.2.0
110 137 def index_query
111 138 values = index.values(query)
112 139 {}.tap do |hash|
@@ -114,21 +141,7 @@ def index_query
114 141 if index.range_key?
115 142 key = query.keys.find{|k| k.to_s.include?('.')}
116 143 if key
117   - if query[key].is_a?(Range)
118   - hash[:range_value] = query[key]
119   - else
120   - val = query[key].to_f
121   - case key.split('.').last
122   - when 'gt'
123   - hash[:range_greater_than] = val
124   - when 'lt'
125   - hash[:range_less_than] = val
126   - when 'gte'
127   - hash[:range_gte] = val
128   - when 'lte'
129   - hash[:range_lte] = val
130   - end
131   - end
  144 + hash.merge!(range_hash(key))
132 145 else
133 146 raise Dynamoid::Errors::MissingRangeKey, 'This index requires a range key'
134 147 end
@@ -136,16 +149,68 @@ def index_query
136 149 end
137 150 end
138 151
  152 + def range_hash(key)
  153 + val = query[key]
  154 +
  155 + return { :range_value => query[key] } if query[key].is_a?(Range)
  156 +
  157 + case key.split('.').last
  158 + when 'gt'
  159 + { :range_greater_than => val.to_f }
  160 + when 'lt'
  161 + { :range_less_than => val.to_f }
  162 + when 'gte'
  163 + { :range_gte => val.to_f }
  164 + when 'lte'
  165 + { :range_lte => val.to_f }
  166 + when 'begins_with'
  167 + { :range_begins_with => val }
  168 + end
  169 + end
  170 +
  171 + def range_query
  172 + opts = { :hash_value => query[:id] }
  173 + if key = query.keys.find { |k| k.to_s.include?('.') }
  174 + opts.merge!(range_key(key))
  175 + end
  176 + opts.merge(query_opts)
  177 + end
  178 +
139 179 # Return an index that fulfills all the attributes the criteria is querying, or nil if none is found.
140 180 #
141   - # @since 0.2.0
  181 + # @since 0.2.0
142 182 def index
143   - index = source.find_index(query.keys.collect{|k| k.to_s.split('.').first})
  183 + index = source.find_index(query_keys)
144 184 return nil if index.blank?
145 185 index
146 186 end
  187 +
  188 + def query_keys
  189 + query.keys.collect{|k| k.to_s.split('.').first}
  190 + end
  191 +
  192 + def range?
  193 + return false unless source.range_key
  194 + query_keys == ['id'] || (query_keys.to_set == ['id', source.range_key.to_s].to_set)
  195 + end
  196 +
  197 + def start_key
  198 + key = { :hash_key_element => { 'S' => @start.id } }
  199 + if range_key = @start.class.range_key
  200 + range_key_type = @start.class.attributes[range_key][:type] == :string ? 'S' : 'N'
  201 + key.merge!({:range_key_element => { range_key_type => @start.send(range_key) } })
  202 + end
  203 + key
  204 + end
  205 +
  206 + def query_opts
  207 + opts = {}
  208 + opts[:limit] = @limit if @limit
  209 + opts[:next_token] = start_key if @start
  210 + opts
  211 + end
147 212 end
148   -
  213 +
149 214 end
150   -
  215 +
151 216 end
7 lib/dynamoid/fields.rb
@@ -9,6 +9,8 @@ module Fields
9 9 # Initialize the attributes we know the class has, in addition to our magic attributes: id, created_at, and updated_at.
10 10 included do
11 11 class_attribute :attributes
  12 + class_attribute :range_key
  13 +
12 14 self.attributes = {}
13 15
14 16 field :id
@@ -36,6 +38,11 @@ def field(name, type = :string, options = {})
36 38
37 39 respond_to?(:define_attribute_method) ? define_attribute_method(name) : define_attribute_methods([name])
38 40 end
  41 +
  42 + def range(name, type = :string)
  43 + field(name, type)
  44 + self.range_key = name
  45 + end
39 46 end
40 47
41 48 # You can access the attributes of an object directly on its attributes method, which is by default an empty hash.
2  lib/dynamoid/indexes.rb
@@ -40,7 +40,7 @@ def find_index(index)
40 40 # @since 0.2.0
41 41 def create_indexes
42 42 self.indexes.each do |name, index|
43   - opts = index.range_key? ? {:range_key => :range} : {}
  43 + opts = index.range_key? ? {:range_key => { :range => :number }} : {}
44 44 self.create_table(index.table_name, :id, opts) unless self.table_exists?(index.table_name)
45 45 end
46 46 end
18 lib/dynamoid/persistence.rb
@@ -27,6 +27,17 @@ def create_table(table_name, id = :id, options = {})
27 27 Dynamoid::Adapter.tables << table_name if Dynamoid::Adapter.create_table(table_name, id.to_sym, options)
28 28 end
29 29
  30 + def create_table_if_neccessary
  31 + return if table_exists?(table_name)
  32 +
  33 + opts = {}
  34 + if range_key
  35 + opts[:range_key] = { range_key => attributes[range_key][:type] }
  36 + end
  37 +
  38 + create_table(table_name, :id, opts)
  39 + end
  40 +
30 41 # Does a table with this name exist?
31 42 #
32 43 # @since 0.2.0
@@ -84,11 +95,6 @@ def undump_field(value, type)
84 95
85 96 end
86 97
87   - # Create the table if it doesn't exist already upon loading the class.
88   - included do
89   - self.create_table(self.table_name) unless self.table_exists?(self.table_name)
90   - end
91   -
92 98 # Is this object persisted in the datastore? Required for some ActiveModel integration stuff.
93 99 #
94 100 # @since 0.2.0
@@ -100,6 +106,8 @@ def persisted?
100 106 #
101 107 # @since 0.2.0
102 108 def save(options = {})
  109 + self.class.create_table_if_neccessary
  110 +
103 111 @previously_changed = changes
104 112
105 113 if new_record?
7 spec/app/models/tweet.rb
... ... @@ -0,0 +1,7 @@
  1 +class Tweet
  2 + include Dynamoid::Document
  3 +
  4 + range :group, :string
  5 +
  6 + field :msg
  7 +end
4 spec/dynamoid/adapter/aws_sdk_spec.rb
@@ -8,7 +8,7 @@
8 8 context 'without a preexisting table' do
9 9 # CreateTable and DeleteTable
10 10 it 'performs CreateTable and DeleteTable' do
11   - table = Dynamoid::Adapter.create_table('CreateTable', :id, :range_key => :created_at)
  11 + table = Dynamoid::Adapter.create_table('CreateTable', :id, :range_key => { :created_at => :number })
12 12
13 13 Dynamoid::Adapter.connection.tables.collect{|t| t.name}.should include 'CreateTable'
14 14
@@ -20,7 +20,7 @@
20 20 before(:all) do
21 21 Dynamoid::Adapter.create_table('dynamoid_tests_TestTable1', :id) unless Dynamoid::Adapter.list_tables.include?('dynamoid_tests_TestTable1')
22 22 Dynamoid::Adapter.create_table('dynamoid_tests_TestTable2', :id) unless Dynamoid::Adapter.list_tables.include?('dynamoid_tests_TestTable2')
23   - Dynamoid::Adapter.create_table('dynamoid_tests_TestTable3', :id, :range_key => :range) unless Dynamoid::Adapter.list_tables.include?('dynamoid_tests_TestTable3')
  23 + Dynamoid::Adapter.create_table('dynamoid_tests_TestTable3', :id, :range_key => { :range => :number }) unless Dynamoid::Adapter.list_tables.include?('dynamoid_tests_TestTable3')
24 24 end
25 25
26 26 # GetItem, PutItem and DeleteItem
156 spec/dynamoid/adapter/local_spec.rb
@@ -4,239 +4,239 @@
4 4 describe Dynamoid::Adapter::Local do
5 5
6 6 unless ENV['ACCESS_KEY'] && ENV['SECRET_KEY']
7   -
  7 +
8 8 # BatchGetItem
9 9 it 'performs BatchGetItem with singular keys' do
10 10 Dynamoid::Adapter.create_table('table1', :id)
11 11 Dynamoid::Adapter.put_item('table1', {:id => '1', :name => 'Josh'})
12 12 Dynamoid::Adapter.create_table('table2', :id)
13   - Dynamoid::Adapter.put_item('table2', {:id => '1', :name => 'Justin'})
14   -
  13 + Dynamoid::Adapter.put_item('table2', {:id => '1', :name => 'Justin'})
  14 +
15 15 results = Dynamoid::Adapter.batch_get_item('table1' => '1', 'table2' => '1')
16 16 results.size.should == 2
17 17 results['table1'].should include({:name => 'Josh', :id => '1'})
18 18 results['table2'].should include({:name => 'Justin', :id => '1'})
19 19 end
20   -
  20 +
21 21 it 'performs BatchGetItem with multiple keys' do
22 22 Dynamoid::Adapter.create_table('table1', :id)
23 23 Dynamoid::Adapter.put_item('table1', {:id => '1', :name => 'Josh'})
24 24 Dynamoid::Adapter.put_item('table1', {:id => '2', :name => 'Justin'})
25   -
  25 +
26 26 results = Dynamoid::Adapter.batch_get_item('table1' => ['1', '2'])
27 27 results.size.should == 1
28 28 results['table1'].should include({:name => 'Josh', :id => '1'})
29 29 results['table1'].should include({:name => 'Justin', :id => '2'})
30 30 end
31   -
  31 +
32 32 it 'performs BatchGetItem with range keys' do
33   - Dynamoid::Adapter.create_table('table1', :id, :range_key => :range)
  33 + Dynamoid::Adapter.create_table('table1', :id, :range_key => { :range => :string })
34 34 Dynamoid::Adapter.put_item('table1', {:id => '1', :range => 1.0})
35 35 Dynamoid::Adapter.put_item('table1', {:id => '2', :range => 2.0})
36   -
  36 +
37 37 results = Dynamoid::Adapter.batch_get_item('table1' => [['1', 1.0], ['2', 2.0]])
38 38 results.size.should == 1
39 39 results['table1'].should include({:id => '1', :range => 1.0})
40 40 results['table1'].should include({:id => '2', :range => 2.0})
41 41 end
42   -
  42 +
43 43 it 'performs BatchGetItem with range keys on one primary key' do
44   - Dynamoid::Adapter.create_table('table1', :id, :range_key => :range)
  44 + Dynamoid::Adapter.create_table('table1', :id, :range_key => { :range => :string })
45 45 Dynamoid::Adapter.put_item('table1', {:id => '1', :range => 1.0})
46 46 Dynamoid::Adapter.put_item('table1', {:id => '1', :range => 2.0})
47   -
  47 +
48 48 results = Dynamoid::Adapter.batch_get_item('table1' => [['1', 1.0], ['1', 2.0]])
49 49 results.size.should == 1
50 50 results['table1'].should include({:id => '1', :range => 1.0})
51 51 results['table1'].should include({:id => '1', :range => 2.0})
52 52 end
53   -
  53 +
54 54 # CreateTable
55 55 it 'performs CreateTable' do
56 56 Dynamoid::Adapter.create_table('Test Table', :id)
57   -
  57 +
58 58 Dynamoid::Adapter.list_tables.should include 'Test Table'
59 59 end
60   -
  60 +
61 61 # DeleteItem
62 62 it 'performs DeleteItem' do
63 63 Dynamoid::Adapter.create_table('table1', :id)
64 64 Dynamoid::Adapter.put_item('table1', {:id => '1', :name => 'Josh'})
65   -
  65 +
66 66 Dynamoid::Adapter.delete_item('table1', '1')
67   -
  67 +
68 68 Dynamoid::Adapter.data['table1'][:data].should be_empty
69 69 end
70   -
  70 +
71 71 it 'performs DeleteItem for an item that does not exist' do
72 72 Dynamoid::Adapter.create_table('table1', :id)
73   -
  73 +
74 74 Dynamoid::Adapter.delete_item('table1', '1')
75   -
  75 +
76 76 Dynamoid::Adapter.data['table1'][:data].should be_empty
77 77 end
78   -
  78 +
79 79 # DeleteTable
80 80 it 'performs DeleteTable' do
81 81 Dynamoid::Adapter.create_table('table1', :id)
82   -
  82 +
83 83 Dynamoid::Adapter.delete_table('table1')
84   -
  84 +
85 85 Dynamoid::Adapter.data['table1'].should be_nil
86 86 end
87   -
  87 +
88 88 # DescribeTable
89   -
  89 +
90 90 # GetItem
91 91 it "performs GetItem for an item that does not exist" do
92 92 Dynamoid::Adapter.create_table('Test Table', :id)
93   -
  93 +
94 94 Dynamoid::Adapter.get_item('Test Table', '1').should be_nil
95 95 end
96   -
  96 +
97 97 it "performs GetItem for an item that does exist" do
98 98 Dynamoid::Adapter.create_table('Test Table', :id)
99 99 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
100   -
  100 +
101 101 Dynamoid::Adapter.get_item('Test Table', '1').should == {:id => '1', :name => 'Josh'}
102 102 end
103   -
  103 +
104 104 it "performs GetItem for an item with a range key" do
105   - Dynamoid::Adapter.create_table('Test Table', :id, :range_key => :range)
  105 + Dynamoid::Adapter.create_table('Test Table', :id, :range_key => { :range => :number })
106 106 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :range => 1.0})
107   -
  107 +
108 108 Dynamoid::Adapter.get_item('Test Table', '1').should be_nil
109 109 Dynamoid::Adapter.get_item('Test Table', '1', 1.0).should == {:id => '1', :range => 1.0}
110 110 end
111   -
  111 +
112 112 # ListTables
113 113 it 'performs ListTables' do
114 114 Dynamoid::Adapter.create_table('Table1', :id)
115 115 Dynamoid::Adapter.create_table('Table2', :id)
116   -
  116 +
117 117 Dynamoid::Adapter.list_tables.should include 'Table1'
118 118 Dynamoid::Adapter.list_tables.should include 'Table2'
119 119 end
120   -
  120 +
121 121 # PutItem
122 122 it 'performs PutItem for an item that does not exist' do
123 123 Dynamoid::Adapter.create_table('Test Table', :id)
124 124 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
125   -
  125 +
126 126 Dynamoid::Adapter.data['Test Table'].should == {:hash_key=>:id, :range_key=>nil, :data=>{"1."=>{:id=>"1", :name=>"Josh"}}}
127 127 end
128   -
  128 +
129 129 it 'puts an item twice and overwrites an existing item' do
130 130 Dynamoid::Adapter.create_table('Test Table', :id)
131 131 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
132 132 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Justin'})
133   -
134   - Dynamoid::Adapter.data['Test Table'].should == {:hash_key=>:id, :range_key=>nil, :data=>{"1."=>{:id=>"1", :name=>"Justin"}}}
  133 +
  134 + Dynamoid::Adapter.data['Test Table'].should == {:hash_key=>:id, :range_key=>nil, :data=>{"1."=>{:id=>"1", :name=>"Justin"}}}
135 135 end
136   -
  136 +
137 137 it 'puts an item twice and does not overwrite an existing item if the range key is not the same' do
138   - Dynamoid::Adapter.create_table('Test Table', :id, :range_key => :range)
  138 + Dynamoid::Adapter.create_table('Test Table', :id, :range_key => { :range => :number })
139 139 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Justin', :range => 1.0})
140 140 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Justin', :range => 2.0})
141   -
  141 +
142 142 Dynamoid::Adapter.data['Test Table'].should == {:hash_key=>:id, :range_key=>:range, :data=>{"1.1.0"=>{:id=>"1", :name=>"Justin", :range => 1.0}, "1.2.0" => {:id=>"1", :name=>"Justin", :range => 2.0}}}
143 143 end
144   -
  144 +
145 145 it 'puts an item twice and does overwrite an existing item if the range key is the same' do
146   - Dynamoid::Adapter.create_table('Test Table', :id, :range_key => :range)
  146 + Dynamoid::Adapter.create_table('Test Table', :id, :range_key => { :range => :number })
147 147 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh', :range => 1.0})
148 148 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Justin', :range => 1.0})
149   -
150   - Dynamoid::Adapter.data['Test Table'].should == {:hash_key=>:id, :range_key=>:range, :data=>{"1.1.0"=>{:id=>"1", :name=>"Justin", :range => 1.0}}}
  149 +
  150 + Dynamoid::Adapter.data['Test Table'].should == {:hash_key=>:id, :range_key=>:range, :data=>{"1.1.0"=>{:id=>"1", :name=>"Justin", :range => 1.0}}}
151 151 end
152   -
153   -
  152 +
  153 +
154 154 # Query
155 155 it 'performs query on a table and returns items' do
156 156 Dynamoid::Adapter.create_table('Test Table', :id)
157 157 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
158   -
159   - Dynamoid::Adapter.query('Test Table', :hash_value => '1').should == { :id=> '1', :name=>"Josh" }
  158 +
  159 + Dynamoid::Adapter.query('Test Table', :hash_value => '1').should == [{ :id=> '1', :name=>"Josh" }]
160 160 end
161   -
  161 +
162 162 it 'performs query on a table and returns items if there are multiple items' do
163 163 Dynamoid::Adapter.create_table('Test Table', :id)
164 164 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
165 165 Dynamoid::Adapter.put_item('Test Table', {:id => '2', :name => 'Justin'})
166   -
167   - Dynamoid::Adapter.query('Test Table', :hash_value => '1').should == { :id=> '1', :name=>"Josh" }
  166 +
  167 + Dynamoid::Adapter.query('Test Table', :hash_value => '1').should == [{ :id=> '1', :name=>"Josh" }]
168 168 end
169   -
  169 +
170 170 context 'range queries' do
171 171 before do
172   - Dynamoid::Adapter.create_table('Test Table', :id, :range_key => :range)
  172 + Dynamoid::Adapter.create_table('Test Table', :id, :range_key => { :range => :number })
173 173 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :range => 1.0})
174   - Dynamoid::Adapter.put_item('Test Table', {:id => '1', :range => 2.0})
  174 + Dynamoid::Adapter.put_item('Test Table', {:id => '1', :range => 2.0})
175 175 end
176   -
177   - it 'performs query on a table with a range and selects items in a range' do
  176 +
  177 + it 'performs query on a table with a range and selects items in a range' do
178 178 Dynamoid::Adapter.query('Test Table', :hash_value => '1', :range_value => 0.0..3.0).should =~ [{:id => '1', :range => 1.0}, {:id => '1', :range => 2.0}]
179 179 end
180   -
181   - it 'performs query on a table with a range and selects items greater than' do
  180 +
  181 + it 'performs query on a table with a range and selects items greater than' do
182 182 Dynamoid::Adapter.query('Test Table', :hash_value => '1', :range_greater_than => 1.0).should =~ [{:id => '1', :range => 2.0}]
183 183 end
184   -
185   - it 'performs query on a table with a range and selects items less than' do
  184 +
  185 + it 'performs query on a table with a range and selects items less than' do
186 186 Dynamoid::Adapter.query('Test Table', :hash_value => '1', :range_less_than => 2.0).should =~ [{:id => '1', :range => 1.0}]
187 187 end
188   -
189   - it 'performs query on a table with a range and selects items gte' do
  188 +
  189 + it 'performs query on a table with a range and selects items gte' do
190 190 Dynamoid::Adapter.query('Test Table', :hash_value => '1', :range_gte => 1.0).should =~ [{:id => '1', :range => 1.0}, {:id => '1', :range => 2.0}]
191 191 end
192   -
193   - it 'performs query on a table with a range and selects items lte' do
  192 +
  193 + it 'performs query on a table with a range and selects items lte' do
194 194 Dynamoid::Adapter.query('Test Table', :hash_value => '1', :range_lte => 2.0).should =~ [{:id => '1', :range => 1.0}, {:id => '1', :range => 2.0}]
195 195 end
196   -
  196 +
197 197 end
198   -
  198 +
199 199 # Scan
200 200 it 'performs scan on a table and returns items' do
201 201 Dynamoid::Adapter.create_table('Test Table', :id)
202 202 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
203   -
  203 +
204 204 Dynamoid::Adapter.scan('Test Table', :name => 'Josh').should == [{ :id=> '1', :name=>"Josh" }]
205 205 end
206   -
  206 +
207 207 it 'performs scan on a table and returns items if there are multiple items but only one match' do
208 208 Dynamoid::Adapter.create_table('Test Table', :id)
209 209 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
210 210 Dynamoid::Adapter.put_item('Test Table', {:id => '2', :name => 'Justin'})
211   -
  211 +
212 212 Dynamoid::Adapter.scan('Test Table', :name => 'Josh').should == [{ :id=> '1', :name=>"Josh" }]
213 213 end
214   -
  214 +
215 215 it 'performs scan on a table and returns multiple items if there are multiple matches' do
216 216 Dynamoid::Adapter.create_table('Test Table', :id)
217 217 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
218 218 Dynamoid::Adapter.put_item('Test Table', {:id => '2', :name => 'Josh'})
219   -
  219 +
220 220 Dynamoid::Adapter.scan('Test Table', :name => 'Josh').should == [{ :id=> '1', :name=>"Josh" }, { :id=> '2', :name=>"Josh" }]
221 221 end
222   -
  222 +
223 223 it 'performs scan on a table and returns all items if no criteria are specified' do
224 224 Dynamoid::Adapter.create_table('Test Table', :id)
225 225 Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
226 226 Dynamoid::Adapter.put_item('Test Table', {:id => '2', :name => 'Josh'})
227   -
  227 +
228 228 Dynamoid::Adapter.scan('Test Table', {}).should == [{ :id=> '1', :name=>"Josh" }, { :id=> '2', :name=>"Josh" }]
229 229 end
230   -
  230 +
231 231 # UpdateItem
232   -
  232 +
233 233 # UpdateTable
234   -
  234 +
235 235 protected
236   -
  236 +
237 237 def setup_value(table, key, value)
238 238 Dynamoid::Adapter.data[table][key] = value
239 239 end
240   -
  240 +
241 241 end
242 242 end
1  spec/dynamoid/associations/association_spec.rb
@@ -3,6 +3,7 @@
3 3 describe "Dynamoid::Associations::Association" do
4 4
5 5 before do
  6 + Subscription.create_table_if_neccessary
6 7 @magazine = Magazine.create
7 8 end
8 9
84 spec/dynamoid/criteria/chain_spec.rb
@@ -7,83 +7,123 @@
7 7 @user = User.create(:name => 'Josh', :email => 'josh@joshsymonds.com', :password => 'Test123')
8 8 @chain = Dynamoid::Criteria::Chain.new(User)
9 9 end
10   -
  10 +
11 11 it 'finds matching index for a query' do
12 12 @chain.query = {:name => 'Josh'}
13 13 @chain.send(:index).should == User.indexes[[:name]]
14   -
  14 +
15 15 @chain.query = {:email => 'josh@joshsymonds.com'}
16 16 @chain.send(:index).should == User.indexes[[:email]]
17   -
  17 +
18 18 @chain.query = {:name => 'Josh', :email => 'josh@joshsymonds.com'}
19 19 @chain.send(:index).should == User.indexes[[:email, :name]]
20 20 end
21   -
  21 +
22 22 it 'finds matching index for a range query' do
23 23 @chain.query = {"created_at.gt" => @time - 1.day}
24 24 @chain.send(:index).should == User.indexes[[:created_at]]
25   -
  25 +
26 26 @chain.query = {:name => 'Josh', "created_at.lt" => @time - 1.day}
27 27 @chain.send(:index).should == User.indexes[[:created_at, :name]]
28 28 end
29   -
  29 +
30 30 it 'does not find an index if there is not an appropriate one' do
31 31 @chain.query = {:password => 'Test123'}
32 32 @chain.send(:index).should be_nil
33   -
  33 +
34 34 @chain.query = {:password => 'Test123', :created_at => @time}
35 35 @chain.send(:index).should be_nil
36 36 end
37   -
  37 +
38 38 it 'returns values for index for a query' do
39 39 @chain.query = {:name => 'Josh'}
40 40 @chain.send(:index_query).should == {:hash_value => 'Josh'}
41   -
  41 +
42 42 @chain.query = {:email => 'josh@joshsymonds.com'}
43 43 @chain.send(:index_query).should == {:hash_value => 'josh@joshsymonds.com'}
44   -
  44 +
45 45 @chain.query = {:name => 'Josh', :email => 'josh@joshsymonds.com'}
46 46 @chain.send(:index_query).should == {:hash_value => 'josh@joshsymonds.com.Josh'}
47   -
  47 +
48 48 @chain.query = {:name => 'Josh', 'created_at.gt' => @time}
49 49 @chain.send(:index_query).should == {:hash_value => 'Josh', :range_greater_than => @time.to_f}
50 50 end
51   -
  51 +
52 52 it 'finds records with an index' do
53 53 @chain.query = {:name => 'Josh'}
54 54 @chain.send(:records_with_index).should == [@user]
55   -
  55 +
56 56 @chain.query = {:email => 'josh@joshsymonds.com'}
57 57 @chain.send(:records_with_index).should == [@user]
58   -
  58 +
59 59 @chain.query = {:name => 'Josh', :email => 'josh@joshsymonds.com'}
60 60 @chain.send(:records_with_index).should == [@user]
61 61 end
62   -
  62 +
63 63 it 'returns records with an index for a ranged query' do
64 64 @chain.query = {:name => 'Josh', "created_at.gt" => @time - 1.day}
65 65 @chain.send(:records_with_index).should == [@user]
66   -
  66 +
67 67 @chain.query = {:name => 'Josh', "created_at.lt" => @time + 1.day}
68 68 @chain.send(:records_with_index).should == [@user]
69 69 end
70   -
  70 +
71 71 it 'finds records without an index' do
72 72 @chain.query = {:password => 'Test123'}
73 73 @chain.send(:records_without_index).should == [@user]
74 74 end
75   -
  75 +
76 76 it 'defines each' do
77 77 @chain.query = {:name => 'Josh'}
78 78 @chain.each {|u| u.update_attribute(:name, 'Justin')}
79   -
  79 +
80 80 User.find(@user.id).name.should == 'Justin'
81 81 end
82   -
  82 +
83 83 it 'includes Enumerable' do
84 84 @chain.query = {:name => 'Josh'}
85   -
  85 +
86 86 @chain.collect {|u| u.name}.should == ['Josh']
87 87 end
88   -
  88 +
  89 + it 'finds range querys' do
  90 + @chain = Dynamoid::Criteria::Chain.new(Tweet)
  91 + @chain.query = { :id => 'test' }
  92 + @chain.send(:range?).should be_true
  93 +
  94 + @chain.query = {:id => 'test', :group => 'xx'}
  95 + @chain.send(:range?).should be_true
  96 +
  97 + @chain.query = { :group => 'xx' }
  98 + @chain.send(:range?).should be_false
  99 +
  100 + @chain.query = { :group => 'xx', :msg => 'hai' }
  101 + @chain.send(:range?).should be_false
  102 + end
  103 +
  104 + it 'finds tweets using range querys' do
  105 + Tweet.create(:id => "x", :group => "one")
  106 + Tweet.create(:id => "x", :group => "two")
  107 + @tweet = Tweet.create(:id => "xx", :group => "two")
  108 +
  109 + @chain = Dynamoid::Criteria::Chain.new(Tweet)
  110 + @chain.query = { :id => "x" }
  111 + @chain.send(:records_with_range).size.should == 2
  112 +
  113 + @chain.all.size.should == 2
  114 + @chain.limit(1).size.should == 1
  115 +
  116 + @chain = Dynamoid::Criteria::Chain.new(Tweet)
  117 + @chain.query = { :id => "x" }
  118 + all = @chain.all
  119 +
  120 + @chain = Dynamoid::Criteria::Chain.new(Tweet)
  121 + @chain.query = { :id => "x" }
  122 + @chain.start(all.first)
  123 + @chain.all.should eq(all[1..-1])
  124 +
  125 + @chain = Dynamoid::Criteria::Chain.new(Tweet)
  126 + @chain.query = { :id => "xx", :group => "two" }
  127 + @chain.send(:records_with_range).should == [@tweet]
  128 + end
89 129 end
16 spec/dynamoid/criteria_spec.rb
@@ -35,4 +35,20 @@
35 35 end
36 36 end
37 37
  38 + it 'returns n records' do
  39 + User.limit(1).size.should eq(1)
  40 + 5.times { |i| User.create(:name => 'Josh', :email => 'josh_#{i}@joshsymonds.com') }
  41 + User.where(:name => 'Josh').all.size.should == 6
  42 + User.where(:name => 'Josh').limit(2).size.should == 2
  43 + end
  44 +
  45 + it 'start with a reord' do
  46 + 5.times { |i| User.create(:name => 'Josh', :email => 'josh_#{i}@joshsymonds.com') }
  47 + all = User.all
  48 + User.start(all[3]).all.should eq(all[4..-1])
  49 +
  50 + all = User.where(:name => 'Josh').all
  51 + User.where(:name => 'Josh').start(all[3]).all.should eq(all[4..-1])
  52 + end
  53 +
38 54 end
6 spec/dynamoid/persistence_spec.rb
@@ -145,10 +145,4 @@
145 145 lambda {Address.create(hash)}.should_not raise_error
146 146 end
147 147
148   - it 'works with a HashWithIndifferentAccess' do
149   - hash = ActiveSupport::HashWithIndifferentAccess.new("test" => "hi", "hello" => "there")
150   -
151   - lambda {Address.create(hash)}.should_not raise_error
152   - end
153   -
154 148 end

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.