active_record_slave 0.2.0 → 1.0.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.
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>