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 CHANGED
@@ -36,10 +36,12 @@ Configure the config/datastax.yml file:
36
36
  connection_options:
37
37
  timeout: 2
38
38
  solr:
39
- url: http://localhost:8983/solr
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
- url: http://localhost:8983/solr
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
@@ -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
- def rsolr #:nodoc:
410
- @rsolr ||= RSolr.connect :url => "#{DatastaxRails::Base.config[:solr][:url]}/#{DatastaxRails::Base.connection.keyspace}.#{@klass.column_family}"
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
 
@@ -1,4 +1,4 @@
1
1
  module DatastaxRails
2
2
  # The current version of the gem
3
- VERSION = "1.0.11"
3
+ VERSION = "1.0.12"
4
4
  end
@@ -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
@@ -0,0 +1,8 @@
1
+ # Placeholder class to imitate a model for the schema migrations column family.
2
+ # Used as part of the CQL generation.
3
+ class SchemaMigration
4
+ # Returns the name of the column family
5
+ def self.column_family
6
+ 'schema_migrations'
7
+ end
8
+ end
@@ -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 age = '23', name = 'John' WHERE KEY IN ('12345')"
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
- replication_factor: 1
6
+ connection_options:
7
+ timeout: 10
7
8
  solr:
8
- url: http://localhost:8983/solr
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
- replication_factor: 1
17
+ connection_options:
18
+ timeout: 10
16
19
  solr:
17
- url: http://localhost:8983/solr
20
+ port: 8983
21
+ path: /solr
18
22