cequel 0.0.0 → 0.4.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.
Files changed (71) hide show
  1. data/lib/cequel.rb +16 -0
  2. data/lib/cequel/batch.rb +58 -0
  3. data/lib/cequel/cql_row_specification.rb +22 -0
  4. data/lib/cequel/data_set.rb +346 -0
  5. data/lib/cequel/errors.rb +4 -0
  6. data/lib/cequel/keyspace.rb +106 -0
  7. data/lib/cequel/model.rb +95 -0
  8. data/lib/cequel/model/associations.rb +120 -0
  9. data/lib/cequel/model/callbacks.rb +32 -0
  10. data/lib/cequel/model/class_internals.rb +48 -0
  11. data/lib/cequel/model/column.rb +20 -0
  12. data/lib/cequel/model/dictionary.rb +202 -0
  13. data/lib/cequel/model/dirty.rb +53 -0
  14. data/lib/cequel/model/dynamic.rb +31 -0
  15. data/lib/cequel/model/errors.rb +13 -0
  16. data/lib/cequel/model/inheritable.rb +48 -0
  17. data/lib/cequel/model/instance_internals.rb +23 -0
  18. data/lib/cequel/model/local_association.rb +42 -0
  19. data/lib/cequel/model/magic.rb +79 -0
  20. data/lib/cequel/model/mass_assignment_security.rb +21 -0
  21. data/lib/cequel/model/naming.rb +17 -0
  22. data/lib/cequel/model/observer.rb +42 -0
  23. data/lib/cequel/model/persistence.rb +173 -0
  24. data/lib/cequel/model/properties.rb +143 -0
  25. data/lib/cequel/model/railtie.rb +33 -0
  26. data/lib/cequel/model/remote_association.rb +40 -0
  27. data/lib/cequel/model/scope.rb +362 -0
  28. data/lib/cequel/model/scoped.rb +50 -0
  29. data/lib/cequel/model/subclass_internals.rb +45 -0
  30. data/lib/cequel/model/timestamps.rb +52 -0
  31. data/lib/cequel/model/translation.rb +17 -0
  32. data/lib/cequel/model/validations.rb +50 -0
  33. data/lib/cequel/new_relic_instrumentation.rb +22 -0
  34. data/lib/cequel/row_specification.rb +63 -0
  35. data/lib/cequel/statement.rb +23 -0
  36. data/lib/cequel/version.rb +3 -0
  37. data/spec/environment.rb +3 -0
  38. data/spec/examples/data_set_spec.rb +382 -0
  39. data/spec/examples/keyspace_spec.rb +63 -0
  40. data/spec/examples/model/associations_spec.rb +109 -0
  41. data/spec/examples/model/callbacks_spec.rb +79 -0
  42. data/spec/examples/model/dictionary_spec.rb +413 -0
  43. data/spec/examples/model/dirty_spec.rb +39 -0
  44. data/spec/examples/model/dynamic_spec.rb +41 -0
  45. data/spec/examples/model/inheritable_spec.rb +45 -0
  46. data/spec/examples/model/magic_spec.rb +199 -0
  47. data/spec/examples/model/mass_assignment_security_spec.rb +13 -0
  48. data/spec/examples/model/naming_spec.rb +9 -0
  49. data/spec/examples/model/observer_spec.rb +86 -0
  50. data/spec/examples/model/persistence_spec.rb +201 -0
  51. data/spec/examples/model/properties_spec.rb +81 -0
  52. data/spec/examples/model/scope_spec.rb +677 -0
  53. data/spec/examples/model/serialization_spec.rb +20 -0
  54. data/spec/examples/model/spec_helper.rb +12 -0
  55. data/spec/examples/model/timestamps_spec.rb +52 -0
  56. data/spec/examples/model/translation_spec.rb +23 -0
  57. data/spec/examples/model/validations_spec.rb +86 -0
  58. data/spec/examples/spec_helper.rb +9 -0
  59. data/spec/models/asset.rb +21 -0
  60. data/spec/models/asset_observer.rb +5 -0
  61. data/spec/models/blog.rb +14 -0
  62. data/spec/models/blog_posts.rb +6 -0
  63. data/spec/models/category.rb +9 -0
  64. data/spec/models/comment.rb +12 -0
  65. data/spec/models/photo.rb +5 -0
  66. data/spec/models/post.rb +88 -0
  67. data/spec/models/post_comments.rb +14 -0
  68. data/spec/models/post_observer.rb +43 -0
  69. data/spec/support/helpers.rb +26 -0
  70. data/spec/support/result_stub.rb +27 -0
  71. metadata +125 -23
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe 'Serialization' do
4
+ let(:post) { Post.new(:id => 1, :title => 'Cequel') }
5
+
6
+ it 'should serialize to JSON' do
7
+ post.to_json.should ==
8
+ {'post' => {'id' => 1, 'title' => 'Cequel'}}.to_json
9
+ end
10
+
11
+ it 'should serialize to XML' do
12
+ post.to_xml.should == <<XML
13
+ <?xml version="1.0" encoding="UTF-8"?>
14
+ <post>
15
+ <id type="integer">1</id>
16
+ <title>Cequel</title>
17
+ </post>
18
+ XML
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+ require 'cequel/model'
3
+
4
+ Dir.glob(File.join(File.dirname(__FILE__), '../../models/**/*.rb')).each do |file|
5
+ require file
6
+ end
7
+
8
+ RSpec.configure do |config|
9
+ config.before :each do
10
+ Cequel::Model.keyspace.connection = connection
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Timestamps do
4
+ let!(:now) { Time.now }
5
+ before { Time.stub(:now).and_return now }
6
+
7
+ it 'should add created_at column' do
8
+ Blog.column_names.should include(:created_at)
9
+ end
10
+
11
+ it 'should add updated_at column' do
12
+ Blog.column_names.should include(:updated_at)
13
+ end
14
+
15
+ it 'should populate created_at and updated_at column on create' do
16
+ connection.should_receive(:execute).
17
+ with "INSERT INTO blogs (?) VALUES (?)",
18
+ ['id', 'published', 'updated_at', 'created_at'],
19
+ [1, true, now, now]
20
+ Blog.create!(:id => 1)
21
+ end
22
+
23
+ it 'should populate updated_at column on update' do
24
+ connection.stub(:execute).
25
+ with("SELECT * FROM blogs WHERE ? = ? LIMIT 1", :id, 1).
26
+ and_return result_stub(
27
+ :id => 1,
28
+ :name => 'Blogtime',
29
+ :created_at => now - 1.day,
30
+ :updated_at => now - 1.day
31
+ )
32
+ blog = Blog.find(1)
33
+ blog.name = 'Bloggy'
34
+ connection.should_receive(:execute).
35
+ with("UPDATE blogs SET ? = ?, ? = ? WHERE ? = ?", 'name', 'Bloggy', 'updated_at', now, :id, 1)
36
+ blog.save
37
+ end
38
+
39
+ it 'should not update updated_at when calling save with no dirty attributes' do
40
+ connection.stub(:execute).
41
+ with("SELECT * FROM blogs WHERE ? = ? LIMIT 1", :id, 1).
42
+ and_return result_stub(
43
+ :id => 1,
44
+ :name => 'Blogtime',
45
+ :created_at => now - 1.day,
46
+ :updated_at => now - 1.day
47
+ )
48
+ blog = Blog.find(1)
49
+ blog.save
50
+ blog.updated_at.should == now - 1.day
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Translation do
4
+ before do
5
+ I18n.backend.load_translations(
6
+ File.expand_path('../../../support/en.yml', __FILE__))
7
+ I18n.locale = 'en'
8
+ end
9
+
10
+ it 'should translate model names' do
11
+ Post.model_name.human.should == 'Blog post'
12
+ end
13
+
14
+ it 'should translate attribute names' do
15
+ Post.human_attribute_name(:title).should == 'Post title'
16
+ end
17
+
18
+ it 'should translate error messages' do
19
+ post = Post.new(:id => 1, :require_title => true)
20
+ post.valid?
21
+ post.errors.full_messages.should include("Post title is a required field")
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Validations do
4
+ describe '#valid?' do
5
+ it 'should be false if model is not valid' do
6
+ Post.new(:id => 1, :require_title => true).should_not be_valid
7
+ end
8
+
9
+ it 'should be true if model is valid' do
10
+ Post.new(:id => 1, :require_title => true, :title => 'Cequel').should be_valid
11
+ end
12
+ end
13
+
14
+ describe '#save' do
15
+ it 'should return false and not persist model if invalid' do
16
+ Post.new(:id => 1, :body => 'Cequel', :require_title => true).save.should be_false
17
+ end
18
+
19
+ it 'should return true and persist model if valid' do
20
+ connection.should_receive(:execute).
21
+ with "INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel']
22
+
23
+ Post.new(:id => 1, :title => 'Cequel', :require_title => true).save.should be_true
24
+ end
25
+ end
26
+
27
+ describe '#save!' do
28
+ it 'should raise error and not persist model if invalid' do
29
+ expect do
30
+ Post.new(:id => 1, :body => 'Cequel', :require_title => true).save!
31
+ end.to raise_error(Cequel::Model::RecordInvalid)
32
+ end
33
+
34
+ it 'should persist model and return self if valid' do
35
+ connection.should_receive(:execute).
36
+ with "INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel']
37
+
38
+ post = Post.new(:id => 1, :title => 'Cequel', :require_title => true)
39
+ post.save!.should == post
40
+ end
41
+ end
42
+
43
+ describe '#update_attributes!' do
44
+ let(:post) do
45
+ connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 1).
46
+ and_return result_stub(:id => 1, :blog_id => 1, :title => 'Cequel')
47
+ Post.find(1)
48
+ end
49
+
50
+ it 'should change attributes and save them if valid' do
51
+ connection.should_receive(:execute).
52
+ with "UPDATE posts SET ? = ? WHERE ? = ?", 'body', 'Cequel cequel', :id, 1
53
+ post.update_attributes!(:body => 'Cequel cequel')
54
+ end
55
+
56
+ it 'should raise error if not valid' do
57
+ post.require_title = true
58
+ expect { post.update_attributes!(:title => nil) }.
59
+ to raise_error(Cequel::Model::RecordInvalid)
60
+ end
61
+ end
62
+
63
+ describe '::create!' do
64
+ it 'should raise RecordInvalid and not persist model if invalid' do
65
+ expect do
66
+ Post.create!(:id => 1, :body => 'Cequel', :require_title => true)
67
+ end.to raise_error(Cequel::Model::RecordInvalid)
68
+ end
69
+
70
+ it 'should and return model if valid' do
71
+ connection.should_receive(:execute).
72
+ with "INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel']
73
+
74
+ Post.create!(:id => 1, :title => 'Cequel', :require_title => true).
75
+ title.should == 'Cequel'
76
+ end
77
+ end
78
+
79
+ describe 'callbacks' do
80
+ it 'should call validation callbacks' do
81
+ post = Post.new(:id => 1)
82
+ post.valid?
83
+ post.should have_callback(:validation)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,9 @@
1
+ require File.expand_path('../../environment', __FILE__)
2
+
3
+ Dir.glob(File.expand_path('../../support/**/*.rb', __FILE__)).each do |file|
4
+ require file
5
+ end
6
+
7
+ RSpec.configure do |config|
8
+ config.include(Cequel::SpecSupport::Helpers)
9
+ end
@@ -0,0 +1,21 @@
1
+ class Asset
2
+
3
+ include Cequel::Model
4
+
5
+ key :id, :integer
6
+ column :class_name, :ascii
7
+ column :label, :varchar
8
+ column :checksum, :varchar
9
+
10
+ index_preference :checksum, :class_name
11
+
12
+ def observed!(callback)
13
+ @observed ||= []
14
+ @observed << callback
15
+ end
16
+
17
+ def has_observed?(callback)
18
+ @observed.include?(callback)
19
+ end
20
+
21
+ end
@@ -0,0 +1,5 @@
1
+ class AssetObserver < Cequel::Model::Observer
2
+ def before_save(asset)
3
+ asset.observed!(:before_save)
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ class Blog
2
+
3
+ include Cequel::Model
4
+ include Timestamps
5
+
6
+ key :id, :integer
7
+ column :name, :varchar
8
+ column :published, :boolean, :default => true
9
+
10
+ has_many :posts, :dependent => :destroy
11
+
12
+ default_scope limit(100)
13
+
14
+ end
@@ -0,0 +1,6 @@
1
+ class BlogPosts < Cequel::Model::Dictionary
2
+ key :blog_id, :int
3
+ maps :uuid => :int
4
+
5
+ self.default_batch_size = 2
6
+ end
@@ -0,0 +1,9 @@
1
+ class Category
2
+
3
+ include Cequel::Model
4
+ include Cequel::Model::Dynamic
5
+
6
+ key :id, :int
7
+ column :name, :text
8
+
9
+ end
@@ -0,0 +1,12 @@
1
+ class Comment
2
+ include Cequel::Model
3
+
4
+ key :id, :uuid
5
+ column :body, :text
6
+
7
+ private
8
+
9
+ def generate_key
10
+ SimpleUUID::UUID.new
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path('../asset', __FILE__)
2
+
3
+ class Photo < Asset
4
+ column :url, :varchar
5
+ end
@@ -0,0 +1,88 @@
1
+ class Post
2
+
3
+ include Cequel::Model
4
+
5
+ key :id, :int
6
+ column :title, :varchar
7
+ column :body, :varchar
8
+ column :published, :boolean
9
+ column :author_name, :text
10
+
11
+ before_save :record_save_callback
12
+ before_create :record_create_callback
13
+ before_update :record_update_callback
14
+ before_destroy :record_destroy_callback
15
+ before_validation :record_validation_callback
16
+
17
+ belongs_to :blog
18
+ has_one :thumbnail, :class_name => 'Photo'
19
+
20
+ validates :title, :presence => true, :if => :require_title?
21
+
22
+ attr_protected :blog_id
23
+
24
+ attr_writer :require_title
25
+
26
+ def self.for_blog(blog_id)
27
+ where(:blog_id => blog_id)
28
+ end
29
+
30
+ def has_callback?(callback)
31
+ callbacks.include?(callback)
32
+ end
33
+
34
+ def has_been_observed?(callback)
35
+ observed.include?(callback)
36
+ end
37
+
38
+ def observed!(callback)
39
+ observed << callback
40
+ end
41
+
42
+ def require_title?
43
+ !!@require_title
44
+ end
45
+
46
+ def author_name=(author_name)
47
+ write_attribute(:author_name, author_name.try(:reverse))
48
+ end
49
+
50
+ def author_name
51
+ read_attribute(:author_name).try(:reverse)
52
+ end
53
+
54
+ private
55
+
56
+ def record_save_callback
57
+ record_callback(:save)
58
+ end
59
+
60
+ def record_create_callback
61
+ record_callback(:create)
62
+ end
63
+
64
+ def record_update_callback
65
+ record_callback(:update)
66
+ end
67
+
68
+ def record_destroy_callback
69
+ record_callback(:destroy)
70
+ end
71
+
72
+ def record_validation_callback
73
+ record_callback(:validation)
74
+ end
75
+
76
+ def record_callback(callback)
77
+ callbacks << callback
78
+ end
79
+
80
+ def callbacks
81
+ @callbacks ||= Set[]
82
+ end
83
+
84
+ def observed
85
+ @observed ||= Set[]
86
+ end
87
+
88
+ end
@@ -0,0 +1,14 @@
1
+ class PostComments < Cequel::Model::Dictionary
2
+ key :post_id, :int
3
+ maps :int => :text
4
+
5
+ private
6
+
7
+ def serialize_value(data)
8
+ data.to_json
9
+ end
10
+
11
+ def deserialize_value(post_id, json)
12
+ JSON.parse(json)
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ class PostObserver < Cequel::Model::Observer
2
+
3
+ def before_create(post)
4
+ post.observed!(:before_create)
5
+ end
6
+
7
+ def after_create(post)
8
+ post.observed!(:after_create)
9
+ end
10
+
11
+ def before_save(post)
12
+ post.observed!(:before_save)
13
+ end
14
+
15
+ def after_save(post)
16
+ post.observed!(:after_save)
17
+ end
18
+
19
+ def before_update(post)
20
+ post.observed!(:before_update)
21
+ end
22
+
23
+ def after_update(post)
24
+ post.observed!(:after_update)
25
+ end
26
+
27
+ def before_destroy(post)
28
+ post.observed!(:before_destroy)
29
+ end
30
+
31
+ def after_destroy(post)
32
+ post.observed!(:after_destroy)
33
+ end
34
+
35
+ def before_validation(post)
36
+ post.observed!(:before_validation)
37
+ end
38
+
39
+ def after_validation(post)
40
+ post.observed!(:after_validation)
41
+ end
42
+
43
+ end
@@ -0,0 +1,26 @@
1
+ module Cequel
2
+
3
+ module SpecSupport
4
+
5
+ Connection = Object.new
6
+
7
+ module Helpers
8
+
9
+ def result_stub(*results)
10
+ ResultStub.new(results)
11
+ end
12
+
13
+ def connection
14
+ Connection
15
+ end
16
+
17
+ def cequel
18
+ @cequel ||= Cequel::Keyspace.new({}).tap do |keyspace|
19
+ keyspace.connection = connection
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end