cequel 1.0.0.rc2 → 1.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
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