connection_manager 0.0.1 → 0.1.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.
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