connection_manager 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +1 -0
  3. data/Gemfile.lock +8 -3
  4. data/README.md +26 -26
  5. data/connection_manager.gemspec +2 -0
  6. data/lib/connection_manager.rb +1 -0
  7. data/lib/connection_manager/connections.rb +34 -25
  8. data/lib/connection_manager/replication_builder.rb +11 -12
  9. data/lib/connection_manager/version.rb +1 -1
  10. data/spec/database.yml +20 -0
  11. data/spec/factories.rb +31 -0
  12. data/spec/helpers/database_spec_helper.rb +68 -0
  13. data/spec/helpers/models_spec_helper.rb +26 -0
  14. data/spec/integration/replication_builder_spec.rb +97 -0
  15. data/{test_app/spec/connection_manager → spec/lib}/associations_spec.rb +0 -0
  16. data/spec/lib/connections_spec.rb +93 -0
  17. data/{test_app/spec/connection_manager → spec/lib}/replication_builder_spec.rb +17 -1
  18. data/spec/spec_helper.rb +17 -5
  19. metadata +49 -60
  20. data/test_app/.gitignore +0 -15
  21. data/test_app/.rspec +0 -1
  22. data/test_app/Gemfile +0 -14
  23. data/test_app/Gemfile.lock +0 -140
  24. data/test_app/README +0 -261
  25. data/test_app/Rakefile +0 -7
  26. data/test_app/app/models/.gitkeep +0 -0
  27. data/test_app/app/models/basket.rb +0 -4
  28. data/test_app/app/models/fruit.rb +0 -6
  29. data/test_app/app/models/fruit_basket.rb +0 -4
  30. data/test_app/app/models/region.rb +0 -3
  31. data/test_app/app/models/type.rb +0 -2
  32. data/test_app/config.ru +0 -4
  33. data/test_app/config/application.rb +0 -52
  34. data/test_app/config/boot.rb +0 -6
  35. data/test_app/config/database.yml +0 -42
  36. data/test_app/config/environment.rb +0 -5
  37. data/test_app/config/environments/development.rb +0 -30
  38. data/test_app/config/environments/production.rb +0 -60
  39. data/test_app/config/environments/test.rb +0 -39
  40. data/test_app/config/initializers/backtrace_silencers.rb +0 -7
  41. data/test_app/config/initializers/inflections.rb +0 -10
  42. data/test_app/config/initializers/load_connection_manager.rb +0 -6
  43. data/test_app/config/initializers/mime_types.rb +0 -5
  44. data/test_app/config/initializers/secret_token.rb +0 -7
  45. data/test_app/config/initializers/session_store.rb +0 -8
  46. data/test_app/config/initializers/wrap_parameters.rb +0 -14
  47. data/test_app/config/locales/en.yml +0 -5
  48. data/test_app/config/routes.rb +0 -58
  49. data/test_app/db/migrate/20111127040654_create_fruits.rb +0 -9
  50. data/test_app/db/migrate/20111127040720_create_baskets.rb +0 -9
  51. data/test_app/db/migrate/20111127040846_create_fruit_baskets.rb +0 -9
  52. data/test_app/db/migrate/20111127040915_create_regions.rb +0 -9
  53. data/test_app/db/migrate/20111127060322_create_types.rb +0 -9
  54. data/test_app/db/schema.rb +0 -16
  55. data/test_app/db/seeds.rb +0 -7
  56. data/test_app/log/.gitkeep +0 -0
  57. data/test_app/script/rails +0 -6
  58. data/test_app/spec/connection_manager/connections_spec.rb +0 -51
  59. data/test_app/spec/factories/baskets.rb +0 -7
  60. data/test_app/spec/factories/fruit_baskets.rb +0 -8
  61. data/test_app/spec/factories/fruits.rb +0 -8
  62. data/test_app/spec/factories/regions.rb +0 -7
  63. data/test_app/spec/factories/types.rb +0 -7
  64. data/test_app/spec/models/type_spec.rb +0 -5
  65. data/test_app/spec/spec.opts +0 -4
  66. data/test_app/spec/spec_helper.rb +0 -42
  67. data/test_app/test_app +0 -0
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  pkg/*
2
2
  *.gem
3
+ *.sqlite3
3
4
  .bundle
4
5
  .DS_Store
5
6
 
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activerecord-connection_manager (0.0.1)
4
+ connection_manager (0.1.0)
5
5
  activerecord (~> 3.0)
6
6
 
7
7
  GEM
@@ -24,11 +24,13 @@ GEM
24
24
  ZenTest (>= 4.4.1)
25
25
  builder (3.0.0)
26
26
  diff-lcs (1.1.3)
27
+ factory_girl (2.3.2)
28
+ activesupport
27
29
  i18n (0.6.0)
28
30
  metaclass (0.0.1)
29
31
  mocha (0.10.0)
30
32
  metaclass (~> 0.0.1)
31
- multi_json (1.0.3)
33
+ multi_json (1.0.4)
32
34
  rspec (2.7.0)
33
35
  rspec-core (~> 2.7.0)
34
36
  rspec-expectations (~> 2.7.0)
@@ -37,13 +39,16 @@ GEM
37
39
  rspec-expectations (2.7.0)
38
40
  diff-lcs (~> 1.1.2)
39
41
  rspec-mocks (2.7.0)
42
+ sqlite3 (1.3.4)
40
43
  tzinfo (0.3.31)
41
44
 
42
45
  PLATFORMS
43
46
  ruby
44
47
 
45
48
  DEPENDENCIES
46
- activerecord-connection_manager!
47
49
  autotest
50
+ connection_manager!
51
+ factory_girl
48
52
  mocha
49
53
  rspec
54
+ sqlite3
data/README.md CHANGED
@@ -1,23 +1,23 @@
1
- # connection_manager
1
+ # ConnectionManager
2
2
  Replication and Multi-Database ActiveRecord add on.
3
3
 
4
4
  ## Goals
5
5
  * Take the lib I've been using finally make something out of it ;)
6
- * Use connection classes instead of establish_connection on every model to ensure connection pooling
7
- * Focus connection management at the model level
6
+ * Use connection classes, instead of establish_connection on every model, to ensure connection pooling
7
+ * Use non-adapter specific code.
8
8
  * Use the default database.yml as single point for all database configurations (no extra .yml files)
9
- * When slave objects are used in html helpers like link_to and form_for the created urls that match the master
9
+ * When slave objects are used in html helpers like link_to and form_for the created urls match those created using a master object
10
10
 
11
11
  ## Installation
12
12
 
13
- connection_manager is available through [Rubygems](https://rubygems.org/gems/connection_manager) and can be installed via:
13
+ ConnectionManager is available through [Rubygems](https://rubygems.org/gems/connection_manager) and can be installed via:
14
14
 
15
15
  $ gem install connection_manager
16
16
 
17
17
  ## Rails 3 setup (Rails 2 untested at this time please let me know if it works for you )
18
18
 
19
- connection_manager assumes the primary connection for the model is the master. For standard
20
- models using the default connection this means the main rails database connection is the master.
19
+ ConnectionManager assumes the primary connection for the model is the master. For standard
20
+ models using the default connection this means the main Rails database connection is the master.
21
21
 
22
22
  Example database.yml
23
23
 
@@ -37,7 +37,7 @@ Example database.yml
37
37
 
38
38
  slave_1_test_app_development:
39
39
  <<: *common
40
- database: portal_production
40
+ database: test_app
41
41
 
42
42
  slave_2_test_app_development:
43
43
  <<: *common
@@ -62,33 +62,32 @@ is the databases name and finally the "development" is the environment. (Of cour
62
62
  each slave would have a different connection to is replication :)
63
63
 
64
64
 
65
- ### Setup Multiple Databases
65
+ ## Multiple Databases
66
66
 
67
- At startup connection_manager builds connection classes to ConnectionManager::Connections
67
+ At startup ConnectionManager builds connection classes to ConnectionManager::Connections
68
68
  using the connections described in your database.yml based on the current rails environment.
69
69
 
70
70
  You can use a different master by having the model inherit from one of your ConnectionManager::Connections.
71
71
 
72
72
  To view your ConnectionManager::Connections, at the Rails console type:
73
73
 
74
- ruby-1.9.2-p290 :001 > ConnectionManager::Connections.all => ["TestAppConnection", "Slave1TestAppConnection", "Slave2TestAppConnection"]
74
+ ConnectionManager::Connections.all => ["TestAppConnection", "Slave1TestAppConnection", "Slave2TestAppConnection"]
75
75
 
76
76
  If your using the example database.yml your array would look like this:
77
77
  ["TestAppConnection", "Slave1TestAppConnection", "Slave2TestAppConnection",
78
78
  "UserDataConnection", "Slave1UserDataConnection", "Slave2UserDataConnection"]
79
79
 
80
80
 
81
-
82
81
  To use one of your ConnectionManager::Connections for your models default/master database
83
- setup you model like the following
82
+ setup your model like the following
84
83
 
85
84
  class User < ConnectionManager::Connections::UserDataConnection
86
85
  # model code ...
87
86
  end
88
87
 
89
- ### Replication
88
+ ## Replication
90
89
 
91
- simply add 'replicated' to you model beneath any defined associations
90
+ Simply add 'replicated' to your model beneath any defined associations
92
91
 
93
92
  class User < ConnectionManager::Connections::UserDataConnection
94
93
  has_one :job
@@ -101,30 +100,31 @@ The replicated method addeds subclass whose names match the replication connecti
101
100
  Based on the above example database.yml User class would now have User::Slave1 and User::Slave2.
102
101
 
103
102
  You can treat your subclass like normal activerecord objects.
104
- User::Slave1.first => returns results from slave_1_use_data_development
105
- User::Slave2.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_2_use_data_development
103
+
104
+ User::Slave1.first => returns results from slave_1_user_data_development
105
+ User::Slave2.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_2_user_data_development
106
106
 
107
- For a more elegant implementation, connection_manager also add class methods to you main model following the
107
+ For a more elegant implementation, ConnectionManager also add class methods to your main model following the
108
108
  same naming standard as the subclass creation.
109
- User.slave_1.first => returns results from slave_1_use_data_development
110
- User.slave_2.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_2_use_data_development
109
+
110
+ User.slave_1.first => returns results from slave_1_user_data_development
111
+ User.slave_2.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_2_user_data_development
111
112
 
112
- Finally connection_manager creates an addional class method that shifts through your
113
+ Finally, ConnectionManager creates an addional class method that shifts through your
113
114
  available slave connections each time it is called using a different connection on each action.
115
+
114
116
  User.slave.first => returns results from slave_1_use_data_development
115
117
  User.slave.last => => returns results from slave_2_use_data_development
116
- User.slave.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_1_use_data_development
117
- User.slave.where(['created_at BETWEEN ? and ?',Time.now - 5.days, Time.now]).all => returns results from slave_2_use_data_development
118
+ User.slave.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_1_user_data_development
119
+ User.slave.where(['created_at BETWEEN ? and ?',Time.now - 5.days, Time.now]).all => returns results from slave_2_user_data_development
118
120
 
119
121
  ## TODO's
120
- * add more to readme
121
- * more specs
122
122
  * sharding
123
123
 
124
124
  ## Other activerecord Connection gems
125
125
  * [Octopus](https://github.com/tchandy/octopus)
126
126
 
127
- ## Contributing to connection_manager
127
+ ## Contributing to ConnectionManager
128
128
 
129
129
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
130
130
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
@@ -18,7 +18,9 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
  s.add_runtime_dependency 'activerecord', '~> 3.0'
21
+ s.add_development_dependency 'sqlite3'
21
22
  s.add_development_dependency 'rspec'
22
23
  s.add_development_dependency 'autotest'
23
24
  s.add_development_dependency 'mocha'
25
+ s.add_development_dependency 'factory_girl'
24
26
  end
@@ -1,6 +1,7 @@
1
1
  require "connection_manager/version"
2
2
 
3
3
  module ConnectionManager
4
+ require 'active_record'
4
5
  require 'connection_manager/connections'
5
6
  require 'connection_manager/associations'
6
7
  require 'connection_manager/replication_builder'
@@ -40,44 +40,49 @@ module ConnectionManager
40
40
 
41
41
  # Returns the database value given a connection key from the database.yml
42
42
  def database_name_from_yml(name_from_yml)
43
- ActiveRecord::Base.configurations[name_from_yml]['database'].to_s
43
+ ActiveRecord::Base.configurations[name_from_yml]['database']
44
44
  end
45
45
 
46
+ def clean_sqlite_db_name(name)
47
+ new_name = "#{name}".gsub(/(\.sqlite3$)/,'')
48
+ new_name = new_name.split("/").last
49
+ new_name.gsub!(Regexp.new("(\_#{env}$)"),'')
50
+ new_name
51
+ end
52
+
53
+ def clean_bd_name(name)
54
+ new_name = "#{name}".gsub(Regexp.new("#{env}$"),'')
55
+ if new_name.blank?
56
+ new_name = "#{database_name_from_yml(name)}"
57
+ end
58
+ new_name = clean_sqlite_db_name(new_name)
59
+ new_name.gsub(/\_$/,'')
60
+ end
61
+
46
62
  # Given an connection key name from the database.yml, returns the string
47
63
  # equivelent of the class name for that entry.
48
64
  def connection_class_name(name_from_yml)
49
- name_from_yml = name_from_yml #clean up sqlite database names
50
- new_class_name = name_from_yml.gsub(Regexp.new("#{env}$"),'')
51
- new_class_name = database_name_from_yml(name_from_yml) if new_class_name.blank?
52
-
53
- #cleanup sqlite database names
54
- if new_class_name.gsub!(/(^db\/)|(\.sqlite3$)/,'')
55
- new_class_name.gsub!(Regexp.new("(\\_#{env}$)"),'')
56
- end
57
-
65
+ new_class_name = clean_bd_name(name_from_yml)
58
66
  new_class_name = new_class_name.gsub(/\_/,' ').titleize.gsub(/ /,'')
59
67
  new_class_name << "Connection"
60
68
  new_class_name
61
69
  end
62
70
 
63
-
71
+ def replication_key(name_from_yml)
72
+ rep_name = clean_bd_name(name_from_yml)
73
+ rep_name.gsub!(/(\_)+(\d+)/,'')
74
+ rep_name.to_sym
75
+ end
64
76
 
65
77
  def add_replication_connection(name_from_yml,new_connection)
66
- rep_name = name_from_yml.split("_")[0]
67
- db_name = database_name_from_yml(name_from_yml)
68
- if db_name.gsub!(/(^db\/)|(\.sqlite3$)/,'')
69
- db_name.gsub!(Regexp.new("(\\_#{env}$)"),'')
70
- end
71
- rep_name << "_#{db_name}"
72
- rep_name = rep_name.to_sym
73
- replication_connections[rep_name] ||= []
74
- replication_connections[rep_name] << new_connection
78
+ key = replication_key(name_from_yml)
79
+ replication_connections[key] ||= []
80
+ replication_connections[key] << new_connection
75
81
  replication_connections
76
82
  end
77
83
 
78
-
79
84
  def connections_for_replication(rep_collection_key)
80
- replication_connections[rep_collection_key.to_sym]
85
+ replication_connections[(rep_collection_key.gsub(Regexp.new("(\_#{env}$)"),'')).to_sym]
81
86
  end
82
87
 
83
88
  # Sets class instance attributes, then builds connection classes, while populating
@@ -87,11 +92,15 @@ module ConnectionManager
87
92
  send("#{k.to_s}=",v)
88
93
  end
89
94
  connection_keys.each do |connection|
90
- new_connection = connection_class_name(connection)
95
+ #puts ActiveRecord::Base.configurations["test"]
96
+ new_connection = connection_class_name(connection)
97
+ #puts ActiveRecord::Base.configurations["test"]
91
98
  add_replication_connection(connection,new_connection)
92
- build_connection_class(new_connection,connection)
99
+ #puts ActiveRecord::Base.configurations["test"]
100
+ build_connection_class(new_connection,connection)
101
+ #puts ActiveRecord::Base.configurations["test"]
93
102
  end
94
- all
103
+ all
95
104
  end
96
105
 
97
106
  # Addes a conneciton subclass to AvailableConnections using the supplied
@@ -1,11 +1,8 @@
1
1
  module ConnectionManager
2
2
  module ReplicationBuilder
3
3
 
4
- def database_name(db_name=nil)
5
- db_name = "#{connection.instance_variable_get(:@config)[:database].to_s}" if db_name.blank?
6
- db_name.gsub!(/(\.sqlite3$)/,'')
7
- db_name = db_name.split("/").last
8
- db_name
4
+ def database_name
5
+ "#{connection.instance_variable_get(:@config)[:database].to_s}"
9
6
  end
10
7
 
11
8
  def replication_association_options(method,association,class_name,options={})
@@ -39,12 +36,14 @@ module ConnectionManager
39
36
 
40
37
  def replication_connection_classes(options)
41
38
  if options[:using] && options[:using].is_a?(Array)
42
- connection_classes = options[:using].collection{|c| Connections.connection_class_name(c)}
39
+ connection_classes = options[:using].collect{|c| Connections.connection_class_name(c)}
43
40
  else
44
- connection_classes = Connections.connections_for_replication("#{options[:name].to_s}_#{database_name}")
41
+ rep_name = "#{options[:name].to_s}_#{Connections.clean_sqlite_db_name(database_name)}"
42
+ connection_classes = Connections.connections_for_replication(rep_name)
45
43
  end
46
44
  connection_classes
47
45
  end
46
+
48
47
 
49
48
  # Adds subclass with the class name of the type provided in the options, which
50
49
  # defaults to 'slave' if blank, that uses the connection from a connection class.
@@ -80,8 +79,8 @@ module ConnectionManager
80
79
  # overrides the rails "readonly?" method to ensure saves are prevented.
81
80
  # Replication class can be called directly for operaitons.
82
81
  # Usage:
83
- # => User::Slave1.where(:id => 1).first => returns results from slave_1 database
84
- # => User::Slave2.where(:id => 2).first => returns results from slave_1 database
82
+ # User::Slave1.where(:id => 1).first => returns results from slave_1 database
83
+ # User::Slave2.where(:id => 2).first => returns results from slave_1 database
85
84
  def build_replication_class(class_name,connection_name,options)
86
85
  class_eval <<-STR, __FILE__, __LINE__
87
86
  class #{class_name} < #{self.name}
@@ -99,8 +98,8 @@ module ConnectionManager
99
98
 
100
99
  # Adds as class method to call a specific replication conneciton.
101
100
  # Usage:
102
- # => User.slave_1.where(:id => 2).first => returns results from slave_1 database
103
- # => User.slave_2.where(:id => 2).first => returns results from slave_1 database
101
+ # User.slave_1.where(:id => 2).first => returns results from slave_1 database
102
+ # User.slave_2.where(:id => 2).first => returns results from slave_2 database
104
103
  def build_single_replication_method(method_name,class_name)
105
104
  class_eval <<-STR, __FILE__, __LINE__
106
105
  class << self
@@ -114,7 +113,7 @@ module ConnectionManager
114
113
  # add a class method that shifts through available connections methods
115
114
  # on each call.
116
115
  # Usage:
117
- # => User.slave.where(:id => 2).first => can return results from slave_1 or slave_2
116
+ # User.slave.where(:id => 2).first => can return results from slave_1 or slave_2
118
117
  def build_full_replication_method(options,connection_methods)
119
118
  class_eval <<-STR, __FILE__, __LINE__
120
119
  @connection_methods = #{connection_methods}
@@ -1,4 +1,4 @@
1
1
  module ConnectionManager
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
4
4
 
data/spec/database.yml ADDED
@@ -0,0 +1,20 @@
1
+ # Warning: The database defined as "test" will be erased and
2
+ # re-generated from your development database when you run "rake".
3
+ # Do not set this db to the same as development or production.
4
+ test:
5
+ adapter: sqlite3
6
+ database: spec/cm_test.sqlite3
7
+ pool: 5
8
+ timeout: 5000
9
+
10
+ slave_1_cm_test:
11
+ adapter: sqlite3
12
+ database: spec/cm_test.sqlite3
13
+ pool: 5
14
+ timeout: 5000
15
+
16
+ slave_2_cm_test:
17
+ adapter: sqlite3
18
+ database: spec/cm_test.sqlite3
19
+ pool: 5
20
+ timeout: 5000
data/spec/factories.rb ADDED
@@ -0,0 +1,31 @@
1
+ FactoryGirl.define do
2
+ factory :basket do
3
+ name "MyString"
4
+ end
5
+ end
6
+
7
+ FactoryGirl.define do
8
+ factory :fruit_basket do
9
+ fruit
10
+ basket
11
+ end
12
+ end
13
+
14
+ FactoryGirl.define do
15
+ factory :fruit do
16
+ name "MyString"
17
+ region
18
+ end
19
+ end
20
+
21
+ FactoryGirl.define do
22
+ factory :region do
23
+ name "MyString"
24
+ end
25
+ end
26
+
27
+ FactoryGirl.define do
28
+ factory :type do
29
+ name "MyString"
30
+ end
31
+ end
@@ -0,0 +1,68 @@
1
+ class TestDB
2
+ def self.connect
3
+ ActiveRecord::Base.configurations = YAML::load(File.open(File.join(File.dirname(__FILE__),'..','database.yml')))
4
+ ActiveRecord::Base.establish_connection('test')
5
+ end
6
+ def self.clean
7
+ [:foos,:fruits,:baskets,:fruit_baskets,:regions,:types].each do |t|
8
+ DBSpecManagement.connection.execute("DELETE FROM #{t.to_s}")
9
+ end
10
+ end
11
+ #Class to clean tables
12
+ class DBSpecManagement < ActiveRecord::Base
13
+ end
14
+ end
15
+
16
+
17
+ #Put all the test migrations here
18
+ class TestMigrations < ActiveRecord::Migration
19
+
20
+ # all the ups
21
+ def self.up
22
+ begin
23
+ create_table :foos do |t|
24
+ t.string :name
25
+ end
26
+ create_table :fruits do |t|
27
+ t.string :name
28
+ t.integer :region_id
29
+ t.timestamps
30
+ end
31
+ create_table :baskets do |t|
32
+ t.string :name
33
+ t.timestamps
34
+ end
35
+ create_table :fruit_baskets do |t|
36
+ t.integer :fruit_id
37
+ t.integer :basket_id
38
+ t.timestamps
39
+ end
40
+ create_table :regions do |t|
41
+ t.string :name
42
+
43
+ t.timestamps
44
+ end
45
+ create_table :types do |t|
46
+ t.string :name
47
+ t.timestamps
48
+ end
49
+ rescue => e
50
+ puts "tables failed to create: #{e}"
51
+ end
52
+
53
+ end
54
+
55
+
56
+ # all the downs
57
+ def self.down
58
+ begin
59
+ [:foos,:fruits,:baskets,:fruit_baskets,:regions,:types].each do |t|
60
+ drop_table t
61
+ end
62
+ rescue => e
63
+ puts "tables were not dropped: #{e}"
64
+ end
65
+ end
66
+
67
+
68
+ end