replicat 0.0.1

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +135 -0
  6. data/Rakefile +1 -0
  7. data/lib/replicat.rb +6 -0
  8. data/lib/replicat/active_record.rb +1 -0
  9. data/lib/replicat/model.rb +9 -0
  10. data/lib/replicat/proxy.rb +100 -0
  11. data/lib/replicat/replicable.rb +54 -0
  12. data/lib/replicat/version.rb +3 -0
  13. data/replicat.gemspec +33 -0
  14. data/spec/dummy/Rakefile +7 -0
  15. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  16. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  17. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  18. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  19. data/spec/dummy/app/mailers/.gitkeep +0 -0
  20. data/spec/dummy/app/models/.gitkeep +0 -0
  21. data/spec/dummy/app/models/recipe.rb +3 -0
  22. data/spec/dummy/app/models/user.rb +3 -0
  23. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  24. data/spec/dummy/config.ru +4 -0
  25. data/spec/dummy/config/application.rb +51 -0
  26. data/spec/dummy/config/boot.rb +10 -0
  27. data/spec/dummy/config/database.yml +41 -0
  28. data/spec/dummy/config/environment.rb +5 -0
  29. data/spec/dummy/config/environments/development.rb +23 -0
  30. data/spec/dummy/config/environments/production.rb +71 -0
  31. data/spec/dummy/config/environments/test.rb +33 -0
  32. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  33. data/spec/dummy/config/initializers/inflections.rb +15 -0
  34. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  35. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  36. data/spec/dummy/config/initializers/session_store.rb +8 -0
  37. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  38. data/spec/dummy/config/locales/en.yml +5 -0
  39. data/spec/dummy/config/routes.rb +2 -0
  40. data/spec/dummy/db/schema.rb +26 -0
  41. data/spec/dummy/lib/assets/.gitkeep +0 -0
  42. data/spec/dummy/log/.gitkeep +0 -0
  43. data/spec/dummy/public/404.html +26 -0
  44. data/spec/dummy/public/422.html +26 -0
  45. data/spec/dummy/public/500.html +25 -0
  46. data/spec/dummy/public/favicon.ico +0 -0
  47. data/spec/dummy/script/rails +6 -0
  48. data/spec/replicat/replicable_spec.rb +81 -0
  49. data/spec/spec_helper.rb +15 -0
  50. metadata +324 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a4318b9a2e2e1c7e637af213a8ae09f2d292235e
4
+ data.tar.gz: 5063232ce2dcc9403137e7d4ec2f5ea4d011a5b9
5
+ SHA512:
6
+ metadata.gz: 8220a33336d837f1adfe5b848eaf9a301ddbdf9c9487abb4f7c90a6e6558f43907195e517ef435ab18c60b3b75ac08a605202646d5e08ff52b0a5d9225125e1d
7
+ data.tar.gz: 66bf65c92a44b0935e92473c77a0ecae0f09b72b64668e33ef7c56a58b0fde6068e594fc24613c43fcc24f010d812d202e354993da4475e6acdf4279b39d5986
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ log/*.log
13
+ pkg
14
+ rdoc
15
+ spec/dummy/.sass-cache
16
+ spec/dummy/db/*.sqlite3
17
+ spec/dummy/log/*.log
18
+ spec/dummy/tmp/
19
+ spec/reports
20
+ test/tmp
21
+ test/version_tmp
22
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in replicat.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ryo Nakamura
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # Replicat
2
+ master-slave replication helper for ActiveRecord.
3
+
4
+ ## Installation
5
+ ```ruby
6
+ # Gemfile
7
+ gem "replicat"
8
+ ```
9
+
10
+ ## Usage
11
+ Modify your replicable models & config/database.yml.
12
+
13
+ ### model
14
+ ```ruby
15
+ # app/models/user.rb
16
+ class User < ActiveRecord::Base
17
+ replicate
18
+ end
19
+
20
+ # config/database.yml
21
+ production:
22
+ adapter: mysql2
23
+ encoding: utf8
24
+ host: 192.168.24.1
25
+ port: 3306
26
+ replications:
27
+ slave1:
28
+ adapter: mysql2
29
+ encoding: utf8
30
+ host: 192.168.24.2
31
+ port: 3306
32
+ slave2:
33
+ adapter: mysql2
34
+ encoding: utf8
35
+ host: 192.168.24.3
36
+ port: 3306
37
+ slave3:
38
+ adapter: mysql2
39
+ encoding: utf8
40
+ host: 192.168.24.4
41
+ port: 3306
42
+ ```
43
+
44
+ ### replication
45
+ Now SELECT queries of User model will be sent to slave connections.
46
+
47
+ ```ruby
48
+ User.create(name: "replicat")
49
+ User.first #=> nil
50
+ ```
51
+
52
+ ### using
53
+ `using` can help you specify particular connection.
54
+ `using(:master)` uses master connection.
55
+
56
+ ```ruby
57
+ User.create(name: "replicat")
58
+ User.using(:master) { User.first } #=> #<User id: 2, name: "replicat">
59
+ ```
60
+
61
+ ### round-robin
62
+ slave connections are balanced by round-robin way.
63
+
64
+ ```ruby
65
+ User.using(:slave1) { User.create(name: "replicat") }
66
+ User.first #=> #<User id: 2, name: "replicat">
67
+ User.first #=> nil
68
+ User.first #=> nil
69
+ User.first #=> #<User id: 2, name: "replicat">
70
+ User.first #=> nil
71
+ User.first #=> nil
72
+ User.first #=> #<User id: 2, name: "replicat">
73
+ User.first #=> nil
74
+ User.first #=> nil
75
+ ```
76
+
77
+ ### multi master-slave set
78
+ Pass the master's connection name to `replicate` method.
79
+
80
+ ```ruby
81
+ # app/models/user.rb
82
+ class User < ActiveRecord::Base
83
+ replicate "production_user"
84
+ end
85
+
86
+ # app/models/recipe.rb
87
+ class Recipe < ActiveRecord::Base
88
+ replicate "production_recipe"
89
+ end
90
+
91
+ # config/database.yml
92
+ production_user:
93
+ <<: *production
94
+ host: 192.168.24.1
95
+ replications:
96
+ slave1:
97
+ <<: *slave
98
+ host: 192.168.24.2
99
+ slave2:
100
+ <<: *slave
101
+ host: 192.168.24.3
102
+ slave3:
103
+ <<: *slave
104
+ host: 192.168.24.4
105
+
106
+ production_recipe:
107
+ <<: *production
108
+ host: 192.168.24.5
109
+ replications:
110
+ slave1:
111
+ <<: *slave
112
+ host: 192.168.24.6
113
+ slave2:
114
+ <<: *slave
115
+ host: 192.168.24.7
116
+ slave3:
117
+ <<: *slave
118
+ host: 192.168.24.8
119
+ ```
120
+
121
+
122
+ ## For contributors
123
+ ```sh
124
+ # setup gems
125
+ bundle install
126
+
127
+ # setup database
128
+ cd spec/dummy
129
+ rake db:create
130
+ rake db:schema:load RAILS_ENV=test
131
+ cd ../../
132
+
133
+ # run tests
134
+ bundle exec rspec
135
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/replicat.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "active_record"
2
+ require "replicat/model"
3
+ require "replicat/proxy"
4
+ require "replicat/replicable"
5
+ require "replicat/version"
6
+ require "replicat/active_record"
@@ -0,0 +1 @@
1
+ ActiveRecord::Base.extend Replicat::Model
@@ -0,0 +1,9 @@
1
+ module Replicat
2
+ module Model
3
+ def replicate(connection_name = nil)
4
+ raise "You must set `connection_name` of this model class." if !connection_name && !defined?(Rails)
5
+ include Replicat::Replicable
6
+ self.connection_name = connection_name || Rails.env.to_s
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,100 @@
1
+ require "active_support/core_ext/object/try"
2
+
3
+ module Replicat
4
+ class Proxy
5
+ REPLICABLE_METHOD_NAMES = %w[select_all schema_cache]
6
+ REPLICABLE_METHOD_NAMES_REGEXP = /\A#{Regexp.union(REPLICABLE_METHOD_NAMES)}\z/
7
+
8
+ attr_accessor :current_connection_name
9
+
10
+ attr_writer :index
11
+
12
+ def initialize(model_class)
13
+ @model_class = model_class
14
+ end
15
+
16
+ private
17
+
18
+ def method_missing(method_name, *args, &block)
19
+ if current_connection_name
20
+ current_connection.send(method_name, *args, &block)
21
+ else
22
+ connection_by_method_name(method_name).send(method_name, *args, &block)
23
+ end
24
+ end
25
+
26
+ def connection_by_method_name(method_name)
27
+ REPLICABLE_METHOD_NAMES_REGEXP === method_name ? slave_connection : master_connection
28
+ end
29
+
30
+ def current_connection
31
+ if current_connection_name.to_s == "master"
32
+ master_connection
33
+ else
34
+ slave_connection_pool_table[current_connection_name.to_s].try(:connection) or raise_connection_not_found
35
+ end
36
+ end
37
+
38
+ def master_connection
39
+ @model_class.connection_without_proxy
40
+ end
41
+
42
+ def slave_connection
43
+ slave_connection_pool.connection
44
+ end
45
+
46
+ def slave_connection_pool
47
+ slave_connection_pool_table.values[slave_connection_index]
48
+ end
49
+
50
+ def slave_connection_pool_table
51
+ @slave_connection_pools ||= @model_class.replications.inject({}) do |table, (name, configuration)|
52
+ table.merge(name => ConnectionPoolCreater.create(configuration))
53
+ end
54
+ end
55
+
56
+ def raise_connection_not_found
57
+ raise Error, "connection #{current_connection_name} is not found"
58
+ end
59
+
60
+ def slave_connection_index
61
+ index.tap { increment_slave_connection_index }
62
+ end
63
+
64
+ def increment_slave_connection_index
65
+ self.index = (index + 1) % slave_connection_pool_table.size
66
+ end
67
+
68
+ def clear_query_cache
69
+ master_connection.clear_query_cache
70
+ slave_connection_pool_table.values.map {|pool| pool.connection.clear_query_cache }
71
+ end
72
+
73
+ def index
74
+ @index ||= rand(slave_connection_pool_table.size)
75
+ end
76
+
77
+ # Creates database connection pool from configuration Hash table.
78
+ class ConnectionPoolCreater
79
+ def self.create(*args)
80
+ new(*args).create
81
+ end
82
+
83
+ def initialize(configuration)
84
+ @configuration = configuration.dup
85
+ end
86
+
87
+ def create
88
+ ActiveRecord::ConnectionAdapters::ConnectionPool.new(
89
+ ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(
90
+ @configuration,
91
+ nil,
92
+ ).spec,
93
+ )
94
+ end
95
+ end
96
+
97
+ class Error < StandardError
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,54 @@
1
+ require "active_record"
2
+ require "active_support/concern"
3
+ require "active_support/core_ext/module/aliasing"
4
+
5
+ module Replicat
6
+ module Replicable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class << self
11
+ def proxy
12
+ @proxy ||= Proxy.new(self)
13
+ end
14
+
15
+ alias_method_chain :connection, :proxy
16
+
17
+ attr_accessor :connection_name
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+ def connection_with_proxy
23
+ if has_any_replication?
24
+ proxy
25
+ else
26
+ connection_without_proxy
27
+ end
28
+ end
29
+
30
+ def has_any_replication?
31
+ has_configuration? && replications.present?
32
+ end
33
+
34
+ def has_configuration?
35
+ !!configuration
36
+ end
37
+
38
+ def configuration
39
+ connection_name && configurations[connection_name]
40
+ end
41
+
42
+ def replications
43
+ configuration["replications"]
44
+ end
45
+
46
+ def using(connection_name)
47
+ proxy.current_connection_name = connection_name
48
+ yield
49
+ ensure
50
+ proxy.current_connection_name = nil
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ module Replicat
2
+ VERSION = "0.0.1"
3
+ end
data/replicat.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "replicat/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "replicat"
7
+ spec.version = Replicat::VERSION
8
+ spec.authors = ["Ryo Nakamura"]
9
+ spec.email = ["r7kamura@gmail.com"]
10
+ spec.summary = "master-slave replication helper for ActiveRecord"
11
+ spec.homepage = "https://github.com/r7kamura/replicat"
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files`.split($/)
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_dependency "activerecord"
20
+ spec.add_dependency "activesupport"
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "coffee-rails", ">= 3.0.10"
23
+ spec.add_development_dependency "jquery-rails"
24
+ spec.add_development_dependency "pry"
25
+ spec.add_development_dependency "pry-rails"
26
+ spec.add_development_dependency "rails", ">= 3.0.10"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "rspec", ">= 2.14.1"
29
+ spec.add_development_dependency "rspec-rails", ">= 2.14.0"
30
+ spec.add_development_dependency "sass-rails", ">= 3.0.10"
31
+ spec.add_development_dependency "sqlite3"
32
+ spec.add_development_dependency "uglifier"
33
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
3
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4
+
5
+ require File.expand_path('../config/application', __FILE__)
6
+
7
+ Dummy::Application.load_tasks
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .