data_fabric 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/CHANGELOG +4 -0
  2. data/Manifest +73 -0
  3. data/README.rdoc +1 -1
  4. data/Rakefile +34 -8
  5. data/data_fabric.gemspec +7 -7
  6. data/example/app/models/figment.rb +1 -1
  7. data/example/db/development.sqlite3 +0 -0
  8. data/example/db/s0_development.sqlite3 +0 -0
  9. data/example/db/s0_test.sqlite3 +0 -0
  10. data/example/db/s1_development.sqlite3 +0 -0
  11. data/example/db/s1_test.sqlite3 +0 -0
  12. data/example/db/test.sqlite3 +0 -0
  13. data/example/vendor/plugins/data_fabric/init.rb +1 -0
  14. data/example/vendor/plugins/data_fabric/lib/data_fabric.rb +106 -0
  15. data/example/vendor/plugins/data_fabric/lib/data_fabric/ar20.rb +135 -0
  16. data/example/vendor/plugins/data_fabric/lib/data_fabric/ar22.rb +172 -0
  17. data/example/vendor/plugins/data_fabric/lib/data_fabric/version.rb +5 -0
  18. data/example22/Rakefile +58 -0
  19. data/example22/app/controllers/accounts_controller.rb +22 -0
  20. data/example22/app/controllers/application.rb +39 -0
  21. data/example22/app/controllers/figments_controller.rb +8 -0
  22. data/example22/app/helpers/application_helper.rb +3 -0
  23. data/example22/app/models/account.rb +3 -0
  24. data/example22/app/models/figment.rb +4 -0
  25. data/example22/app/views/accounts/index.html.erb +47 -0
  26. data/example22/app/views/layouts/application.html.erb +8 -0
  27. data/example22/config/boot.rb +109 -0
  28. data/example22/config/database.yml +21 -0
  29. data/example22/config/environment.rb +76 -0
  30. data/example22/config/environments/development.rb +17 -0
  31. data/example22/config/environments/production.rb +24 -0
  32. data/example22/config/environments/test.rb +22 -0
  33. data/example22/config/initializers/inflections.rb +10 -0
  34. data/example22/config/initializers/mime_types.rb +5 -0
  35. data/example22/config/initializers/new_rails_defaults.rb +17 -0
  36. data/example22/config/locales/en.yml +5 -0
  37. data/example22/config/routes.rb +46 -0
  38. data/example22/db/migrate/20080702154628_create_accounts.rb +14 -0
  39. data/example22/db/migrate/20080702154820_create_figments.rb +14 -0
  40. data/example22/public/404.html +30 -0
  41. data/example22/public/422.html +30 -0
  42. data/example22/public/500.html +33 -0
  43. data/example22/public/dispatch.cgi +10 -0
  44. data/example22/public/dispatch.fcgi +24 -0
  45. data/example22/public/dispatch.rb +10 -0
  46. data/example22/public/favicon.ico +0 -0
  47. data/example22/public/images/rails.png +0 -0
  48. data/example22/public/index.html +274 -0
  49. data/example22/public/javascripts/application.js +2 -0
  50. data/example22/public/javascripts/controls.js +963 -0
  51. data/example22/public/javascripts/dragdrop.js +973 -0
  52. data/example22/public/javascripts/effects.js +1128 -0
  53. data/example22/public/javascripts/prototype.js +4320 -0
  54. data/example22/public/robots.txt +5 -0
  55. data/example22/script/about +4 -0
  56. data/example22/script/console +3 -0
  57. data/example22/script/dbconsole +3 -0
  58. data/example22/script/destroy +3 -0
  59. data/example22/script/generate +3 -0
  60. data/example22/script/performance/benchmarker +3 -0
  61. data/example22/script/performance/profiler +3 -0
  62. data/example22/script/performance/request +3 -0
  63. data/example22/script/plugin +3 -0
  64. data/example22/script/process/inspector +3 -0
  65. data/example22/script/process/reaper +3 -0
  66. data/example22/script/process/spawner +3 -0
  67. data/example22/script/runner +3 -0
  68. data/example22/script/server +3 -0
  69. data/example22/test/fixtures/accounts.yml +7 -0
  70. data/example22/test/functional/accounts_controller_test.rb +12 -0
  71. data/example22/test/integration/account_figments_test.rb +97 -0
  72. data/example22/test/performance/browsing_test.rb +9 -0
  73. data/example22/test/test_helper.rb +38 -0
  74. data/lib/data_fabric.rb +7 -132
  75. data/lib/data_fabric/ar20.rb +133 -0
  76. data/lib/data_fabric/ar22.rb +172 -0
  77. data/lib/data_fabric/version.rb +1 -1
  78. data/test/connection_test.rb +6 -2
  79. data/test/database_test.rb +26 -2
  80. data/test/test_helper.rb +34 -28
  81. data/test/thread_test.rb +19 -11
  82. data/test/vr_austin_master.db +0 -0
  83. data/test/vr_austin_slave.db +0 -0
  84. data/test/vr_dallas_master.db +0 -0
  85. data/test/vr_dallas_slave.db +0 -0
  86. metadata +79 -5
@@ -0,0 +1,172 @@
1
+ module DataFabric
2
+ module Extensions
3
+ def self.included(model)
4
+ # Wire up ActiveRecord::Base
5
+ model.extend ClassMethods
6
+ ConnectionProxy.shard_pools = {}
7
+ end
8
+
9
+ # Class methods injected into ActiveRecord::Base
10
+ module ClassMethods
11
+ def data_fabric(options)
12
+ DataFabric.log { "Creating data_fabric proxy for class #{name}" }
13
+ @proxy = DataFabric::ConnectionProxy.new(self, options)
14
+
15
+ class << self
16
+ def connection
17
+ @proxy
18
+ end
19
+
20
+ def connected?
21
+ @proxy.connected?
22
+ end
23
+
24
+ def remove_connection(klass)
25
+ DataFabric.log(Logger::ERROR) { "remove_connection not implemented by data_fabric" }
26
+ end
27
+
28
+ def connection_pool
29
+ raise "dynamic connection switching means you cannot get direct access to a pool"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class ConnectionProxy
37
+ cattr_accessor :shard_pools
38
+
39
+ def initialize(model_class, options)
40
+ @model_class = model_class
41
+ @replicated = options[:replicated]
42
+ @shard_group = options[:shard_by]
43
+ @prefix = options[:prefix]
44
+ set_role('slave') if @replicated
45
+
46
+ @model_class.send :include, ActiveRecordConnectionMethods if @replicated
47
+ end
48
+
49
+ delegate :insert, :update, :delete, :create_table, :rename_table, :drop_table, :add_column, :remove_column,
50
+ :change_column, :change_column_default, :rename_column, :add_index, :remove_index, :initialize_schema_information,
51
+ :dump_schema_information, :execute, :execute_ignore_duplicate, :to => :master
52
+
53
+ delegate :insert_many, :to => :master # ar-extensions bulk insert support
54
+
55
+ def transaction(start_db_transaction = true, &block)
56
+ # Transaction is not re-entrant in SQLite 3 so we
57
+ # need to track if we've already started an XA to avoid
58
+ # calling it twice.
59
+ return yield if in_transaction?
60
+
61
+ with_master do
62
+ connection.transaction(start_db_transaction, &block)
63
+ end
64
+ end
65
+
66
+ def method_missing(method, *args, &block)
67
+ DataFabric.log(Logger::DEBUG) { "Calling #{method} on #{connection}" }
68
+ connection.send(method, *args, &block)
69
+ end
70
+
71
+ def connection_name
72
+ connection_name_builder.join('_')
73
+ end
74
+
75
+ def with_master
76
+ # Allow nesting of with_master.
77
+ old_role = current_role
78
+ set_role('master')
79
+ yield
80
+ ensure
81
+ set_role(old_role)
82
+ end
83
+
84
+ private
85
+
86
+ def in_transaction?
87
+ current_role == 'master'
88
+ end
89
+
90
+ def current_pool
91
+ name = connection_name
92
+ self.class.shard_pools[name] ||= begin
93
+ config = ActiveRecord::Base.configurations[name]
94
+ raise ArgumentError, "Unknown database config: #{name}, have #{ActiveRecord::Base.configurations.inspect}" unless config
95
+ ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec_for(config))
96
+ end
97
+ end
98
+
99
+ def spec_for(config)
100
+ # XXX This looks pretty fragile. Will break if AR changes how it initializes connections and adapters.
101
+ config = config.symbolize_keys
102
+ adapter_method = "#{config[:adapter]}_connection"
103
+ initialize_adapter(config[:adapter])
104
+ ActiveRecord::Base::ConnectionSpecification.new(config, adapter_method)
105
+ end
106
+
107
+ def initialize_adapter(adapter)
108
+ begin
109
+ require 'rubygems'
110
+ gem "activerecord-#{adapter}-adapter"
111
+ require "active_record/connection_adapters/#{adapter}_adapter"
112
+ rescue LoadError
113
+ begin
114
+ require "active_record/connection_adapters/#{adapter}_adapter"
115
+ rescue LoadError
116
+ raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$!})"
117
+ end
118
+ end
119
+ end
120
+
121
+ def connection_name_builder
122
+ @connection_name_builder ||= begin
123
+ clauses = []
124
+ clauses << @prefix if @prefix
125
+ clauses << @shard_group if @shard_group
126
+ clauses << StringProxy.new { DataFabric.active_shard(@shard_group) } if @shard_group
127
+ clauses << RAILS_ENV
128
+ clauses << StringProxy.new { current_role } if @replicated
129
+ clauses
130
+ end
131
+ end
132
+
133
+ def connection
134
+ current_pool.connection
135
+ end
136
+
137
+ def active?
138
+ DataFabric.shard_active_for?(@shard_group)
139
+ end
140
+
141
+ def set_role(role)
142
+ Thread.current[:data_fabric_role] = role
143
+ end
144
+
145
+ def current_role
146
+ Thread.current[:data_fabric_role]
147
+ end
148
+
149
+ def master
150
+ with_master { return connection }
151
+ end
152
+ end
153
+
154
+ module ActiveRecordConnectionMethods
155
+ def self.included(base)
156
+ base.alias_method_chain :reload, :master
157
+ end
158
+
159
+ def reload_with_master(*args, &block)
160
+ connection.with_master { reload_without_master }
161
+ end
162
+ end
163
+
164
+ class StringProxy
165
+ def initialize(&block)
166
+ @proc = block
167
+ end
168
+ def to_s
169
+ @proc.call
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,5 @@
1
+ module DataFabric
2
+ module Version
3
+ STRING = "1.2.0"
4
+ end
5
+ end
@@ -0,0 +1,58 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require(File.join(File.dirname(__FILE__), 'config', 'boot'))
5
+
6
+ require 'rake'
7
+ require 'rake/testtask'
8
+ require 'rake/rdoctask'
9
+
10
+ require 'tasks/rails'
11
+
12
+ require 'fileutils'
13
+ include FileUtils::Verbose
14
+
15
+ namespace :db do
16
+ task :migrate do
17
+ require 'erb'
18
+ require 'logger'
19
+ require 'active_record'
20
+ reference = YAML::load(ERB.new(IO.read("config/database.yml")).result)
21
+ env = RAILS_ENV = ENV['RAILS_ENV'] || 'development'
22
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
23
+ ActiveRecord::Base.logger.level = Logger::WARN
24
+ ActiveRecord::Base.configurations = reference.dup
25
+ old_config = reference[env]
26
+ reference.each_key do |name|
27
+ next unless name.include? env
28
+ next if name.include? 'slave' # Replicated databases should not be touched directly
29
+
30
+ puts "Migrating #{name}"
31
+ ActiveRecord::Base.clear_active_connections!
32
+ ActiveRecord::Base.configurations[env] = reference[name]
33
+ ActiveRecord::Base.establish_connection RAILS_ENV
34
+ ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
35
+ ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
36
+ end
37
+ end
38
+ end
39
+
40
+ namespace :app do
41
+ task :prepare => [:clean, :copy_plugin, :migrate]
42
+
43
+ task :copy_plugin do
44
+ mkdir_p 'vendor/plugins/data_fabric'
45
+ cp_r '../lib', 'vendor/plugins/data_fabric'
46
+ cp '../init.rb', 'vendor/plugins/data_fabric'
47
+ end
48
+
49
+ task :clean do
50
+ rm_rf 'vendor/plugins/data_fabric'
51
+ rm_f 'db/*.sqlite3'
52
+ end
53
+
54
+ task :migrate do
55
+ sh "rake db:migrate"
56
+ sh "rake RAILS_ENV=test db:migrate"
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ class AccountsController < ApplicationController
2
+ layout 'application'
3
+
4
+ def index
5
+ @accounts = Account.find(:all)
6
+ end
7
+
8
+ def choose
9
+ @account = Account.find(params[:id])
10
+ if @account
11
+ session[:account_id] = @account.id
12
+ flash[:notice] = "Selected account: #{@account.name}"
13
+ end
14
+ redirect_to '/'
15
+ end
16
+
17
+ def create
18
+ Account.create!(params[:acct])
19
+ flash[:notice] = "Account created successfully"
20
+ redirect_to '/'
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ # Filters added to this controller apply to all controllers in the application.
2
+ # Likewise, all the methods added will be available for all controllers.
3
+
4
+ class ApplicationController < ActionController::Base
5
+ helper :all # include all helpers, all the time
6
+
7
+ # See ActionController::RequestForgeryProtection for details
8
+ # Uncomment the :secret if you're not using the cookie session store
9
+ protect_from_forgery # :secret => '44f8cf8a0491c23ae99c031a900123cc'
10
+
11
+ # See ActionController::Base for details
12
+ # Uncomment this to filter the contents of submitted sensitive data parameters
13
+ # from your application log (in this case, all fields with names like "password").
14
+ # filter_parameter_logging :password
15
+
16
+ before_filter :find_account
17
+ around_filter :select_shard
18
+
19
+ private
20
+ def find_account
21
+ aid = session[:account_id]
22
+ if aid && aid.to_i != 0
23
+ begin
24
+ @account = Account.find(Integer(aid))
25
+ rescue ActiveRecord::RecordNotFound => e
26
+ RAILS_DEFAULT_LOGGER.warn "No such account #{aid}, skipping..."
27
+ end
28
+ end
29
+ session[:account_id] = nil unless @account
30
+ end
31
+
32
+ def select_shard(&block)
33
+ if @account
34
+ DataFabric.activate_shard(:shard => @account.shard, &block)
35
+ else
36
+ yield
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ class FigmentsController < ApplicationController
2
+
3
+ def create
4
+ @account.figments.create!(params[:figment])
5
+ flash[:notice] = "Figment created"
6
+ redirect_to '/'
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ # Methods added to this helper will be available to all templates in the application.
2
+ module ApplicationHelper
3
+ end
@@ -0,0 +1,3 @@
1
+ class Account < ActiveRecord::Base
2
+ has_many :figments
3
+ end
@@ -0,0 +1,4 @@
1
+ class Figment < ActiveRecord::Base
2
+ data_fabric :shard_by => 'shard', :replicated => false
3
+ belongs_to :account
4
+ end
@@ -0,0 +1,47 @@
1
+ <hr/>
2
+ <h2>Accounts</h2>
3
+ <table>
4
+ <thead>
5
+ <tr><th>ID</th><th>Name</th><th>Shard</th><th>&nbsp;</th></tr>
6
+ </thead>
7
+ <tbody>
8
+ <% @accounts.each do |account| %>
9
+ <tr>
10
+ <td><%= account.id %></td>
11
+ <td><%= account.name %></td>
12
+ <td><%= account.shard %></td>
13
+ <td><%= link_to 'Choose', choose_account_path(account) %></td>
14
+ </tr>
15
+ <% end %>
16
+ </tbody>
17
+ </table>
18
+ <hr/>
19
+ <h2>New Account</h2>
20
+ <p>
21
+ <% form_for :acct, :url => accounts_path do |f| %>
22
+ Name : <%= f.text_field :name %><br/>
23
+ Shard: <%= select 'acct', 'shard', [[0,0],[1,1]] %><br/>
24
+ <%= submit_tag 'Save' %>
25
+ <% end %>
26
+ </p>
27
+ <hr/>
28
+ <% if @account %>
29
+ <h2>Current Account: <%= @account.name %></h2>
30
+ <table>
31
+ <thead>
32
+ <tr><th>ID</th><th>Account</th><th>Value</th></tr>
33
+ </thead>
34
+ <tbody>
35
+ <% @account.figments.each do |fig| %>
36
+ <tr>
37
+ <td><%= fig.id %></td>
38
+ <td><%= fig.account.name %></td>
39
+ <td><%= fig.value %></td>
40
+ </tr>
41
+ <% end %>
42
+ </tbody>
43
+ </table>
44
+ Add Fig: <% form_for :figment, :url => figments_path do |f| %><%= f.text_field :value %><%= submit_tag 'Save' %><% end %>
45
+ <% else %>
46
+ <em>No account selected.</em>
47
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <body>
3
+ <div id='flash' style="background-color: #DDFFDD;">
4
+ <%= flash[:notice] %>
5
+ </div>
6
+ <%= yield %>
7
+ </body>
8
+ </html>
@@ -0,0 +1,109 @@
1
+ # Don't change this file!
2
+ # Configure your app in config/environment.rb and config/environments/*.rb
3
+
4
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
5
+
6
+ module Rails
7
+ class << self
8
+ def boot!
9
+ unless booted?
10
+ preinitialize
11
+ pick_boot.run
12
+ end
13
+ end
14
+
15
+ def booted?
16
+ defined? Rails::Initializer
17
+ end
18
+
19
+ def pick_boot
20
+ (vendor_rails? ? VendorBoot : GemBoot).new
21
+ end
22
+
23
+ def vendor_rails?
24
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
25
+ end
26
+
27
+ def preinitialize
28
+ load(preinitializer_path) if File.exist?(preinitializer_path)
29
+ end
30
+
31
+ def preinitializer_path
32
+ "#{RAILS_ROOT}/config/preinitializer.rb"
33
+ end
34
+ end
35
+
36
+ class Boot
37
+ def run
38
+ load_initializer
39
+ Rails::Initializer.run(:set_load_path)
40
+ end
41
+ end
42
+
43
+ class VendorBoot < Boot
44
+ def load_initializer
45
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
46
+ Rails::Initializer.run(:install_gem_spec_stubs)
47
+ end
48
+ end
49
+
50
+ class GemBoot < Boot
51
+ def load_initializer
52
+ self.class.load_rubygems
53
+ load_rails_gem
54
+ require 'initializer'
55
+ end
56
+
57
+ def load_rails_gem
58
+ if version = self.class.gem_version
59
+ gem 'rails', version
60
+ else
61
+ gem 'rails'
62
+ end
63
+ rescue Gem::LoadError => load_error
64
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
65
+ exit 1
66
+ end
67
+
68
+ class << self
69
+ def rubygems_version
70
+ Gem::RubyGemsVersion rescue nil
71
+ end
72
+
73
+ def gem_version
74
+ if defined? RAILS_GEM_VERSION
75
+ RAILS_GEM_VERSION
76
+ elsif ENV.include?('RAILS_GEM_VERSION')
77
+ ENV['RAILS_GEM_VERSION']
78
+ else
79
+ parse_gem_version(read_environment_rb)
80
+ end
81
+ end
82
+
83
+ def load_rubygems
84
+ require 'rubygems'
85
+ min_version = '1.3.1'
86
+ unless rubygems_version >= min_version
87
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
88
+ exit 1
89
+ end
90
+
91
+ rescue LoadError
92
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
93
+ exit 1
94
+ end
95
+
96
+ def parse_gem_version(text)
97
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
98
+ end
99
+
100
+ private
101
+ def read_environment_rb
102
+ File.read("#{RAILS_ROOT}/config/environment.rb")
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # All that for this:
109
+ Rails.boot!