rom-dynamodb 2.0.2 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 828f463a357464de133be0aa1c4ac5f72ffecff6
4
- data.tar.gz: a42ab2427b122c3c8c6c9b7fb2b6fc9c069743fa
3
+ metadata.gz: 5dfe29ecc7e7f6bc3d64a8cdb9413034b1a36ee7
4
+ data.tar.gz: fab828906c5c5c0778f14632713b7dcbf82b970b
5
5
  SHA512:
6
- metadata.gz: 906b3e36665c1741af7c7076c1484c867a738174a47cc4ef00edb22729723b679048fdd416988ff56feff7290d79dba778bebc77c62877c950c885486437a525
7
- data.tar.gz: 38dd2529874f699c5d38f93299cb4fe78c16f05d8dc0c1981d427a2f16268bc564cc947d8d358f215991039255515725922307530126814d2d80e85073bb7c82
6
+ metadata.gz: 06a62bc7f70a74398de8fffc03aa0ba35995daf6915717a130c985155078e28d9a01dda35daf485de909f269b3b1eb463c2835c658f03ca6a8756a4d7d9aadec
7
+ data.tar.gz: 840044b23db325be06a58b74dc2d5861b7aa74c020e0d120ad72a635569f32526fd47d1b9dc5a8e10930ad2acc5b8092f71d62213ef8e40101dab9a2ce6e4118
@@ -10,6 +10,4 @@ Describe what should happen
10
10
  Describe what happens instead
11
11
 
12
12
  ### System configuration
13
- **Drumbeat version**:
14
-
15
13
  **Ruby version**:
@@ -1,4 +1,5 @@
1
- sudo: false
1
+ services:
2
+ - docker
2
3
 
3
4
  sudo: required
4
5
 
@@ -6,12 +7,15 @@ before_install:
6
7
  - docker-compose pull
7
8
  - docker-compose build
8
9
 
9
- script: docker-compose run rom bundle exec rspec
10
+ script:
11
+ - docker-compose run rom bundle exec rspec
12
+
13
+ after_success:
14
+ - bundle exec codeclimate-test-reporter
10
15
 
11
16
  env:
12
17
  global:
13
- - CI=true
14
- - TRAVIS=true
18
+ - secure: "y+WKE+O64Mk9/MIiVDsHY12FsIDv31c5kGT1PD8X3bGzDLSs2RsxH+6aAPKj8soQIU6vN1cpXzHOI9rmgSa/jPwXN/TVhXNQUEaQGaCQjlucx4D7M0SjXxrEVcFsXeQpkb+sDH7zeRDj9mxnpzpHzFc1uLBodKUKFoqDJP67rYzdfHYTGettvGxPBR38gZxSPGejVNbb+cnY8bvUECK/Qn+0MTR3xjkZi+y4PKsj4ZlN7+4m8GOQt/S/9SK++5MJtlXXRtC7tufXZ3QJvmyfB/FbDYm5/e+ovAWVFI2s62/IuE5qdoEjBK3j8cKvTErrBzp1mo9JtM1QeJGAGuOroM8ki9cjPV+mtuFdIq7pzjBOK8ZIWGCQCljuIvyWZ88jZQzqkjgcREvBxGJa5u/maaNduUXWdfR/Q2DFazX7SfrD5CLXvinemVdPpo8/D0NhduSW8pgSKF0E9WBGng2z3BP40L+l7aTFfzrrirdzDNU1KsOzfDDs9+h9Rq8uwIhKiZhiPr+4ztXteNmaJMaeL9KnAgIhZGW1EYUkC1gAVXRkzaFFh3tnRhKAzU1LJxl2qskf0MJFgc/hr3hTHvWgYCQYe2QCZdulqb7HdnUH55Twsha+XVtMp25GxKVrTN4ET5L971xALGvTRxKaO2lekZmVxr4qauhyoXMUc1pCN4g="
15
19
  - secure: "kQuM1k5J7COei8KyvabP/YROhoUFlLZkieaDQLFyOj2cobYW6bYsfzuWkNKaNpBrVjEenumLHwHg6P9vVmI7FQ1IEUW2QkAYSUUni/jRBK53jICIINWo6YLWgLQdpdkBnCzMv8MVXBlrqLgqcUn3aUXEtOhM9uHQbhknBx+z+Yja8tdjt6m8scBmqGj8a3zH3Pypi0PbH7fNHUXzAGx/CUyB/s58zUkm9SRs7uR5TRVKtmUyXaroTxmKL9iDIuArjMsOTzu/Xr1kwdlg1kmzB//j0basRzYPOSNdJAGKEdwXo6ul7ZGc993iIJGS/+LZuLgrOcdjHklBSWlduQABEqOFpL76cG7RUH5Mo58Wqd+ftR/Lz5+vEDxpj1JXOUzKv4yB/EA3hTxnK7vCvp5pYSAD+sqtTn5f8TpcxaKYwR0n5YUtV9E8JhZ4I5QvWONU1zXsb9ulhGBFRhZniFKNEsBoapdCA+rAEXLJS0D7ODEE7degsv1uMhO6QPt1sAWvHZvZh01XvlEM9p7aeTOnD72DjKw47GTU/GL6Ekl8er+wubk/ZGBKMQ9YZXiT2jQwgIJyKSWw19s0Ru9otLhrSgwY0eHhvQuyYWL2DwFV2Bcrx1Glgj96+nSzj8g/42NTL1fX5cYAAPYjnqWLc8yuqF+ioF24Yq22CuTQhn/u0BE="
16
20
 
17
21
  addons:
@@ -19,12 +23,6 @@ addons:
19
23
  repo_token:
20
24
  secure: "AL3aOEHyWvXkXGne+U3n6ToU0Ubp8yiy3y6gqo/IF2DsN133ynEwcUsif81eIeMTYa+LH4PfDV4iImbwrawhJdyr1ghAqC98k2xfzD5oBSOO8b6gj5k3fRLGRIlGcFwrSSZm9t3F3J3teU3QeaeOW5QhJrtGtlApVvMRzUQPmPbFKjSbzg9oXnl6rWpghUGPjkJZ1zJARZqYx6yJ5JYoRFX7xPcwmwvMa3tCnLh3wfpVCexsa33gssFCGUuh/qU2aLO9f/EyLV95nkxNoXdNoMAajuddDAUK2GQSBLuGijyydUl+x9ZLEsfMJbW3xFqeYVQzMiRXMku0kp7NuzSF9TfPP4TSv5EIJAuVujB3u7sc+jRGEy+m+WawUunu8BmX1l9vjBMcMUonqcsToTBHf9u1nLBdJMhwk0WQkvcJ31g4lrc5y4wut/mRdylBTrplAJAwcHfXnM7ADWL4DN3Yx+Vo+lPgkCrtnsMST2PUcyW2v+hln/BR9qB9PGPC121o7D+BNNuT4oLrw7tebnl3CWI9cpb2jaZl1cVrINhOnApukO1oqxOE2xGVCkNIZ4RIDLlj1WVbbLBN/C/dREjf6n/jNT5LylWEULHofYpit2+1o8KSHjT7aCMLwWlyIuAyYz45WSPviy4oyrjeLUGUo3u15EjJeGcKhXtrtreVdvk="
21
25
 
22
- services:
23
- - docker
24
-
25
- # after_success:
26
- # - CI=true TRAVIS=true coveralls --verbose
27
-
28
26
  deploy:
29
27
  provider: rubygems
30
28
  api_key:
@@ -0,0 +1,6 @@
1
+ #### v2.2.0
2
+ - Added ability to use `where { ... }` directly from the relation
3
+ - Using `:expression_attribute_values`, `:expression_attribute_names` and `:key_condition_expression` when querying.
4
+
5
+ #### v2.0.2
6
+ - Initial release of adapter
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # ROM DynamoDB Adapter
2
+ [![Gem Version](https://badge.fury.io/rb/rom-dynamodb.svg)](https://badge.fury.io/rb/rom-dynamodb) [![GitHub](https://img.shields.io/badge/github-davidkelley%2From--dynamo-blue.svg)](https://github.com/davidkelley/rom-dynamodb) [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/github/davidkelley/rom-dynamodb) [![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](#license) [![Gitter](http://img.shields.io/badge/gitter-rom--rb-red.svg)](https://gitter.im/rom-rb/chat)
2
3
 
3
- [![Gem Version](https://badge.fury.io/rb/rom-dynamodb.svg)](https://badge.fury.io/rb/rom-dynamodb) [![Code Climate](https://codeclimate.com/github/davidkelley/rom-dynamodb/badges/gpa.svg)](https://codeclimate.com/github/davidkelley/rom-dynamodb) [![Test Coverage](https://codeclimate.com/github/davidkelley/rom-dynamodb/badges/coverage.svg)](https://codeclimate.com/github/davidkelley/rom-dynamodb/coverage) [![Dependency Status](https://gemnasium.com/badges/github.com/davidkelley/rom-dynamodb.svg)](https://gemnasium.com/github.com/davidkelley/rom-dynamodb)
4
+ [![Code Climate](https://codeclimate.com/github/davidkelley/rom-dynamodb/badges/gpa.svg)](https://codeclimate.com/github/davidkelley/rom-dynamodb) [![Coverage Status](https://coveralls.io/repos/github/davidkelley/rom-dynamodb/badge.svg?branch=master)](https://coveralls.io/github/davidkelley/rom-dynamodb?branch=master) [![Dependency Status](https://gemnasium.com/badges/github.com/davidkelley/rom-dynamodb.svg)](https://gemnasium.com/github.com/davidkelley/rom-dynamodb)
4
5
  [![Build Status](https://travis-ci.org/davidkelley/rom-dynamodb.svg?branch=master)](https://travis-ci.org/davidkelley/rom-dynamodb) [![Inline docs](http://inch-ci.org/github/davidkelley/rom-dynamodb.svg?branch=master)](http://inch-ci.org/github/davidkelley/rom-dynamodb)
5
6
 
6
7
  ---
7
8
 
8
- TODO: Delete this and the text above, and describe your gem
9
+ This adapter uses [ROM (>= 2.0.0)](http://rom-rb.org/) to provide an easy-to-use, clean and understandable interface for [AWS DynamoDB](https://aws.amazon.com/documentation/dynamodb/).
9
10
 
10
11
  ## Installation
11
12
 
@@ -25,11 +26,150 @@ Or install it yourself as:
25
26
 
26
27
  ## Usage
27
28
 
28
- TODO
29
+ The following container setup is for demonstration purposes only. You should follow the standard way of integrating ROM into your environment, as [documented here](http://rom-rb.org/learn/advanced/flat-style-setup/).
30
+
31
+ ```ruby
32
+ require 'rom/dynamodb'
33
+
34
+ TABLE = "my-dynamodb-users-table"
35
+
36
+ # any other AWS::DynamoDB::Client options
37
+ credentials = { region: 'us-east-1' }
38
+
39
+ container = ROM.container(:dynamodb, credentials) do |rom|
40
+ rom.relation(:users) do
41
+ # Key Schema: id<Hash>
42
+ dataset TABLE
43
+
44
+ def by_id(val)
45
+ where { id == val }
46
+ end
47
+ end
48
+
49
+ rom.commands(:users) do
50
+ FILTER = Functions[:symbolize_keys] >> Functions[:accept_keys, [:id]]
51
+
52
+ define(:create) do
53
+ KEYS = %w(id name)
54
+ result :one
55
+ input Functions[:accept_keys, KEYS]
56
+ end
57
+
58
+ define(:delete) do
59
+ result :one
60
+ input FILTER
61
+ end
62
+
63
+ define(:update) do
64
+ result :one
65
+ input FILTER
66
+ end
67
+ end
68
+ end
69
+
70
+ relation = container.relation(:users)
71
+
72
+ relation.count # => 1234
73
+
74
+ relation.where { id == 1 }.one! # => { id: 1, name: "David" }
75
+
76
+ relation.info # => <Hash> DynamoDB Table Information
77
+
78
+ relation.status # => :active
79
+
80
+ # create a new user
81
+ create = container.commands[:users][:create]
82
+ user = create.call({ id: 2, name: "James" })
83
+
84
+ # update an existing user
85
+ update = container.commands[:users][:update]
86
+ update.by_id(user[:id]).call(name: "Mark")
87
+
88
+ relation.where(id: user[:id]) { id == id }.one! # => { id: 2, name: "Mark" }
89
+
90
+ # delete an existing user
91
+ delete = container.commands[:users][:delete]
92
+ delete.by_id(user[:id]).call
93
+ ```
94
+ ---
95
+
96
+ #### Querying a composite key DynamoDB Table
97
+
98
+ ```ruby
99
+ container = ROM.container(:dynamodb, credentials) do |rom|
100
+ rom.relation(:logs) do
101
+ # Key Schema: host<Hash>, timestamp<Range>
102
+ dataset "my-logs-table"
103
+
104
+ def by_host(ip)
105
+ where { host == ip }
106
+ end
107
+
108
+ def after_timestamp(time)
109
+ where { timestamp > time }
110
+ end
111
+
112
+ def before_timestamp(time)
113
+ where { timestamp < time }
114
+ end
115
+ end
116
+
117
+ rom.commands(:logs) do
118
+ define(:create) do
119
+ KEYS = %w(host timestamp message)
120
+ result :one
121
+ input Functions[:accept_keys, KEYS]
122
+ end
123
+ end
124
+ end
125
+
126
+ num_of_logs = 20
127
+
128
+ host = "192.168.0.1"
129
+
130
+ logs = (1..num_of_logs).to_a.collect do |i|
131
+ { host: host, timestamp: Time.now.to_f + (i * 60), message: "some message" }
132
+ end
133
+
134
+ # create fake logs
135
+ container.commands[:logs][:create].call(logs)
136
+
137
+ relation = container.relation(:logs)
138
+
139
+ relation.count == num_of_logs # => true
140
+
141
+ all = relation.where(ip: host) { host == ip }.after(0).to_a # => [{host: "192.168.0.1", ... }, ...]
142
+
143
+ all.size # => 20
144
+
145
+ before = relation.where(ip: host) { [host == ip, timestamp < (Time.now.to_f + 60 * 60)] }.limit(1).to_a
146
+
147
+ before.size # => 1
148
+
149
+ before.first == logs.first # => true
150
+
151
+ offset = { ip: host, timestamp: logs[-2][:timestamp] }
152
+
153
+ last = relation.where(ip: host) { ip == host }.descending.after(0).offset(offset).limit(1).one!
154
+
155
+ last == logs.last # => true
156
+ ```
157
+ ---
29
158
 
30
159
  ## Development
31
160
 
32
- TODO
161
+ All development takes place inside [Docker Compose](). Run the following commands to get setup:
162
+
163
+ ```
164
+ $ docker-compose pull
165
+ $ docker-compose build
166
+ ```
167
+
168
+ You can then begin developing, running RSpec tests with the following command:
169
+
170
+ ```
171
+ $ docker-compose run rom rspec [args...]
172
+ ```
33
173
 
34
174
  ## Contributing
35
175
 
@@ -13,7 +13,13 @@ services:
13
13
  dockerfile: Dockerfile.development
14
14
  command: rspec
15
15
  environment:
16
- DYNAMO_ENDPOINT: 'http://dynamo:8000'
16
+ - DYNAMO_ENDPOINT=http://dynamo:8000
17
+ - CI
18
+ - TRAVIS
19
+ - TRAVIS_BRANCH
20
+ - TRAVIS_JOB_ID
21
+ - TRAVIS_PULL_REQUEST
22
+ - CODECLIMATE_REPO_TOKEN
17
23
  volumes:
18
24
  - .:/app
19
25
  links:
@@ -10,7 +10,7 @@ module ROM
10
10
 
11
11
  def with_tuple(tuple, attributes)
12
12
  data = tuple.is_a?(Hash) ? tuple : tuple.to_h
13
- relation.update(input[data], attributes.to_h)
13
+ source.update(input[data], attributes.to_h)
14
14
  end
15
15
  end
16
16
  end
@@ -1,8 +1,40 @@
1
+ require "rom/dynamodb/dataset/where_clause"
2
+
1
3
  module ROM
2
4
  module DynamoDB
3
5
  class Dataset
6
+ # @!macro rom.dynamodb.dataset.chain_note
7
+ # @note This method can be chained with other relational methods.
8
+
9
+ # @!macro rom.dynamodb.dataset.order_note
10
+ # @note Items with the same partition key value are stored in sorted
11
+ # order by sort key. If the sort key data type is Number, the results
12
+ # are stored in numeric order. For type String, the results are stored
13
+ # in order of ASCII character code values. For type Binary, DynamoDB
14
+ # treats each byte of the binary data as unsigned.
15
+
16
+ # @!macro rom.dynamodb.dataset.series_note
17
+ # @note Whilst the naming and parameters of this method indicate a
18
+ # time-series based operation, DynamoDB can perform a range query
19
+ # for any orderable data.
20
+
21
+ # @attr_reader [String] name the full name of the DynamoDB Table to query.
22
+
23
+ # @attr_reader [Symbol] operation the operation to perform on DynamoDB.
24
+
25
+ # @attr_reader [Hash] config the configuration to apply to the underlying
26
+ # DynamoDB client.
27
+
4
28
  attr_reader :name, :operation, :config
5
29
 
30
+ # @attr [Array<Hash>] queries the array of query sets to merge and use
31
+ # to query DynamoDB.
32
+
33
+ # This array of hashes is built up by chaining the
34
+ # relational methods together and when the query is finally executed,
35
+ # all the hashes in this array are merged and send to the underlying
36
+ # DynamoDB client.
37
+
6
38
  attr_accessor :queries
7
39
 
8
40
  def initialize(name:, operation: :query, config: {}, queries: [])
@@ -12,42 +44,211 @@ module ROM
12
44
  @queries = queries
13
45
  end
14
46
 
47
+ # The name of an index to query. This index can be any local secondary
48
+ # index or global secondary index on the table.
49
+ #
50
+ # @!macro rom.dynamodb.dataset.chain_note
51
+ #
52
+ # @param name [String] the name of the index to query.
53
+ #
54
+ # @return [self] the {Dataset} object the method was performed on.
15
55
  def index(name)
16
56
  restrict(index_name: name)
17
57
  end
18
58
 
59
+ # Performs a traversal of the index in ascending order on the range key.
60
+ # DynamoDB will return the results in the order in which they
61
+ # have been stored.
62
+ #
63
+ # @note This is the default behaviour.
64
+ #
65
+ # @!macro rom.dynamodb.dataset.chain_note
66
+ # @!macro rom.dynamodb.dataset.order_note
67
+ #
68
+ # @return [self] the {Dataset} object the method was performed on.
19
69
  def ascending
20
70
  restrict(scan_index_forward: true)
21
71
  end
22
72
 
73
+ # Performs a traversal of the index in descending order on the range key.
74
+ # DynamoDB reads the results in reverse order by sort key value, and
75
+ # then returns the results to the client.
76
+ #
77
+ # @note This is the default behaviour.
78
+ #
79
+ # @!macro rom.dynamodb.dataset.chain_note
80
+ # @!macro rom.dynamodb.dataset.order_note
81
+ #
82
+ # @return [self] the {Dataset} object the method was performed on.
23
83
  def descending
24
84
  restrict(scan_index_forward: false)
25
85
  end
26
86
 
87
+ # Limits the number of results returned by the query or scan operation.
88
+ #
89
+ # The maximum number of items to evaluate (not necessarily the number of
90
+ # matching items). If DynamoDB processes the number of items up to the
91
+ # limit while processing the results, it stops the operation and returns
92
+ # the matching values up to that point.
93
+ #
94
+ # If there are more matches to be returned, the {#last_evaluated_key}
95
+ #
96
+ # @!macro rom.dynamodb.dataset.chain_note
97
+ #
98
+ # @return [self] the {Dataset} object the method was performed on.
27
99
  def limit(num = nil)
28
100
  append { { limit: num } unless num.nil? }
29
101
  end
30
102
 
103
+ # The composite key of the first item that the resulting query will
104
+ # evaluate. Use this method if you have a populated {#last_evaluated_key}.
105
+ #
106
+ # @!macro rom.dynamodb.dataset.chain_note
107
+ #
108
+ # @note When applying an offset, it must include the same composite key of
109
+ # which the index you are querying is composed from. Therefore, if your
110
+ # index has both a hash and range key, the key you provide must also have
111
+ # these.
112
+ #
113
+ # @param key [Hash] the composite offset key matching the queryable index.
114
+ #
115
+ # @return [self] the {Dataset} object the method was performed on.
31
116
  def offset(key)
32
117
  append { { exclusive_start_key: key } unless key.nil? }
33
118
  end
34
119
 
120
+ # Selects one or more keys to retrieve from the table.
121
+ #
122
+ # These keys can include scalars, sets, or elements of a JSON
123
+ # document.
124
+ #
125
+ # @!macro rom.dynamodb.dataset.chain_note
126
+ #
127
+ # @param keys [Array<String>] an array of string expressions to apply
128
+ # to the query
129
+ #
130
+ # @return [self] the {Dataset} object the method was performed on.
35
131
  def select(keys)
36
- restrict(select: "SPECIFIC_ATTRIBUTES", attributes_to_get: keys.collect(&:to_s))
132
+ restrict(projection_expression: keys.collect(&:to_s).join(","))
37
133
  end
38
134
 
135
+ # Restricts keys within a composite key, by values using a specific operand.
136
+ #
137
+ # You can compose where clauses using compartive operators from inside
138
+ # a block, allowing a greater level of flexibility.
139
+ #
140
+ # Multiple where clauses can be chained, or multiple predicates defined
141
+ # inside an array within a single clause. See the examples below.
142
+ #
143
+ # @!macro rom.dynamodb.dataset.chain_note
144
+ #
145
+ # @note The following operands are supported, :>=, :>, :<, :<=, :== and :between
146
+ #
147
+ # @example Given Table[id<Hash>,legs<Range>]
148
+ # animals = relation.where { [id == "mammal", legs > 0] }
149
+ # animals #=> [{id: "mammal", legs: 2, name: "Human"}, ...]
150
+ #
151
+ # @example Using a value mapping hash
152
+ # keys = { type: "mammal", min_legs: 2 }
153
+ # animals = relation.where(keys) { [id == type, legs > min_legs] }
154
+ # animals #=> [{id: "mammal", legs: 2, name: "Elephant"}, ...]
155
+ #
156
+ # @example Matching by exact value
157
+ # keys = { type: "mammal" }
158
+ # animals = relation.where(keys) { id == type }
159
+ # animals #=> [{id: "mammal", legs: 2, name: "Elephant"}, ...]
160
+ #
161
+ # @example Between two values
162
+ #
163
+ # @example Matching with begins_with
164
+ #
165
+ def where(maps = {}, &block)
166
+ clauses = WhereClause.new(maps).execute(&block).clauses
167
+ append(:query) do
168
+ {
169
+ expression_attribute_values: clauses.expression_attribute_values,
170
+ expression_attribute_names: clauses.expression_attribute_names,
171
+ key_condition_expression: clauses.key_condition_expression,
172
+ }
173
+ end
174
+ end
175
+
176
+ # Retrieves a key present within the composite key for this index by its
177
+ # exact value.
178
+ #
179
+ # @deprecated Use {#where} instead.
180
+ #
181
+ # @!macro rom.dynamodb.dataset.chain_note
182
+ #
183
+ # @example Given Table[id<Hash>]
184
+ # relation.equal(:id, 1).one! #=> { id: 1, ... }
185
+ #
186
+ # @example Given Table[id<Hash>,created_at<Range>]
187
+ # relation.equal(:id, 1).equal(:created_at, Time.now.to_f).one! #=> { id: 1, ... }
188
+ #
189
+ # @param key [Symbol] the key to match the provided value against.
190
+ # @param val the value to match against the key in the index.
191
+ # @param predicate [Symbol] the query predicate to apply to DynamoDB.
192
+ #
193
+ # @return [self] the {Dataset} object the method was performed on.
39
194
  def equal(key, val, predicate = :eq)
40
195
  restrict_by(key, predicate, [val])
41
196
  end
42
197
 
198
+ # Retrieves all matching range keys within the composite key for the
199
+ # index between two points.
200
+ #
201
+ # @deprecated Use {#where} instead.
202
+ #
203
+ # @!macro rom.dynamodb.dataset.chain_note
204
+ #
205
+ # @!macro rom.dynamodb.dataset.series_note
206
+ #
207
+ # @example Given Table[id<Hash>,legs<Range>]
208
+ # users = relation.equal(:id, "mammal").between(:legs, 0, 4).to_a
209
+ # users #=> [{id: "mammal", legs: 2, name: "Human"}, {id: "mammal", legs: 4, name: "Elephant"}, ...]
210
+ #
211
+ # @param key [Symbol] the key to match the provided value against.
212
+ # @param after the value to match range values after
213
+ # @param before the value to match range values before
214
+ #
215
+ # @return [self] the {Dataset} object the method was performed on.
43
216
  def between(key, after, before, predicate = :between)
44
217
  restrict_by(key, predicate, [after, before])
45
218
  end
46
219
 
220
+ # Retrieves all matching range keys within the composite key after the
221
+ # value provided.
222
+ #
223
+ # @deprecated Use {#where} instead.
224
+ #
225
+ # @!macro rom.dynamodb.dataset.chain_note
226
+ #
227
+ # @!macro rom.dynamodb.dataset.series_note
228
+ #
229
+ # @deprecated Use {#where} instead.
230
+ #
231
+ # @example Given Table[id<Hash>,legs<Range>]
232
+ # users = relation.equal(:id, "mammal").after(:legs, 0).to_a
233
+ # users #=> [{id: "mammal", legs: 2, name: "Human"}, ...]
234
+ #
235
+ # @param key [Symbol] the key to match the provided value against.
236
+ # @param after the value to match range values after
237
+ # @param predicate [String] the query predicate to apply to DynamoDB.
238
+ #
239
+ # @return [self] the {Dataset} object the method was performed on.
47
240
  def after(key, after, predicate = :ge)
48
241
  restrict_by(key, predicate, [after])
49
242
  end
50
243
 
244
+ # Retreives all matching range keys within the composite key before the
245
+ # value provided.
246
+ #
247
+ # @deprecated Use {#where} instead.
248
+ #
249
+ # @!macro rom.dynamodb.dataset.chain_note
250
+ #
251
+ # @!macro rom.dynamodb.dataset.series_note
51
252
  def before(key, before, predicate = :le)
52
253
  restrict_by(key, predicate, [before])
53
254
  end
@@ -135,6 +336,9 @@ module ROM
135
336
 
136
337
  def append(operation = :query, &block)
137
338
  result = block.call
339
+ if result[:key_condition_expression]
340
+
341
+ end
138
342
  if result
139
343
  args = { name: name, config: config, operation: operation }
140
344
  self.class.new(args.merge(queries: queries + [result].flatten))
@@ -144,17 +348,10 @@ module ROM
144
348
  end
145
349
 
146
350
  def to_update_structure(hash)
147
- values = {}
148
- maps = {}
149
- expr = 'SET ' + hash.map do |key, val|
150
- values[":#{key}"] = val
151
- maps["##{key}"] = key
152
- "##{key}=:#{key}"
153
- end.join(', ')
154
351
  {
155
- expression_attribute_values: values,
156
- expression_attribute_names: maps,
157
- update_expression: expr
352
+ expression_attribute_values: Hash[hash.map { |k, v| [":#{k}_u", v] }],
353
+ expression_attribute_names: Hash[hash.map { |k, v| ["##{k}_u", k] }],
354
+ update_expression: "SET " + hash.map { |k, v| "##{k}_u = :#{k}_u" }.join(", "),
158
355
  }
159
356
  end
160
357
  end
@@ -0,0 +1,100 @@
1
+ module ROM
2
+ module DynamoDB
3
+ class Dataset
4
+ class WhereClause
5
+ class Clause
6
+ attr_reader :clauses
7
+
8
+ def initialize(clauses = [])
9
+ @clauses = clauses.is_a?(Array) ? clauses : [clauses]
10
+ end
11
+
12
+ def concat(val)
13
+ @clauses.concat(val.is_a?(Array) ? val : [val])
14
+ end
15
+
16
+ def expression_attribute_values
17
+ clauses.collect(&method(:to_values)).inject(:merge)
18
+ end
19
+
20
+ def expression_attribute_names
21
+ clauses.collect(&method(:to_names)).inject(:merge)
22
+ end
23
+
24
+ def key_condition_expression
25
+ clauses.collect(&method(:to_expression)).join(" AND ")
26
+ end
27
+
28
+ def to_expression(clause)
29
+ case clause.operand
30
+ when :between
31
+ "##{clause.key} BETWEEN :#{clause.key}_a AND :#{clause.key}_b"
32
+ when :begins_with
33
+ "begins_with(##{clause.key}, :#{clause.key})"
34
+ when :==
35
+ "##{clause.key} = :#{clause.key}"
36
+ else
37
+ "##{clause.key} #{clause.operand} :#{clause.key}"
38
+ end
39
+ end
40
+
41
+ def to_values(clause)
42
+ case clause.operand
43
+ when :between
44
+ value = clause.val
45
+ min = value.min.is_a?(Operand) ? value.min.val : value.min
46
+ max = value.max.is_a?(Operand) ? value.max.val : value.max
47
+ {
48
+ ":#{clause.key}_a" => min,
49
+ ":#{clause.key}_b" => max
50
+ }
51
+ else
52
+ { ":#{clause.key}" => clause.val }
53
+ end
54
+ end
55
+
56
+ def to_names(clause)
57
+ { "##{clause.key}" => clause.key }
58
+ end
59
+ end
60
+
61
+ class Operand
62
+ attr_reader :key, :operand, :val
63
+
64
+ def initialize(key:, val:)
65
+ @key = key
66
+ @val = val
67
+ end
68
+
69
+ %i(<= == >= > < between).each do |com|
70
+ define_method(com) do |val|
71
+ @operand = com
72
+ @val = val.is_a?(Operand) ? val.val : val
73
+ self
74
+ end
75
+ end
76
+
77
+ def <=>(op)
78
+ val <=> op.val
79
+ end
80
+ end
81
+
82
+ attr_reader :maps, :clauses
83
+
84
+ def initialize(maps = {})
85
+ @clauses = Clause.new
86
+ @maps = maps
87
+ end
88
+
89
+ def execute(&block)
90
+ @clauses.concat(instance_exec(&block))
91
+ self
92
+ end
93
+
94
+ def method_missing(key)
95
+ Operand.new(key: key, val: maps[key])
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -3,29 +3,72 @@ module ROM
3
3
  class Relation < ROM::Relation
4
4
  adapter :dynamodb
5
5
 
6
- # retrieval
7
- forward :restrict, :scan, :retrieve, :batch_get, :equal, :index,
8
- :before, :after, :between, :select, :offset, :limit
6
+ # @!macro [attach] r.forward
7
+ # Performs a $1 action on the relation. In most cases, these operations
8
+ # can be chained to build up larger queries to perform.
9
+ # @note This method forwards the $1 operation to the underlying dataset.
10
+ # @see DynamoDB::Dataset#$1
11
+ forward :restrict
9
12
 
10
- # operations
11
- forward :create, :delete, :update
13
+ forward :scan
12
14
 
13
- # storing
14
- forward :ascending, :descending
15
+ forward :retrieve
15
16
 
16
- # def initialize(*r)
17
- # super *r
18
- # puts schema.inspect
19
- # end
17
+ forward :batch_get
20
18
 
19
+ forward :equal
20
+
21
+ forward :index
22
+
23
+ forward :where
24
+
25
+ forward :before
26
+
27
+ forward :after
28
+
29
+ forward :between
30
+
31
+ forward :select
32
+
33
+ forward :offset
34
+
35
+ forward :limit
36
+
37
+ forward :create
38
+
39
+ forward :delete
40
+
41
+ forward :update
42
+
43
+ forward :ascending
44
+
45
+ forward :descending
46
+
47
+ # Retrieve a single record, providing a hash key name and the ID to
48
+ # fetch.
49
+ #
50
+ # @note This is a very simple helper allowing you to easily retrieve
51
+ # singular records using a hash key lookup.
52
+ #
53
+ # @param key [Symbol] the hash key name to fetch on
54
+ # @param id [String, Fixnum] an accepted data format for DynamoDB to lookup on
55
+ # @return [Hash] a single object retrieved from DynamoDB
56
+ def fetch(key, id)
57
+ retrieve(key, id).one!
58
+ end
59
+
60
+ # @see https://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#describe_table-instance_method
61
+ # @return [Hash] AWS SDK payload of table information
21
62
  def info
22
63
  dataset.information
23
64
  end
24
65
 
66
+ # @return [Fixnum] current total item count for the associated DynamoDB table
25
67
  def count
26
68
  dataset.information.item_count
27
69
  end
28
70
 
71
+ # @return [Symbol] current status of the DynamoDB table
29
72
  def status
30
73
  dataset.information.table_status.downcase.to_sym
31
74
  rescue
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module DynamoDB
3
- VERSION = "2.0.2"
3
+ VERSION = "2.2.0"
4
4
  end
5
5
  end
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "bigdecimal", "~> 1.3"
29
29
  spec.add_development_dependency "rake", "~> 10.0"
30
30
  spec.add_development_dependency "rspec", "~> 3.0"
31
- spec.add_development_dependency "coveralls", "~> 0.8"
31
+ spec.add_development_dependency "yard", "~> 0.9"
32
+ spec.add_development_dependency "codeclimate-test-reporter", "~> 1.0"
32
33
  spec.add_development_dependency "factory_girl", "~> 4.5"
33
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-dynamodb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - davidkelley
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-18 00:00:00.000000000 Z
11
+ date: 2017-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rom
@@ -123,19 +123,33 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: '3.0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: coveralls
126
+ name: yard
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '0.8'
131
+ version: '0.9'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '0.8'
138
+ version: '0.9'
139
+ - !ruby/object:Gem::Dependency
140
+ name: codeclimate-test-reporter
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: factory_girl
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -162,6 +176,7 @@ files:
162
176
  - ".gitignore"
163
177
  - ".rspec"
164
178
  - ".travis.yml"
179
+ - CHANGELOG.md
165
180
  - CODE_OF_CONDUCT.md
166
181
  - Dockerfile.development
167
182
  - Gemfile
@@ -177,6 +192,7 @@ files:
177
192
  - lib/rom/dynamodb/commands/delete.rb
178
193
  - lib/rom/dynamodb/commands/update.rb
179
194
  - lib/rom/dynamodb/dataset.rb
195
+ - lib/rom/dynamodb/dataset/where_clause.rb
180
196
  - lib/rom/dynamodb/functions.rb
181
197
  - lib/rom/dynamodb/gateway.rb
182
198
  - lib/rom/dynamodb/relation.rb