Skip to content

Commit

Permalink
Merge pull request #374 from Dynamoid/add-project-method
Browse files Browse the repository at this point in the history
Add Criteria::Chain#project method
  • Loading branch information
andrykonchin committed Aug 17, 2019
2 parents 30324db + 119ac4b commit 8a9a2b4
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## Features

* Feature: [#374](https://github.com/Dynamoid/dynamoid/pull/374) Add `#project` query method to load only specified fields

## Improvements

* Improvement: [#359](https://github.com/Dynamoid/dynamoid/pull/359) Add support of `NULL` and `NOT_NULL` operators
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,32 @@ operators check attribute presence in a document, not value.
So if attribute `postcode`'s value is `NULL`, `NULL` operator will return false
because attribute exists even if has `NULL` value.

#### Selecting some specific fields only

It could be done with `project` method:

```ruby
class User
include Dynamoid::Document
field :name
end

User.create(name: 'Alex')
user = User.project(:name).first

user.id # => nil
user.name # => 'Alex'
user.created_at # => nil
```

Returned models with have filled specified fields only.

Several fields could be specified:

```ruby
user = User.project(:name, :created_at)
```

### Consistent Reads

Querying supports consistent reading. By default, DynamoDB reads are eventually consistent: if you do a write and then a read immediately afterwards, the results of the previous write may not be reflected. If you need to do a consistent read (that is, you need to read the results of a write immediately) you can do so, but keep in mind that consistent reads are twice as expensive as regular reads for DynamoDB.
Expand Down
15 changes: 11 additions & 4 deletions lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Query
OPTIONS_KEYS = %i[
limit hash_key hash_value range_key consistent_read scan_index_forward
select index_name batch_size exclusive_start_key record_limit scan_limit
project
].freeze

attr_reader :client, :table, :options, :conditions
Expand Down Expand Up @@ -63,10 +64,11 @@ def build_request
batch_size = options[:batch_size]
limit = [record_limit, scan_limit, batch_size].compact.min

request[:limit] = limit if limit
request[:table_name] = table.name
request[:key_conditions] = key_conditions
request[:query_filter] = query_filter
request[:limit] = limit if limit
request[:table_name] = table.name
request[:key_conditions] = key_conditions
request[:query_filter] = query_filter
request[:attributes_to_get] = attributes_to_get

request
end
Expand Down Expand Up @@ -117,6 +119,11 @@ def query_filter
result
end
end

def attributes_to_get
return if options[:project].nil?
options[:project].map(&:to_s)
end
end
end
end
Expand Down
12 changes: 9 additions & 3 deletions lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ def build_request
batch_size = options[:batch_size]
limit = [record_limit, scan_limit, batch_size].compact.min

request[:limit] = limit if limit
request[:table_name] = table.name
request[:scan_filter] = scan_filter
request[:limit] = limit if limit
request[:table_name] = table.name
request[:scan_filter] = scan_filter
request[:attributes_to_get] = attributes_to_get

request
end
Expand All @@ -81,6 +82,11 @@ def scan_filter
result
end
end

def attributes_to_get
return if options[:project].nil?
options[:project].map(&:to_s)
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/dynamoid/criteria.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Criteria
extend ActiveSupport::Concern

module ClassMethods
%i[where all first last each record_limit scan_limit batch start scan_index_forward find_by_pages].each do |meth|
%i[where all first last each record_limit scan_limit batch start scan_index_forward find_by_pages project].each do |meth|
# Return a criteria chain in response to a method that will begin or end a chain. For more information,
# see Dynamoid::Criteria::Chain.
#
Expand Down
25 changes: 20 additions & 5 deletions lib/dynamoid/criteria/chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ def find_by_pages(&block)
pages.each(&block)
end

def project(*fields)
@project = fields.map(&:to_sym)
self
end

private

# The actual records referenced by the association.
Expand Down Expand Up @@ -190,9 +195,12 @@ def pages
#
# @since 3.1.0
def pages_via_query
Enumerator.new do |yielder|
Enumerator.new do |y|
Dynamoid.adapter.query(source.table_name, range_query).each do |items, metadata|
yielder.yield items.map { |hash| source.from_database(hash) }, metadata.slice(:last_evaluated_key)
page = items.map { |h| source.from_database(h) }
options = metadata.slice(:last_evaluated_key)

y.yield page, options
end
end
end
Expand All @@ -203,9 +211,12 @@ def pages_via_query
#
# @since 3.1.0
def pages_via_scan
Enumerator.new do |yielder|
Enumerator.new do |y|
Dynamoid.adapter.scan(source.table_name, scan_query, scan_opts).each do |items, metadata|
yielder.yield(items.map { |hash| source.from_database(hash) }, metadata.slice(:last_evaluated_key))
page = items.map { |h| source.from_database(h) }
options = metadata.slice(:last_evaluated_key)

y.yield page, options
end
end
end
Expand Down Expand Up @@ -368,13 +379,16 @@ def start_key

def query_opts
opts = {}
# Don't specify select = ALL_ATTRIBUTES option explicitly because it's
# already a default value of Select statement. Explicite Select value
# conflicts with AttributesToGet statement (project option).
opts[:index_name] = @key_fields_detector.index_name if @key_fields_detector.index_name
opts[:select] = 'ALL_ATTRIBUTES'
opts[:record_limit] = @record_limit if @record_limit
opts[:scan_limit] = @scan_limit if @scan_limit
opts[:batch_size] = @batch_size if @batch_size
opts[:exclusive_start_key] = start_key if @start
opts[:scan_index_forward] = @scan_index_forward
opts[:project] = @project
opts
end

Expand All @@ -398,6 +412,7 @@ def scan_opts
opts[:batch_size] = @batch_size if @batch_size
opts[:exclusive_start_key] = start_key if @start
opts[:consistent_read] = true if @consistent_read
opts[:project] = @project
opts
end
end
Expand Down
39 changes: 39 additions & 0 deletions spec/dynamoid/criteria/chain_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,45 @@ def request_params
end
end

describe '#project' do
let(:model) do
new_class do
field :name
field :age, :integer
end
end

it 'loads only specified attributes' do
model.create(name: 'Alex', age: 21)
obj, = model.project(:age).to_a

expect(obj.age).to eq 21

expect(obj.id).to eq nil
expect(obj.name).to eq nil
end

it 'works with Scan' do
model.create(name: 'Alex', age: 21)

chain = Dynamoid::Criteria::Chain.new(model)
expect(chain).to receive(:pages_via_scan).and_call_original

obj, = chain.project(:age).to_a
expect(obj.attributes).to eq(age: 21)
end

it 'works with Query' do
obj = model.create(name: 'Alex', age: 21)

chain = Dynamoid::Criteria::Chain.new(model)
expect(chain).to receive(:pages_via_query).and_call_original

obj_loaded, = chain.where(id: obj.id).project(:age).to_a
expect(obj_loaded.attributes).to eq(age: 21)
end
end

describe 'User' do
let(:chain) { described_class.new(User) }

Expand Down

0 comments on commit 8a9a2b4

Please sign in to comment.