active_record_slave 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source :rubygems
2
+
3
+ group :test do
4
+ gem "shoulda"
5
+ end
6
+
7
+ gem "activerecord"
8
+ platforms :ruby do
9
+ gem 'sqlite3'
10
+ end
11
+
12
+ platforms :jruby do
13
+ gem 'jdbc-sqlite3'
14
+ gem 'activerecord-jdbcsqlite3-adapter'
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.2.8)
5
+ activesupport (= 3.2.8)
6
+ builder (~> 3.0.0)
7
+ activerecord (3.2.8)
8
+ activemodel (= 3.2.8)
9
+ activesupport (= 3.2.8)
10
+ arel (~> 3.0.2)
11
+ tzinfo (~> 0.3.29)
12
+ activesupport (3.2.8)
13
+ i18n (~> 0.6)
14
+ multi_json (~> 1.0)
15
+ arel (3.0.2)
16
+ builder (3.0.4)
17
+ i18n (0.6.1)
18
+ multi_json (1.3.7)
19
+ shoulda (3.3.2)
20
+ shoulda-context (~> 1.0.1)
21
+ shoulda-matchers (~> 1.4.1)
22
+ shoulda-context (1.0.1)
23
+ shoulda-matchers (1.4.1)
24
+ activesupport (>= 3.0.0)
25
+ sqlite3 (1.3.6)
26
+ tzinfo (0.3.35)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ activerecord
33
+ shoulda
34
+ sqlite3
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  active_record_slave
2
2
  ===================
3
3
 
4
+ ActiveRecord drop-in solution to efficiently redirect reads to slave databases
5
+
4
6
  * http://github.com/ClarityServices/active_record_slave
5
7
 
6
8
  ## Introduction
@@ -13,6 +15,7 @@ database to ensure data consistency.
13
15
  ## Features
14
16
 
15
17
  * Redirecting reads to a single slave database
18
+ * Works with any database driver that works with ActiveRecord
16
19
  * Supports all Rails 3 read apis, including dynamic finders, AREL, and ActiveRecord::Base.select
17
20
  * Transaction aware. Detects when a query is inside of a transaction and sends
18
21
  those reads to the master
@@ -47,6 +50,39 @@ Log file output:
47
50
  03-13-12 06:02:09 pm,[2608],b[0],[0], Role Load (2.0ms) SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'manager' LIMIT 1
48
51
  03-13-12 06:02:09 pm,[2608],b[0],[0], AREL (2.0ms) UPDATE `roles` SET `description` = 'Manager' WHERE `roles`.`id` = 4
49
52
 
53
+ ### Forcing a read against the master
54
+
55
+ Sometimes it is necessary to read from the master:
56
+
57
+ ```ruby
58
+ ActiveRecordSlave.read_from_master do
59
+ r = Role.where(:name => "manager").first
60
+ end
61
+ ```
62
+
63
+ ## Usage Notes
64
+
65
+ ### delete_all
66
+
67
+ Delete all executes against the master database since it is only a delete:
68
+
69
+ ```
70
+ D, [2012-11-06T19:47:29.125932 #89772] DEBUG -- : SQL (1.0ms) DELETE FROM "users"
71
+ ```
72
+
73
+ ### destroy_all
74
+
75
+ First performs a read against the slave database and then deletes the corresponding
76
+ data from the master
77
+
78
+ ```
79
+ D, [2012-11-06T19:43:26.890674 #89002] DEBUG -- : Slave: User Load (0.1ms) SELECT "users".* FROM "users"
80
+ D, [2012-11-06T19:43:26.890972 #89002] DEBUG -- : (0.0ms) begin transaction
81
+ D, [2012-11-06T19:43:26.891667 #89002] DEBUG -- : SQL (0.4ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 3]]
82
+ D, [2012-11-06T19:43:26.892697 #89002] DEBUG -- : (0.9ms) commit transaction
83
+ ```
84
+
85
+
50
86
  ## Requirements
51
87
 
52
88
  * ActiveRecord 3 or greater (Rails 3 or greater)
@@ -114,9 +150,16 @@ slave reads only on the linux host 'batch':
114
150
  pool: 20
115
151
  <% end %>
116
152
 
153
+ ## Tests
154
+
155
+ * active_record_slave is running in a large production system and was tested
156
+ directly as part of that solution
157
+ * I welcome anyone that can submit tests to verify this gem in a standalone outside
158
+ environment
159
+
117
160
  ## Possible Future Enhancements
118
161
 
119
- * Support multiple slaves (ask for it by submitting a ticket)
162
+ * Support for multiple slaves (ask for it by submitting a ticket)
120
163
 
121
164
  Meta
122
165
  ----
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ task :gem do |t|
18
18
  s.date = Date.today.to_s
19
19
  s.summary = "ActiveRecord read from slave"
20
20
  s.description = "ActiveRecordSlave is a library to seamlessly enable reading from database slaves in a Rails 3 project, written in Ruby."
21
- s.files = FileList["./**/*"].exclude('*.gem', 'nbproject').map{|f| f.sub(/^\.\//, '')}
21
+ s.files = FileList["./**/*"].exclude(/.gem$/, /.log$/,/nbproject/,/sqlite3$/).map{|f| f.sub(/^\.\//, '')}
22
22
  s.has_rdoc = true
23
23
  #s.add_dependency 'activerecord', '>= 3.0.0'
24
24
  end
@@ -4,17 +4,30 @@
4
4
  module ActiveRecordSlave
5
5
 
6
6
  # Install ActiveRecord::Slave into ActiveRecord to redirect reads to the slave
7
- # By default, only the default Database adapter (ActiveRecord::Base.connection.class)
8
- # is extended with slave read capabilities
9
- def self.install!(adapter_class = nil)
10
- if slave_config = ActiveRecord::Base.connection.config[:slave]
11
- Rails.logger.info "ActiveRecordSlave.install! v#{ActiveRecordSlave::VERSION} Establishing connection to slave database"
7
+ # Parameters:
8
+ # adapter_class:
9
+ # By default, only the default Database adapter (ActiveRecord::Base.connection.class)
10
+ # is extended with slave read capabilities
11
+ #
12
+ # environment:
13
+ # In a non-Rails environment, supply the environment such as
14
+ # 'development', 'production'
15
+ def self.install!(adapter_class = nil, environment = nil)
16
+ slave_config = if ActiveRecord::Base.connection.respond_to?(:config)
17
+ ActiveRecord::Base.connection.config[:slave]
18
+ else
19
+ ActiveRecord::Base.configurations[environment || Rails.env]['slave']
20
+ end
21
+ if slave_config
22
+ ActiveRecord::Base.logger.info "ActiveRecordSlave.install! v#{ActiveRecordSlave::VERSION} Establishing connection to slave database"
12
23
  Slave.establish_connection(slave_config)
13
24
 
14
25
  # Inject a new #select method into the ActiveRecord Database adapter
15
26
  base = adapter_class || ActiveRecord::Base.connection.class
16
27
  base.send(:include, InstanceMethods)
17
28
  base.alias_method_chain(:select, :slave_reader)
29
+ else
30
+ ActiveRecord::Base.logger.info "ActiveRecordSlave no slave database defined"
18
31
  end
19
32
  end
20
33
 
@@ -3,14 +3,14 @@ module ActiveRecordSlave
3
3
 
4
4
  # Database Adapter method #select is called for every select call
5
5
  # Replace #select with one that calls the slave connection instead
6
- def select_with_slave_reader(sql, name = nil)
6
+ def select_with_slave_reader(sql, name = nil, *args)
7
7
  # Only read from slave when not in a transaction and when this is not already the slave connection
8
8
  if (open_transactions == 0) && (Thread.current[:active_record_slave] != :master)
9
9
  ActiveRecordSlave.read_from_master do
10
- Slave.connection.select(sql, "Slave: #{name || 'SQL'}")
10
+ Slave.connection.select(sql, "Slave: #{name || 'SQL'}", *args)
11
11
  end
12
12
  else
13
- select_without_slave_reader(sql, name)
13
+ select_without_slave_reader(sql, name, *args)
14
14
  end
15
15
  end
16
16
 
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordSlave #:nodoc
2
- VERSION = "0.2.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,121 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'logger'
6
+ require 'erb'
7
+ require 'test/unit'
8
+ require 'shoulda'
9
+ require 'active_record'
10
+ require 'active_record_slave'
11
+
12
+ l = Logger.new('test.log')
13
+ l.level = ::Logger::DEBUG
14
+ ActiveRecord::Base.logger = l
15
+ ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read('test/database.yml')).result)
16
+
17
+ # Define Schema in second database (slave)
18
+ # Note: This is not be required when the master database is being replicated to the slave db
19
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']['slave'])
20
+
21
+ # Create table users in database active_record_slave_test
22
+ ActiveRecord::Schema.define :version => 0 do
23
+ create_table :users, :force => true do |t|
24
+ t.string :name
25
+ t.string :address
26
+ end
27
+ end
28
+
29
+ # Define Schema in master database
30
+ ActiveRecord::Base.establish_connection(:test)
31
+
32
+ # Create table users in database active_record_slave_test
33
+ ActiveRecord::Schema.define :version => 0 do
34
+ create_table :users, :force => true do |t|
35
+ t.string :name
36
+ t.string :address
37
+ end
38
+ end
39
+
40
+ # AR Model
41
+ class User < ActiveRecord::Base
42
+ end
43
+
44
+ # Install ActiveRecord slave. Done automatically by railtie in a Rails environment
45
+ # Also tell it to use the test environment since Rails.env is not available
46
+ ActiveRecordSlave.install!(nil, 'test')
47
+
48
+ #
49
+ # Unit Test for active_record_slave
50
+ #
51
+ class ActiveRecordSlaveTest < Test::Unit::TestCase
52
+ context 'the active_record_slave gem' do
53
+
54
+ setup do
55
+ User.delete_all
56
+
57
+ @name = "Joe Bloggs"
58
+ @address = "Somewhere"
59
+ @user = User.new(
60
+ :name => @name,
61
+ :address => @address
62
+ )
63
+ end
64
+
65
+ teardown do
66
+ User.delete_all
67
+ end
68
+
69
+ should "save to master" do
70
+ assert_equal true, @user.save!
71
+ end
72
+
73
+ #
74
+ # NOTE:
75
+ #
76
+ # There is no automated replication between the SQL lite databases
77
+ # so the tests will be verifying that reads going to the "slave" (second)
78
+ # database do not find data written to the master.
79
+ #
80
+ should "save to master, read from slave" do
81
+ # Read from slave
82
+ assert_equal 0, User.where(:name => @name, :address => @address).count
83
+
84
+ # Write to master
85
+ assert_equal true, @user.save!
86
+
87
+ # Read from slave
88
+ assert_equal 0, User.where(:name => @name, :address => @address).count
89
+ end
90
+
91
+ should "save to master, read from master when in a transaction" do
92
+ User.transaction do
93
+ # Read from Master
94
+ assert_equal 0, User.where(:name => @name, :address => @address).count
95
+
96
+ # Write to master
97
+ assert_equal true, @user.save!
98
+
99
+ # Read from Master
100
+ assert_equal 1, User.where(:name => @name, :address => @address).count
101
+ end
102
+ end
103
+
104
+ should "save to master, force a read from master even when _not_ in a transaction" do
105
+ # Read from slave
106
+ assert_equal 0, User.where(:name => @name, :address => @address).count
107
+
108
+ # Write to master
109
+ assert_equal true, @user.save!
110
+
111
+ # Read from slave
112
+ assert_equal 0, User.where(:name => @name, :address => @address).count
113
+
114
+ # Read from Master
115
+ ActiveRecordSlave.read_from_master do
116
+ assert_equal 1, User.where(:name => @name, :address => @address).count
117
+ end
118
+ end
119
+
120
+ end
121
+ end
data/test/database.yml ADDED
@@ -0,0 +1,12 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: test/db/test.sqlite3
4
+ pool: 5
5
+ timeout: 5000
6
+ # Make the slave a separate database that is not slaved to ensure reads
7
+ # and writes go to the appropriate databases
8
+ slave:
9
+ adapter: sqlite3
10
+ database: test/db/test_slave.sqlite3
11
+ pool: 5
12
+ timeout: 5000
metadata CHANGED
@@ -1,34 +1,26 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: active_record_slave
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 2
8
- - 0
9
- version: 0.2.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Reid Morrison
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2012-03-16 00:00:00 -04:00
18
- default_executable:
12
+ date: 2012-11-06 00:00:00.000000000 Z
19
13
  dependencies: []
20
-
21
- description: ActiveRecordSlave is a library to seamlessly enable reading from database slaves in a Rails 3 project, written in Ruby.
22
- email:
14
+ description: ActiveRecordSlave is a library to seamlessly enable reading from database
15
+ slaves in a Rails 3 project, written in Ruby.
16
+ email:
23
17
  - reidmo@gmail.com
24
18
  executables: []
25
-
26
19
  extensions: []
27
-
28
20
  extra_rdoc_files: []
29
-
30
- files:
31
- - active_record_slave-0.1.0.gem
21
+ files:
22
+ - Gemfile
23
+ - Gemfile.lock
32
24
  - lib/active_record_slave/active_record_slave.rb
33
25
  - lib/active_record_slave/instance_methods.rb
34
26
  - lib/active_record_slave/railtie.rb
@@ -36,43 +28,35 @@ files:
36
28
  - lib/active_record_slave/version.rb
37
29
  - lib/active_record_slave.rb
38
30
  - LICENSE.txt
39
- - nbproject/private/config.properties
40
- - nbproject/private/private.properties
41
- - nbproject/private/private.xml
42
- - nbproject/private/rake-d.txt
43
- - nbproject/project.properties
44
- - nbproject/project.xml
45
31
  - Rakefile
46
32
  - README.md
47
- has_rdoc: true
33
+ - test/active_record_slave_test.rb
34
+ - test/database.yml
48
35
  homepage: https://github.com/ClarityServices/active_record_slave
49
36
  licenses: []
50
-
51
37
  post_install_message:
52
38
  rdoc_options: []
53
-
54
- require_paths:
39
+ require_paths:
55
40
  - lib
56
- required_ruby_version: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- segments:
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ segments:
61
48
  - 0
62
- version: "0"
63
- required_rubygems_version: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- segments:
68
- - 0
69
- version: "0"
49
+ hash: -2259159571471524106
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
70
56
  requirements: []
71
-
72
57
  rubyforge_project:
73
- rubygems_version: 1.3.6
58
+ rubygems_version: 1.8.24
74
59
  signing_key:
75
60
  specification_version: 3
76
61
  summary: ActiveRecord read from slave
77
62
  test_files: []
78
-
Binary file
File without changes
@@ -1,3 +0,0 @@
1
- file.reference.activerecord_slave-lib=/Users/rmorrison/Sandbox/activerecord_slave/lib
2
- file.reference.activerecord_slave-test=/Users/rmorrison/Sandbox/activerecord_slave/test
3
- platform.active=JRuby_0
@@ -1,4 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project-private xmlns="http://www.netbeans.org/ns/project-private/1">
3
- <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
4
- </project-private>
@@ -1,4 +0,0 @@
1
- clean=
2
- clobber=
3
- gem=
4
- test=
@@ -1,8 +0,0 @@
1
- file.reference.activerecord_slave-lib=lib
2
- file.reference.activerecord_slave-test=test
3
- javac.classpath=
4
- main.file=
5
- platform.active=JRuby_0
6
- source.encoding=UTF-8
7
- src.lib.dir=lib
8
- test.test.dir=test
@@ -1,15 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project xmlns="http://www.netbeans.org/ns/project/1">
3
- <type>org.netbeans.modules.ruby.rubyproject</type>
4
- <configuration>
5
- <data xmlns="http://www.netbeans.org/ns/ruby-project/1">
6
- <name>active_record_slave</name>
7
- <source-roots>
8
- <root id="src.lib.dir" name="Source Files"/>
9
- </source-roots>
10
- <test-roots>
11
- <root id="test.test.dir" name="Test Files"/>
12
- </test-roots>
13
- </data>
14
- </configuration>
15
- </project>