datastax_rails 1.0.11 → 1.0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +7 -2
- data/lib/blankslate.rb +106 -0
- data/lib/datastax_rails/base.rb +8 -2
- data/lib/datastax_rails/connection.rb +78 -0
- data/lib/datastax_rails/railtie.rb +3 -3
- data/lib/datastax_rails/relation.rb +3 -2
- data/lib/datastax_rails/rsolr_client_wrapper.rb +27 -0
- data/lib/datastax_rails/tasks/column_family.rb +0 -6
- data/lib/datastax_rails/version.rb +1 -1
- data/lib/datastax_rails.rb +3 -0
- data/lib/schema_migration.rb +8 -0
- data/spec/datastax_rails/cql/update_spec.rb +1 -1
- data/spec/dummy/config/datastax.yml +8 -4
- data/spec/dummy/log/test.log +904 -0
- metadata +7 -4
data/README.rdoc
CHANGED
@@ -36,10 +36,12 @@ Configure the config/datastax.yml file:
|
|
36
36
|
connection_options:
|
37
37
|
timeout: 2
|
38
38
|
solr:
|
39
|
-
|
39
|
+
port: 8983
|
40
|
+
path: /solr
|
40
41
|
|
41
42
|
The above is configured to use NetworkTopologyStrategy. If you go with this, you'll need to configure Datastax to use the
|
42
43
|
NetworkTopologySnitch and set up the cassandra-topology.properties file. See the Datastax documentation for more information.
|
44
|
+
|
43
45
|
For a more simple, single datacenter setup, something like this should probably work:
|
44
46
|
|
45
47
|
development:
|
@@ -50,7 +52,10 @@ For a more simple, single datacenter setup, something like this should probably
|
|
50
52
|
connection_options:
|
51
53
|
timeout: 2
|
52
54
|
solr:
|
53
|
-
|
55
|
+
port: 8983
|
56
|
+
path: /solr
|
57
|
+
|
58
|
+
See DatastaxRails::Connection::ClassMethods for a description of what options are available.
|
54
59
|
|
55
60
|
Create your keyspace:
|
56
61
|
|
data/lib/blankslate.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
|
4
|
+
# All rights reserved.
|
5
|
+
|
6
|
+
# Permission is granted for use, copying, modification, distribution,
|
7
|
+
# and distribution of modified versions of this work as long as the
|
8
|
+
# above copyright notice is included.
|
9
|
+
#++
|
10
|
+
|
11
|
+
######################################################################
|
12
|
+
# BlankSlate provides an abstract base class with no predefined
|
13
|
+
# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
|
14
|
+
# BlankSlate is useful as a base class when writing classes that
|
15
|
+
# depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
|
16
|
+
class BlankSlate
|
17
|
+
class << self
|
18
|
+
|
19
|
+
# Hide the method named +name+ in the BlankSlate class. Don't
|
20
|
+
# hide +instance_eval+ or any method beginning with "__".
|
21
|
+
def hide(name)
|
22
|
+
methods = instance_methods.map(&:to_sym)
|
23
|
+
if methods.include?(name.to_sym) and
|
24
|
+
name !~ /^(__|instance_eval)/
|
25
|
+
@hidden_methods ||= {}
|
26
|
+
@hidden_methods[name.to_sym] = instance_method(name)
|
27
|
+
undef_method name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_hidden_method(name)
|
32
|
+
@hidden_methods ||= {}
|
33
|
+
@hidden_methods[name] || superclass.find_hidden_method(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Redefine a previously hidden method so that it may be called on a blank
|
37
|
+
# slate object.
|
38
|
+
def reveal(name)
|
39
|
+
hidden_method = find_hidden_method(name)
|
40
|
+
fail "Don't know how to reveal method '#{name}'" unless hidden_method
|
41
|
+
define_method(name, hidden_method)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
instance_methods.each { |m| hide(m) }
|
46
|
+
end
|
47
|
+
|
48
|
+
######################################################################
|
49
|
+
# Since Ruby is very dynamic, methods added to the ancestors of
|
50
|
+
# BlankSlate <em>after BlankSlate is defined</em> will show up in the
|
51
|
+
# list of available BlankSlate methods. We handle this by defining a
|
52
|
+
# hook in the Object and Kernel classes that will hide any method
|
53
|
+
# defined after BlankSlate has been loaded.
|
54
|
+
module Kernel
|
55
|
+
class << self
|
56
|
+
alias_method :blank_slate_method_added, :method_added
|
57
|
+
|
58
|
+
# Detect method additions to Kernel and remove them in the
|
59
|
+
# BlankSlate class.
|
60
|
+
def method_added(name)
|
61
|
+
result = blank_slate_method_added(name)
|
62
|
+
return result if self != Kernel
|
63
|
+
BlankSlate.hide(name)
|
64
|
+
result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
######################################################################
|
70
|
+
# Same as above, except in Object.
|
71
|
+
class Object
|
72
|
+
class << self
|
73
|
+
alias_method :blank_slate_method_added, :method_added
|
74
|
+
|
75
|
+
# Detect method additions to Object and remove them in the
|
76
|
+
# BlankSlate class.
|
77
|
+
def method_added(name)
|
78
|
+
result = blank_slate_method_added(name)
|
79
|
+
return result if self != Object
|
80
|
+
BlankSlate.hide(name)
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_hidden_method(name)
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
######################################################################
|
91
|
+
# Also, modules included into Object need to be scanned and have their
|
92
|
+
# instance methods removed from blank slate. In theory, modules
|
93
|
+
# included into Kernel would have to be removed as well, but a
|
94
|
+
# "feature" of Ruby prevents late includes into modules from being
|
95
|
+
# exposed in the first place.
|
96
|
+
class Module
|
97
|
+
alias blankslate_original_append_features append_features
|
98
|
+
def append_features(mod)
|
99
|
+
result = blankslate_original_append_features(mod)
|
100
|
+
return result if mod != Object
|
101
|
+
instance_methods.each do |name|
|
102
|
+
BlankSlate.hide(name)
|
103
|
+
end
|
104
|
+
result
|
105
|
+
end
|
106
|
+
end
|
data/lib/datastax_rails/base.rb
CHANGED
@@ -445,20 +445,26 @@ module DatastaxRails #:nodoc:
|
|
445
445
|
class << self
|
446
446
|
delegate :find, :first, :all, :exists?, :any?, :many?, :to => :scoped
|
447
447
|
delegate :destroy, :destroy_all, :delete, :update, :update_all, :to => :scoped
|
448
|
-
# delegate :find_each, :find_in_batches, :to => :scoped
|
449
448
|
delegate :order, :limit, :where, :where_not, :page, :paginate, :select, :to => :scoped
|
450
449
|
delegate :per_page, :each, :group, :total_pages, :search, :fulltext, :to => :scoped
|
451
450
|
delegate :count, :first, :first!, :last, :last!, :to => :scoped
|
452
451
|
delegate :cql, :with_cassandra, :with_solr, :commit_solr, :to => :scoped
|
453
452
|
|
453
|
+
# Sets the column family name
|
454
|
+
#
|
455
|
+
# @param [String] column_family the name of the column family in cassandra
|
454
456
|
def column_family=(column_family)
|
455
457
|
@column_family = column_family
|
456
458
|
end
|
457
459
|
|
460
|
+
# Returns the column family name. If it has been set manually, the set name is returned.
|
461
|
+
# Otherwise returns the pluralized version of the class name.
|
462
|
+
#
|
463
|
+
# Returns [String] the name of the column family
|
458
464
|
def column_family
|
459
465
|
@column_family || name.pluralize
|
460
466
|
end
|
461
|
-
|
467
|
+
|
462
468
|
def base_class
|
463
469
|
klass = self
|
464
470
|
while klass.superclass != Base
|
@@ -1,9 +1,13 @@
|
|
1
|
+
# require 'datastax_rails/rsolr_client_wrapper'
|
1
2
|
module DatastaxRails
|
3
|
+
# The connection module holds all the code for establishing and maintaining a connection to
|
4
|
+
# Datastax Exterprise. This includes both the Cassandra and Solr connections.
|
2
5
|
module Connection
|
3
6
|
extend ActiveSupport::Concern
|
4
7
|
|
5
8
|
included do
|
6
9
|
class_attribute :connection
|
10
|
+
class_attribute :solr
|
7
11
|
end
|
8
12
|
|
9
13
|
module ClassMethods
|
@@ -11,12 +15,86 @@ module DatastaxRails
|
|
11
15
|
:servers => "127.0.0.1:9160",
|
12
16
|
:thrift => {}
|
13
17
|
}
|
18
|
+
|
19
|
+
# Returns the current server that we are talking to. This is useful when you are talking to a
|
20
|
+
# cluster, and we want to know which server specifically we are connected to.
|
21
|
+
#
|
22
|
+
# Used by Relation to calculate the SOLR URL so that it follows the Cassandra connection.
|
23
|
+
def current_server
|
24
|
+
thrift_client.instance_variable_get(:@current_server).to_s.split(/\:/).first
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the thrift client object
|
28
|
+
def thrift_client
|
29
|
+
self.connection.instance_variable_get(:@connection)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Establish a Cassandra connection to DSE. datastax.yml will be read and the current environment's
|
33
|
+
# settings passed to this method.
|
34
|
+
#
|
35
|
+
# The following is an example production configuration document. Assume that your setup consists
|
36
|
+
# of three datacenters each with three servers and RF=3 (i.e., you're storing your data 9 times)
|
37
|
+
#
|
38
|
+
# servers: ["10.1.2.5:9160", "10.1.2.6:9160", "10.1.2.7:9160"]
|
39
|
+
# keyspace: "datastax_rails_production"
|
40
|
+
# strategy_class: "org.apache.cassandra.locator.NetworkTopologyStrategy"
|
41
|
+
# strategy_options: {"DS1": "3", "DS2": "3", "DS3": "3"}
|
42
|
+
# connection_options:
|
43
|
+
# timeout: 10
|
44
|
+
# retries: 2
|
45
|
+
# server_max_requests: 1000
|
46
|
+
# solr:
|
47
|
+
# port: 8983
|
48
|
+
# path: /solr
|
49
|
+
#
|
50
|
+
# The +servers+ entry should be a list of all of the servers in your local datacenter. These
|
51
|
+
# are the servers that DSR will attempt to connect to and will round-robin through.
|
52
|
+
#
|
53
|
+
# Since we're using the NetworkTopologyStrategy for our locator, it is important that you configure
|
54
|
+
# cassandra-topology.properties. See the DSE documentation at http://www.datastax.com for more
|
55
|
+
# information.
|
56
|
+
#
|
57
|
+
# strategy_options lets us specify what our topology looks like. In this case, we have RF=3 in all
|
58
|
+
# three of our datacenters (DS1, DS2, and DS3).
|
59
|
+
#
|
60
|
+
# connection_options are the options that are passed to the thrift layer for the connection to
|
61
|
+
# cassandra.
|
62
|
+
# * *retries* - Number of times a request will be retried. Should likely be the number of servers - 1. Defaults to 0.
|
63
|
+
# * *server_retry_period* - Amount of time to wait before retrying a down server. Defaults to 1.
|
64
|
+
# * *server_max_requests* - Number of requests to make to a server before moving to the next one (helps keep load balanced). Default to nil which means cycling does not take place.
|
65
|
+
# * *retry_overrides* - Overrides retries option for individual exceptions.
|
66
|
+
# * *connect_timeout* - The connection timeout on the Thrift socket. Defaults to 0.1.
|
67
|
+
# * *timeout* - The timeout for the transport layer. Defaults to 1.
|
68
|
+
# * *timeout_overrides* - Overrides the timeout value for specific methods (advanced).
|
69
|
+
# * *exception_classes* - List of exceptions for which Thrift will automatically retry a new server in the cluster (up to retry limit).
|
70
|
+
# Defaults to [IOError, Thrift::Exception, Thrift::ApplicationException, Thrift::TransportException].
|
71
|
+
# * *exception_class_overrides* - List of exceptions which will never cause a retry. Defaults to [CassandraCQL::Thrift::InvalidRequestException].
|
72
|
+
# * *wrapped_exception_options* - List of exceptions that will be automatically wrapped in an exception provided by client class with the same name (advanced).
|
73
|
+
# Defaults to [Thrift::ApplicationException, Thrift::TransportException].
|
74
|
+
# * *raise* - Whether to raise exceptions or default calls that cause an error (advanced). Defaults to true (raise exceptions).
|
75
|
+
# * *defaults* - When raise is false and an error is encountered, these methods are called to default the return value (advanced). Should be a hash of method names to values.
|
76
|
+
# * *protocol* - The thrift protocol to use (advanced). Defaults to Thrift::BinaryProtocol.
|
77
|
+
# * *protocol_extra_params* - Any extra parameters to send to the protocol (advanced).
|
78
|
+
# * *transport* - The thrift transport to use (advanced). Defaults to Thrift::Socket.
|
79
|
+
# * *transport_wrapper* - The thrift transport wrapper to use (advanced). Defaults to Thrift::FramedTransport.
|
80
|
+
#
|
81
|
+
# See +solr_connection+ for a description of the solr options in datastax.yml
|
14
82
|
def establish_connection(spec)
|
15
83
|
DatastaxRails::Base.config = spec.with_indifferent_access
|
16
84
|
spec.reverse_merge!(DEFAULT_OPTIONS)
|
17
85
|
connection_options = spec[:connection_options] || {}
|
18
86
|
self.connection = CassandraCQL::Database.new(spec[:servers], {:keyspace => spec[:keyspace]}, connection_options.symbolize_keys)
|
19
87
|
end
|
88
|
+
|
89
|
+
# Similar to +establish_connection+, this method creates a connection object for Solr. Since HTTP is stateless, this doesn't
|
90
|
+
# actually launch the connection, but it gets everything set up so that RSolr can do its work. It's important to note that
|
91
|
+
# unlike the cassandra connection which is global to all of DSR, each model will have its own solr_connection.
|
92
|
+
def solr_connection
|
93
|
+
DatastaxRails::Base.establish_connection unless self.connection
|
94
|
+
port = DatastaxRails::Base.config[:solr][:port]
|
95
|
+
path = DatastaxRails::Base.config[:solr][:path]
|
96
|
+
@rsolr ||= DatastaxRails::RSolrClientWrapper.new(RSolr.connect :url => "http://#{self.current_server}:#{port}#{path}/#{DatastaxRails::Base.connection.keyspace}.#{self.column_family}")
|
97
|
+
end
|
20
98
|
end
|
21
99
|
end
|
22
100
|
end
|
@@ -13,8 +13,8 @@ module DatastaxRails
|
|
13
13
|
load 'datastax_rails/tasks/ds.rake'
|
14
14
|
end
|
15
15
|
|
16
|
-
generators do
|
17
|
-
require 'datastax_rails/generators/migration_generator'
|
18
|
-
end
|
16
|
+
# generators do
|
17
|
+
# require 'datastax_rails/generators/migration_generator'
|
18
|
+
# end
|
19
19
|
end
|
20
20
|
end
|
@@ -406,8 +406,9 @@ module DatastaxRails
|
|
406
406
|
end
|
407
407
|
end
|
408
408
|
|
409
|
-
|
410
|
-
|
409
|
+
# Calculates the solr URL and sets up an RSolr connection
|
410
|
+
def rsolr
|
411
|
+
@klass.solr_connection
|
411
412
|
end
|
412
413
|
end
|
413
414
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module DatastaxRails
|
2
|
+
# Wraps the RSolr Client class so that exceptions such as Connection Refused can be caught and
|
3
|
+
# a new server tried (if one is available)
|
4
|
+
class RSolrClientWrapper < BlankSlate
|
5
|
+
def initialize(rsolr)
|
6
|
+
@rsolr = rsolr
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(sym, *args, &block)
|
10
|
+
if @rsolr.uri.host != DatastaxRails::Base.current_server
|
11
|
+
@rsolr.uri.host = DatastaxRails::Base.current_server
|
12
|
+
@rsolr = RSolr.connect(:url => @rsolr.uri.to_s)
|
13
|
+
end
|
14
|
+
@rsolr.__send__(sym, *args, &block)
|
15
|
+
rescue Errno::ECONNREFUSED
|
16
|
+
tries ||= DatastaxRails::Base.thrift_client.options[:retries] + 1
|
17
|
+
tries -= 1
|
18
|
+
if tries > 0
|
19
|
+
# Force cassandra connection to roll
|
20
|
+
DatastaxRails::Cql::Select.new(SchemaMigration, ['id']).limit(1).execute
|
21
|
+
retry
|
22
|
+
else
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -2,12 +2,6 @@ require 'digest/sha1'
|
|
2
2
|
|
3
3
|
module DatastaxRails
|
4
4
|
module Tasks
|
5
|
-
class SchemaMigration
|
6
|
-
def self.column_family
|
7
|
-
'schema_migrations'
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
5
|
class ColumnFamily
|
12
6
|
COMPARATOR_TYPES = [:blob, :ascii, :text, :varint, :bigint, :uuid, :timestamp, :boolean, :float, :doublt, :decimal]
|
13
7
|
|
data/lib/datastax_rails.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'active_support/all'
|
2
2
|
require 'cassandra-cql/1.0'
|
3
|
+
require 'blankslate'
|
4
|
+
require 'schema_migration'
|
3
5
|
|
4
6
|
# Welcome to DatastaxRails. DatastaxRails::Base is probably a good place to start.
|
5
7
|
module DatastaxRails
|
@@ -27,6 +29,7 @@ module DatastaxRails
|
|
27
29
|
autoload :SpawnMethods
|
28
30
|
end
|
29
31
|
|
32
|
+
autoload :RSolrClientWrapper, 'datastax_rails/rsolr_client_wrapper'
|
30
33
|
autoload :Schema
|
31
34
|
autoload :Scoping
|
32
35
|
autoload :Serialization
|
@@ -8,7 +8,7 @@ describe DatastaxRails::Cql::Update do
|
|
8
8
|
it "should generate valid CQL" do
|
9
9
|
cql = DatastaxRails::Cql::Update.new(@model_class, "12345")
|
10
10
|
cql.using(DatastaxRails::Cql::Consistency::QUORUM).columns(:name => 'John', :age => '23')
|
11
|
-
cql.to_cql.should == "update users using consistency QUORUM SET
|
11
|
+
cql.to_cql.should == "update users using consistency QUORUM SET name = 'John', age = '23' WHERE KEY IN ('12345')"
|
12
12
|
end
|
13
13
|
|
14
14
|
it_has_behavior "default_consistency"
|
@@ -3,16 +3,20 @@ development:
|
|
3
3
|
keyspace: "datastax_rails_development"
|
4
4
|
strategy_class: "org.apache.cassandra.locator.SimpleStrategy"
|
5
5
|
strategy_options: {"DC1": "1"}
|
6
|
-
|
6
|
+
connection_options:
|
7
|
+
timeout: 10
|
7
8
|
solr:
|
8
|
-
|
9
|
+
port: 8983
|
10
|
+
path: /solr
|
9
11
|
|
10
12
|
test:
|
11
13
|
servers: ["localhost:9160"]
|
12
14
|
keyspace: "datastax_rails_test"
|
13
15
|
strategy_class: "org.apache.cassandra.locator.SimpleStrategy"
|
14
16
|
strategy_options: {"DC1": "1"}
|
15
|
-
|
17
|
+
connection_options:
|
18
|
+
timeout: 10
|
16
19
|
solr:
|
17
|
-
|
20
|
+
port: 8983
|
21
|
+
path: /solr
|
18
22
|
|