cequel 1.0.0.rc2 → 1.0.0.rc3

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: 4988b87b5a5a9fc843dcaaf3bdfb3a5842b20cc8
4
- data.tar.gz: ce93eaeb725d23f31a22dc3f8e2e84fc4d9ec0e7
3
+ metadata.gz: 1ed6d7b1a7f6b34c7ebfb6421f5402b163f79ad2
4
+ data.tar.gz: 6e88f21a67e23480f71b2c391f20d21df851b490
5
5
  SHA512:
6
- metadata.gz: b738d10a75bd0f977eeff27c119ccd9d7980c66e2e1bdf004ef74e980dc847514ea7083cd9432f28b4d1099a151c635a71f50cd1f1757d6a20c16c42074504a0
7
- data.tar.gz: be8264f1142625d8755a149550f68a9c2dc66b93c7a6a562f0c893043dbaf859ab879632911c8b753b96cdd07ea025b2348f12a237e07bdc4c7cad27015093a8
6
+ metadata.gz: 53f2888d6b7fe8be5b36cb647e558be6b2ea4a8cffde531125f398a03bc922b19a90e5c02c6a92434a0ff9f7f7b40bd0339280b0d7e21f9daf2e7695a5647a8a
7
+ data.tar.gz: c92544762c93bab662602603d9d32810fd19f2010dd8754c955ac9ec674207104753453de508a0bf8fff346c4b6cd9d147d6b72230c38396ff9995fd098cd9fa
@@ -21,6 +21,7 @@ require 'cequel/record/mass_assignment'
21
21
  require 'cequel/record/callbacks'
22
22
  require 'cequel/record/validations'
23
23
  require 'cequel/record/dirty'
24
+ require 'cequel/record/conversion'
24
25
 
25
26
  require 'cequel/record'
26
27
 
@@ -88,6 +89,7 @@ module Cequel
88
89
  include Validations
89
90
  include Dirty
90
91
  extend ActiveModel::Naming
92
+ include Conversion
91
93
  include ActiveModel::Serializers::JSON
92
94
  include ActiveModel::Serializers::Xml
93
95
  end
@@ -2,7 +2,7 @@ module Cequel
2
2
  module Record
3
3
  #
4
4
  # Collection of records from a
5
- # {Associations::ClassMethods#has_many has_many} associaiton. Encapsulates
5
+ # {Associations::ClassMethods#has_many has_many} association. Encapsulates
6
6
  # and behaves like a {RecordSet}, but unlike a normal RecordSet the loaded
7
7
  # records are held in memory after they are loaded.
8
8
  #
@@ -11,6 +11,7 @@ module Cequel
11
11
  #
12
12
  class AssociationCollection < DelegateClass(RecordSet)
13
13
  include Enumerable
14
+ extend Forwardable
14
15
 
15
16
  #
16
17
  # @yield [Record]
@@ -20,10 +21,84 @@ module Cequel
20
21
  target.each(&block)
21
22
  end
22
23
 
24
+ #
25
+ # (see RecordSet#find)
26
+ #
27
+ def find(*keys)
28
+ if block_given? then super
29
+ else record_set.find(*keys)
30
+ end
31
+ end
32
+
33
+ #
34
+ # (see RecordSet#select)
35
+ #
36
+ def select(*columns)
37
+ if block_given? then super
38
+ else record_set.select(*columns)
39
+ end
40
+ end
41
+
42
+ #
43
+ # (see RecordSet#first)
44
+ #
45
+ def first(*args)
46
+ if loaded? then super
47
+ else record_set.first(*args)
48
+ end
49
+ end
50
+
51
+ #
52
+ # @!method count
53
+ # Get the count of child records stored in the database. This method
54
+ # will always query Cassandra, even if the records are loaded in
55
+ # memory.
56
+ #
57
+ # @return [Integer] number of child records in the database
58
+ # @see #size
59
+ # @see #length
60
+ #
61
+ def_delegator :record_set, :count
62
+
63
+ #
64
+ # @!method length
65
+ # The number of child instances in the in-memory collection. If the
66
+ # records are not loaded in memory, they will be loaded and then
67
+ # counted.
68
+ #
69
+ # @return [Integer] length of the loaded record collection in memory
70
+ # @see #size
71
+ # @see #count
72
+ #
73
+ def_delegator :entries, :length
74
+
75
+ #
76
+ # Get the size of the child collection. If the records are loaded in
77
+ # memory from a previous operation, count the length of the array in
78
+ # memory. If the collection is unloaded, perform a `COUNT` query.
79
+ #
80
+ # @return [Integer] size of the child collection
81
+ # @see #length
82
+ # @see #count
83
+ #
84
+ def size
85
+ loaded? ? length : count
86
+ end
87
+
88
+ #
89
+ # @return [Boolean] true if this collection's records are loaded in
90
+ # memory
91
+ #
92
+ def loaded?
93
+ !!@target
94
+ end
95
+
23
96
  private
24
97
 
98
+ alias_method :record_set, :__getobj__
99
+
25
100
  def target
26
- @target ||= __getobj__.entries
101
+ @target ||= record_set.entries
27
102
  end
28
103
  end
29
104
  end
@@ -0,0 +1,15 @@
1
+ module Cequel
2
+ module Record
3
+ #
4
+ # Provides support for ActiveModel::Conversions
5
+ #
6
+ module Conversion
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ include ActiveModel::Conversion
11
+ alias_method :to_key, :key_values
12
+ end
13
+ end
14
+ end
15
+ end
@@ -67,6 +67,11 @@ module Cequel
67
67
  self
68
68
  end
69
69
 
70
+ # @private
71
+ def assert_fully_specified!
72
+ self
73
+ end
74
+
70
75
  private
71
76
 
72
77
  attr_reader :record_set
@@ -86,6 +86,7 @@ module Cequel
86
86
  def key_values
87
87
  key_attributes.values
88
88
  end
89
+ alias_method :to_key, :key_values
89
90
 
90
91
  #
91
92
  # Check if an unloaded record exists in the database
@@ -224,8 +224,7 @@ module Cequel
224
224
  #
225
225
  def at(*scoped_key_values)
226
226
  warn "`at` is deprecated. Use `[]` instead"
227
- scoped_key_values
228
- .reduce(self) { |record_set, key_value| record_set[key_value] }
227
+ traverse(*scoped_key_values)
229
228
  end
230
229
 
231
230
  #
@@ -314,6 +313,9 @@ module Cequel
314
313
  # Return a loaded Record or collection of loaded Records with the
315
314
  # specified primary key values
316
315
  #
316
+ # Multiple arguments are mapped onto unscoped key columns. To specify
317
+ # multiple values for a given key column, use an array.
318
+ #
317
319
  # @param scoped_key_values one or more values for the final primary key
318
320
  # column
319
321
  # @return [Record] if a single key is specified, return the loaded
@@ -322,15 +324,31 @@ module Cequel
322
324
  # collection of loaded records at those keys
323
325
  # @raise [RecordNotFound] if not all the keys correspond to records in
324
326
  # the table
325
- # @raise [ArgumentError] if not all primary key columns have been
326
- # specified
327
327
  #
328
- # @note This should only be called when all but the last column in the
329
- # primary key is already specified in this record set
330
- def find(*scoped_key_values)
331
- (scoped_key_values.one? ?
332
- self[scoped_key_values.first] :
333
- values_at(*scoped_key_values)).load!
328
+ # @example One record with one-column primary key
329
+ # # find the blog with subdomain 'cassandra'
330
+ # Blog.find('cassandra')
331
+ #
332
+ # @example Multiple records with one-column primary key
333
+ # # find the blogs with subdomain 'cassandra' and 'postgres'
334
+ # Blog.find(['cassandra', 'postgres'])
335
+ #
336
+ # @example One record with two-column primary key
337
+ # # find the post instance with blog subdomain 'cassandra' and
338
+ # # permalink 'my-post'
339
+ # Post.find('cassandra', 'my-post')
340
+ #
341
+ # @example Multiple records with two-column primary key
342
+ # # find the post instances with blog subdomain cassandra and
343
+ # # permalinks 'my-post' and 'my-new-post'
344
+ # Post.find('cassandra', ['my-post', 'my-new-post']
345
+ #
346
+ def find(*keys)
347
+ return super if block_given?
348
+ keys = [keys] if almost_fully_specified? && keys.many?
349
+ records = traverse(*keys).assert_fully_specified!.load!
350
+ force_array = keys.any? { |value| value.is_a?(Array) }
351
+ force_array ? Array.wrap(records) : records
334
352
  end
335
353
 
336
354
  #
@@ -472,6 +490,8 @@ module Cequel
472
490
  def count
473
491
  data_set.count
474
492
  end
493
+ alias_method :length, :count
494
+ alias_method :size, :count
475
495
 
476
496
  #
477
497
  # Enumerate over the records in this record set
@@ -594,6 +614,13 @@ module Cequel
594
614
  end
595
615
  end
596
616
 
617
+ # @private
618
+ def assert_fully_specified!
619
+ raise ArgumentError,
620
+ "Missing key component(s) " \
621
+ "#{unscoped_key_names.join(', ')}"
622
+ end
623
+
597
624
  def_delegators :entries, :inspect
598
625
 
599
626
  # @private
@@ -635,6 +662,16 @@ module Cequel
635
662
  end
636
663
  end
637
664
 
665
+ def traverse(*keys)
666
+ keys.reduce(self) do |record_set, key_value|
667
+ if key_value.is_a?(Array)
668
+ record_set.values_at(*key_value)
669
+ else
670
+ record_set[key_value]
671
+ end
672
+ end
673
+ end
674
+
638
675
  def scoped_key_names
639
676
  scoped_key_columns.map { |column| column.name }
640
677
  end
@@ -671,6 +708,10 @@ module Cequel
671
708
  scoped_key_values.length == target_class.key_columns.length
672
709
  end
673
710
 
711
+ def almost_fully_specified?
712
+ scoped_key_values.length == target_class.key_columns.length - 1
713
+ end
714
+
674
715
  def partition_specified?
675
716
  scoped_key_values.length >= target_class.partition_key_columns.length
676
717
  end
@@ -49,6 +49,11 @@ module Cequel
49
49
  end
50
50
  end
51
51
 
52
+ # @private
53
+ def assert_fully_specified!
54
+ self
55
+ end
56
+
52
57
  private
53
58
 
54
59
  def initialize_new_record(*)
@@ -1,4 +1,4 @@
1
1
  module Cequel
2
2
  # The current version of the library
3
- VERSION = '1.0.0.rc2'
3
+ VERSION = '1.0.0.rc3'
4
4
  end
@@ -138,6 +138,57 @@ describe Cequel::Record::Associations do
138
138
  blog.posts(true).map(&:title).should == ['Post 1', 'Post 2']
139
139
  end
140
140
 
141
+ it 'should support #find with key' do
142
+ blog.posts.find(posts.first.id).should == posts.first
143
+ end
144
+
145
+ it 'should support #find with block' do
146
+ blog.posts.find { |post| post.title.include?('1') }.should == posts[1]
147
+ end
148
+
149
+ it 'should support #select with block' do
150
+ blog.posts.select { |post| !post.title.include?('2') }
151
+ .should == posts.first(2)
152
+ end
153
+
154
+ it 'should support #select with arguments' do
155
+ expect { blog.posts.select(:title).first.id }
156
+ .to raise_error(Cequel::Record::MissingAttributeError)
157
+ end
158
+
159
+ it 'should load #first directly from the database if unloaded' do
160
+ blog.posts.first.title
161
+ blog.posts.should_not be_loaded
162
+ end
163
+
164
+ it 'should read #first from loaded collection' do
165
+ blog.posts.entries
166
+ disallow_queries!
167
+ blog.posts.first.title.should == 'Post 0'
168
+ end
169
+
170
+ it 'should always query the database for #count' do
171
+ blog.posts.entries
172
+ posts.first.destroy
173
+ blog.posts.count.should == 2
174
+ end
175
+
176
+ it 'should always load the records for #length' do
177
+ blog.posts.length.should == 3
178
+ blog.posts.should be_loaded
179
+ end
180
+
181
+ it 'should count from database for #size if unloaded' do
182
+ blog.posts.size.should == 3
183
+ blog.posts.should_not be_loaded
184
+ end
185
+
186
+ it 'should count records in memory for #size if loaded' do
187
+ blog.posts.entries
188
+ disallow_queries!
189
+ blog.posts.size.should == 3
190
+ end
191
+
141
192
  it "does not allow invalid :dependent options" do
142
193
  expect {
143
194
  Post.class_eval do
@@ -27,6 +27,11 @@ describe Cequel::Record::Persistence do
27
27
  end.tap(&:save)
28
28
  end
29
29
 
30
+ describe 'new record' do
31
+ specify { Blog.new.should_not be_persisted }
32
+ specify { Blog.new.should be_transient }
33
+ end
34
+
30
35
  describe '#save' do
31
36
  context 'on create' do
32
37
  it 'should save row to database' do
@@ -129,11 +129,21 @@ describe Cequel::Record::RecordSet do
129
129
 
130
130
  it { should be_persisted }
131
131
  it { should_not be_transient }
132
- specify { Blog.new.should_not be_persisted }
133
- specify { Blog.new.should be_transient }
134
132
 
135
133
  it 'should cast argument to correct type' do
136
- Blog.find('blog-0'.force_encoding('ASCII-8BIT')).should be
134
+ Blog.find('blog-0'.force_encoding('ASCII-8BIT')).should == blogs.first
135
+ end
136
+
137
+ it 'should return multiple results as an array from vararg keys' do
138
+ Blog.find('blog-0', 'blog-1').should == blogs.first(2)
139
+ end
140
+
141
+ it 'should return multiple results as an array from array of keys' do
142
+ Blog.find(['blog-0', 'blog-1']).should == blogs.first(2)
143
+ end
144
+
145
+ it 'should return result in an array from one-element array of keys' do
146
+ Blog.find(['blog-1']).should == [blogs[1]]
137
147
  end
138
148
 
139
149
  it 'should raise RecordNotFound if bad argument passed' do
@@ -164,6 +174,23 @@ describe Cequel::Record::RecordSet do
164
174
  expect { Post['cequel'].find('bogus')}.
165
175
  to raise_error(Cequel::Record::RecordNotFound)
166
176
  end
177
+
178
+ it 'should take vararg of values for single key' do
179
+ Post.find('cassandra', 'cequel0').should == posts.first
180
+ end
181
+
182
+ it 'should take multiple values for key' do
183
+ Post.find('cassandra', ['cequel0', 'cequel1']).should == posts.first(2)
184
+ end
185
+
186
+ it 'should use Enumerable#find if block given' do
187
+ Post['cassandra'].find { |post| post.title.include?('1') }
188
+ .should == posts[1]
189
+ end
190
+
191
+ it 'should raise error if not enough key values specified' do
192
+ expect { Post.find('cassandra') }.to raise_error(ArgumentError)
193
+ end
167
194
  end
168
195
  end
169
196
 
@@ -335,6 +362,9 @@ describe Cequel::Record::RecordSet do
335
362
  end
336
363
  end
337
364
 
365
+ describe '#find' do
366
+ end
367
+
338
368
  describe '#[]' do
339
369
  it 'should return partial collection' do
340
370
  Post['cassandra'].find_each(:batch_size => 2).map(&:title).
@@ -109,9 +109,6 @@ module Cequel
109
109
  def disallow_queries!
110
110
  cequel.should_not_receive(:execute)
111
111
  end
112
-
113
112
  end
114
-
115
113
  end
116
-
117
114
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc2
4
+ version: 1.0.0.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mat Brown
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2014-02-01 00:00:00.000000000 Z
15
+ date: 2014-02-02 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: activesupport
@@ -176,6 +176,7 @@ files:
176
176
  - lib/cequel/record/callbacks.rb
177
177
  - lib/cequel/record/collection.rb
178
178
  - lib/cequel/record/configuration_generator.rb
179
+ - lib/cequel/record/conversion.rb
179
180
  - lib/cequel/record/data_set_builder.rb
180
181
  - lib/cequel/record/dirty.rb
181
182
  - lib/cequel/record/errors.rb