data_fabric 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/CHANGELOG +1 -0
- data/Manifest +79 -0
- data/README.rdoc +108 -0
- data/Rakefile +69 -0
- data/TESTING.rdoc +34 -0
- data/TODO +1 -0
- data/data_fabric.gemspec +166 -0
- data/example/Rakefile +58 -0
- data/example/app/controllers/accounts_controller.rb +22 -0
- data/example/app/controllers/application.rb +39 -0
- data/example/app/controllers/figments_controller.rb +8 -0
- data/example/app/helpers/accounts_helper.rb +2 -0
- data/example/app/helpers/application_helper.rb +3 -0
- data/example/app/helpers/figments_helper.rb +2 -0
- data/example/app/models/account.rb +3 -0
- data/example/app/models/figment.rb +4 -0
- data/example/app/views/accounts/index.html.erb +47 -0
- data/example/app/views/layouts/application.html.erb +8 -0
- data/example/config/boot.rb +109 -0
- data/example/config/database.yml +21 -0
- data/example/config/environment.rb +67 -0
- data/example/config/environments/development.rb +17 -0
- data/example/config/environments/production.rb +22 -0
- data/example/config/environments/test.rb +22 -0
- data/example/config/initializers/inflections.rb +10 -0
- data/example/config/initializers/mime_types.rb +5 -0
- data/example/config/initializers/new_rails_defaults.rb +15 -0
- data/example/config/routes.rb +45 -0
- data/example/db/development.sqlite3 +0 -0
- data/example/db/migrate/20080702154628_create_accounts.rb +14 -0
- data/example/db/migrate/20080702154820_create_figments.rb +14 -0
- data/example/db/s0_development.sqlite3 +0 -0
- data/example/db/s0_test.sqlite3 +0 -0
- data/example/db/s1_development.sqlite3 +0 -0
- data/example/db/s1_test.sqlite3 +0 -0
- data/example/db/schema.rb +28 -0
- data/example/db/test.sqlite3 +0 -0
- data/example/public/404.html +30 -0
- data/example/public/422.html +30 -0
- data/example/public/500.html +30 -0
- data/example/public/dispatch.cgi +10 -0
- data/example/public/dispatch.fcgi +24 -0
- data/example/public/dispatch.rb +10 -0
- data/example/public/favicon.ico +0 -0
- data/example/public/images/rails.png +0 -0
- data/example/public/javascripts/application.js +2 -0
- data/example/public/javascripts/controls.js +963 -0
- data/example/public/javascripts/dragdrop.js +972 -0
- data/example/public/javascripts/effects.js +1120 -0
- data/example/public/javascripts/prototype.js +4225 -0
- data/example/public/robots.txt +5 -0
- data/example/script/about +3 -0
- data/example/script/console +3 -0
- data/example/script/dbconsole +3 -0
- data/example/script/destroy +3 -0
- data/example/script/generate +3 -0
- data/example/script/performance/benchmarker +3 -0
- data/example/script/performance/profiler +3 -0
- data/example/script/performance/request +3 -0
- data/example/script/plugin +3 -0
- data/example/script/process/inspector +3 -0
- data/example/script/process/reaper +3 -0
- data/example/script/process/spawner +3 -0
- data/example/script/runner +3 -0
- data/example/script/server +3 -0
- data/example/test/fixtures/accounts.yml +7 -0
- data/example/test/functional/accounts_controller_test.rb +12 -0
- data/example/test/integration/account_figments_test.rb +95 -0
- data/example/test/test_helper.rb +41 -0
- data/example/vendor/plugins/data_fabric/init.rb +1 -0
- data/example/vendor/plugins/data_fabric/lib/data_fabric.rb +231 -0
- data/init.rb +1 -0
- data/lib/data_fabric/version.rb +5 -0
- data/lib/data_fabric.rb +231 -0
- data/test/connection_test.rb +103 -0
- data/test/database.yml +27 -0
- data/test/database_test.rb +39 -0
- data/test/shard_test.rb +24 -0
- data/test/test_helper.rb +17 -0
- data/test/thread_test.rb +91 -0
- metadata +164 -0
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
DataFabric.init
|
data/lib/data_fabric.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/version'
|
3
|
+
|
4
|
+
# DataFabric adds a new level of flexibility to ActiveRecord connection handling.
|
5
|
+
# You need to describe the topology for your database infrastructure in your model(s). As with ActiveRecord normally, different models can use different topologies.
|
6
|
+
#
|
7
|
+
# class MyHugeVolumeOfDataModel < ActiveRecord::Base
|
8
|
+
# connection_topology :replicated => true, :shard_by => :city
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# There are four supported modes of operation, depending on the options given to the connection_topology method. The plugin will look for connections in your config/database.yml with the following convention:
|
12
|
+
#
|
13
|
+
# No connection topology:
|
14
|
+
# #{environment} - this is the default, as with ActiveRecord, e.g. "production"
|
15
|
+
#
|
16
|
+
# connection_topology :replicated => true
|
17
|
+
# #{environment}_#{role} - no sharding, just replication, where role is "master" or "slave", e.g. "production_master"
|
18
|
+
#
|
19
|
+
# connection_topology :shard_by => :city
|
20
|
+
# #{group}_#{shard}_#{environment} - sharding, no replication, e.g. "city_austin_production"
|
21
|
+
#
|
22
|
+
# connection_topology :replicated => true, :shard_by => :city
|
23
|
+
# #{group}_#{shard}_#{environment}_#{role} - sharding with replication, e.g. "city_austin_production_master"
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# When marked as replicated, all write and transactional operations for the model go to the master, whereas read operations go to the slave.
|
27
|
+
#
|
28
|
+
# Since sharding is an application-level concern, your application must set the shard to use based on the current request or environment. The current shard for a group is set on a thread local variable. For example, you can set the shard in an ActionController around_filter based on the user as follows:
|
29
|
+
#
|
30
|
+
# class ApplicationController < ActionController::Base
|
31
|
+
# around_filter :select_shard
|
32
|
+
#
|
33
|
+
# private
|
34
|
+
# def select_shard(&action_block)
|
35
|
+
# DataFabric.activate_shard(:city => @current_user.city, &action_block)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
module DataFabric
|
39
|
+
VERSION = "1.0.0"
|
40
|
+
|
41
|
+
def self.logger
|
42
|
+
ActiveRecord::Base.logger
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.init
|
46
|
+
logger.info "Loading data_fabric #{VERSION} with ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
47
|
+
ActiveRecord::Base.send(:include, self)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.activate_shard(shards, &block)
|
51
|
+
ensure_setup
|
52
|
+
shards.each do |key, value|
|
53
|
+
Thread.current[:shards][key.to_s] = value.to_s
|
54
|
+
end
|
55
|
+
if block_given?
|
56
|
+
begin
|
57
|
+
yield
|
58
|
+
ensure
|
59
|
+
shards.each do |key, value|
|
60
|
+
Thread.current[:shards].delete(key.to_s)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# For cases where you can't pass a block to activate_shards, you can
|
67
|
+
# clean up the thread local settings by calling this method at the
|
68
|
+
# end of processing
|
69
|
+
def self.deactivate_shard(shards)
|
70
|
+
ensure_setup
|
71
|
+
shards.each do |key, value|
|
72
|
+
Thread.current[:shards].delete(key.to_s)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.active_shard(group)
|
77
|
+
raise ArgumentError, 'No shard has been activated' unless Thread.current[:shards]
|
78
|
+
|
79
|
+
returning(Thread.current[:shards][group.to_s]) do |shard|
|
80
|
+
raise ArgumentError, "No active shard for #{group}" unless shard
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.included(model)
|
85
|
+
# Wire up ActiveRecord::Base
|
86
|
+
model.extend ClassMethods
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.ensure_setup
|
90
|
+
Thread.current[:shards] = {} unless Thread.current[:shards]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Class methods injected into ActiveRecord::Base
|
94
|
+
module ClassMethods
|
95
|
+
def connection_topology(options)
|
96
|
+
proxy = DataFabric::ConnectionProxy.new(self, options)
|
97
|
+
ActiveRecord::Base.active_connections[name] = proxy
|
98
|
+
|
99
|
+
raise ArgumentError, "data_fabric does not support ActiveRecord's allow_concurrency = true" if allow_concurrency
|
100
|
+
DataFabric.logger.info "Creating data_fabric proxy for class #{name}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class StringProxy
|
105
|
+
def initialize(&block)
|
106
|
+
@proc = block
|
107
|
+
end
|
108
|
+
def to_s
|
109
|
+
@proc.call
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class ConnectionProxy
|
114
|
+
def initialize(model_class, options)
|
115
|
+
@model_class = model_class
|
116
|
+
@replicated = options[:replicated]
|
117
|
+
@shard_group = options[:shard_by]
|
118
|
+
@prefix = options[:prefix]
|
119
|
+
@current_role = 'slave' if @replicated
|
120
|
+
@current_connection_name_builder = connection_name_builder
|
121
|
+
@cached_connection = nil
|
122
|
+
@current_connection_name = nil
|
123
|
+
@role_changed = false
|
124
|
+
|
125
|
+
@model_class.send :include, ActiveRecordConnectionMethods if @replicated
|
126
|
+
end
|
127
|
+
|
128
|
+
delegate :insert, :update, :delete, :create_table, :rename_table, :drop_table, :add_column, :remove_column,
|
129
|
+
:change_column, :change_column_default, :rename_column, :add_index, :remove_index, :initialize_schema_information,
|
130
|
+
:dump_schema_information, :to => :master
|
131
|
+
|
132
|
+
def transaction(start_db_transaction = true, &block)
|
133
|
+
with_master { raw_connection.transaction(start_db_transaction, &block) }
|
134
|
+
end
|
135
|
+
|
136
|
+
def method_missing(method, *args, &block)
|
137
|
+
unless @cached_connection and !@role_changed
|
138
|
+
raw_connection
|
139
|
+
@role_changed = false
|
140
|
+
end
|
141
|
+
if logger.debug?
|
142
|
+
logger.debug("Calling #{method} on #{@cached_connection}")
|
143
|
+
end
|
144
|
+
@cached_connection.send(method, *args, &block)
|
145
|
+
end
|
146
|
+
|
147
|
+
def connection_name
|
148
|
+
@current_connection_name_builder.join('_')
|
149
|
+
end
|
150
|
+
|
151
|
+
def disconnect!
|
152
|
+
@cached_connection.disconnect! if @cached_connection
|
153
|
+
@cached_connection = nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def verify!(arg)
|
157
|
+
@cached_connection.verify!(0) if @cached_connection
|
158
|
+
end
|
159
|
+
|
160
|
+
def with_master
|
161
|
+
set_role('master')
|
162
|
+
yield
|
163
|
+
ensure
|
164
|
+
set_role('slave')
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def connection_name_builder
|
170
|
+
clauses = []
|
171
|
+
clauses << @prefix if @prefix
|
172
|
+
clauses << @shard_group if @shard_group
|
173
|
+
clauses << StringProxy.new { DataFabric.active_shard(@shard_group) } if @shard_group
|
174
|
+
clauses << RAILS_ENV
|
175
|
+
clauses << StringProxy.new { @current_role } if @replicated
|
176
|
+
clauses
|
177
|
+
end
|
178
|
+
|
179
|
+
def raw_connection
|
180
|
+
conn_name = connection_name
|
181
|
+
unless already_connected_to? conn_name
|
182
|
+
@cached_connection = begin
|
183
|
+
config = ActiveRecord::Base.configurations[conn_name]
|
184
|
+
raise ArgumentError, "Unknown database config: #{conn_name}, have #{ActiveRecord::Base.configurations.inspect}" unless config
|
185
|
+
@model_class.establish_connection config
|
186
|
+
if logger.debug?
|
187
|
+
logger.debug "Switching from #{@current_connection_name} to #{conn_name}"
|
188
|
+
end
|
189
|
+
@current_connection_name = conn_name
|
190
|
+
conn = @model_class.connection
|
191
|
+
conn.verify! 0
|
192
|
+
conn
|
193
|
+
end
|
194
|
+
@model_class.active_connections[@model_class.name] = self
|
195
|
+
end
|
196
|
+
@cached_connection
|
197
|
+
end
|
198
|
+
|
199
|
+
def already_connected_to?(conn_name)
|
200
|
+
conn_name == @current_connection_name and @cached_connection
|
201
|
+
end
|
202
|
+
|
203
|
+
def set_role(role)
|
204
|
+
if @replicated and @current_role != role
|
205
|
+
@current_role = role
|
206
|
+
@role_changed = true
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def master
|
211
|
+
set_role('master')
|
212
|
+
return raw_connection
|
213
|
+
ensure
|
214
|
+
set_role('slave')
|
215
|
+
end
|
216
|
+
|
217
|
+
def logger
|
218
|
+
DataFabric.logger
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
module ActiveRecordConnectionMethods
|
223
|
+
def self.included(base)
|
224
|
+
base.alias_method_chain :reload, :master
|
225
|
+
end
|
226
|
+
|
227
|
+
def reload_with_master(*args, &block)
|
228
|
+
connection.with_master { reload_without_master }
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'flexmock/test_unit'
|
3
|
+
|
4
|
+
class PrefixModel < ActiveRecord::Base
|
5
|
+
connection_topology :prefix => 'prefix'
|
6
|
+
end
|
7
|
+
|
8
|
+
class ShardModel < ActiveRecord::Base
|
9
|
+
connection_topology :shard_by => :city
|
10
|
+
end
|
11
|
+
|
12
|
+
class TheWholeEnchilada < ActiveRecord::Base
|
13
|
+
connection_topology :prefix => 'fiveruns', :replicated => true, :shard_by => :city
|
14
|
+
end
|
15
|
+
|
16
|
+
class AdapterMock < ActiveRecord::ConnectionAdapters::AbstractAdapter
|
17
|
+
# Minimum required to perform a find with no results
|
18
|
+
def columns(table_name, name=nil)
|
19
|
+
[]
|
20
|
+
end
|
21
|
+
def select(sql, name=nil)
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
def execute(sql, name=nil)
|
25
|
+
0
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
'fake-db'
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(name, *args)
|
33
|
+
raise ArgumentError, "#{self.class.name} missing '#{name}': #{args.inspect}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class RawConnection
|
38
|
+
def method_missing(name, *args)
|
39
|
+
puts "#{self.class.name} missing '#{name}': #{args.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class ConnectionTest < Test::Unit::TestCase
|
44
|
+
|
45
|
+
def test_should_install_into_arbase
|
46
|
+
assert PrefixModel.methods.include?('connection_topology')
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_prefix_connection_name
|
50
|
+
setup_configuration_for PrefixModel, 'prefix_test'
|
51
|
+
assert_equal 'prefix_test', PrefixModel.connection.connection_name
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_shard_connection_name
|
55
|
+
setup_configuration_for ShardModel, 'city_austin_test'
|
56
|
+
# ensure unset means error
|
57
|
+
assert_raises ArgumentError do
|
58
|
+
ShardModel.connection.connection_name
|
59
|
+
end
|
60
|
+
DataFabric.activate_shard(:city => 'austin', :category => 'art') do
|
61
|
+
assert_equal 'city_austin_test', ShardModel.connection.connection_name
|
62
|
+
end
|
63
|
+
# ensure it got unset
|
64
|
+
assert_raises ArgumentError do
|
65
|
+
ShardModel.connection.connection_name
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_enchilada
|
70
|
+
setup_configuration_for TheWholeEnchilada, 'fiveruns_city_dallas_test_slave'
|
71
|
+
setup_configuration_for TheWholeEnchilada, 'fiveruns_city_dallas_test_master'
|
72
|
+
DataFabric.activate_shard :city => :dallas do
|
73
|
+
assert_equal 'fiveruns_city_dallas_test_slave', TheWholeEnchilada.connection.connection_name
|
74
|
+
|
75
|
+
# Should use the slave
|
76
|
+
assert_raises ActiveRecord::RecordNotFound do
|
77
|
+
TheWholeEnchilada.find(1)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Should use the master
|
81
|
+
mmmm = TheWholeEnchilada.new
|
82
|
+
mmmm.instance_variable_set(:@attributes, { 'id' => 1 })
|
83
|
+
assert_raises ActiveRecord::RecordNotFound do
|
84
|
+
mmmm.reload
|
85
|
+
end
|
86
|
+
# ...but immediately set it back to default to the slave
|
87
|
+
assert_equal 'fiveruns_city_dallas_test_slave', TheWholeEnchilada.connection.connection_name
|
88
|
+
|
89
|
+
# Should use the master
|
90
|
+
TheWholeEnchilada.transaction do
|
91
|
+
mmmm.save!
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def setup_configuration_for(clazz, name)
|
99
|
+
flexmock(clazz).should_receive(:mysql_connection).and_return(AdapterMock.new(RawConnection.new))
|
100
|
+
ActiveRecord::Base.configurations ||= HashWithIndifferentAccess.new
|
101
|
+
ActiveRecord::Base.configurations[name] = HashWithIndifferentAccess.new({ :adapter => 'mysql', :database => name, :host => 'localhost'})
|
102
|
+
end
|
103
|
+
end
|
data/test/database.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
fiveruns_city_austin_test_master:
|
2
|
+
adapter: mysql
|
3
|
+
host: localhost
|
4
|
+
database: vr_austin_master
|
5
|
+
username: root
|
6
|
+
password:
|
7
|
+
|
8
|
+
fiveruns_city_austin_test_slave:
|
9
|
+
adapter: mysql
|
10
|
+
host: localhost
|
11
|
+
database: vr_austin_slave
|
12
|
+
username: root
|
13
|
+
password:
|
14
|
+
|
15
|
+
fiveruns_city_dallas_test_master:
|
16
|
+
adapter: mysql
|
17
|
+
host: localhost
|
18
|
+
database: vr_dallas_master
|
19
|
+
username: root
|
20
|
+
password:
|
21
|
+
|
22
|
+
fiveruns_city_dallas_test_slave:
|
23
|
+
adapter: mysql
|
24
|
+
host: localhost
|
25
|
+
database: vr_dallas_slave
|
26
|
+
username: root
|
27
|
+
password:
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'flexmock/test_unit'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
class TheWholeBurrito < ActiveRecord::Base
|
6
|
+
connection_topology :prefix => 'fiveruns', :replicated => true, :shard_by => :city
|
7
|
+
end
|
8
|
+
|
9
|
+
class DatabaseTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def setup
|
12
|
+
filename = File.join(File.dirname(__FILE__), "database.yml")
|
13
|
+
ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(filename)).result)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_live_burrito
|
17
|
+
DataFabric.activate_shard :city => :dallas do
|
18
|
+
assert_equal 'fiveruns_city_dallas_test_slave', TheWholeBurrito.connection.connection_name
|
19
|
+
|
20
|
+
# Should use the slave
|
21
|
+
burrito = TheWholeBurrito.find(1)
|
22
|
+
assert_equal 'vr_dallas_slave', burrito.name
|
23
|
+
|
24
|
+
# Should use the master
|
25
|
+
burrito.reload
|
26
|
+
assert_equal 'vr_dallas_master', burrito.name
|
27
|
+
|
28
|
+
# ...but immediately set it back to default to the slave
|
29
|
+
assert_equal 'fiveruns_city_dallas_test_slave', TheWholeBurrito.connection.connection_name
|
30
|
+
|
31
|
+
# Should use the master
|
32
|
+
TheWholeBurrito.transaction do
|
33
|
+
burrito = TheWholeBurrito.find(1)
|
34
|
+
assert_equal 'vr_dallas_master', burrito.name
|
35
|
+
burrito.save!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/test/shard_test.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class ShardTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_activation_should_persist_in_thread
|
6
|
+
DataFabric.activate_shard(:city => 'austin')
|
7
|
+
assert_equal 'austin', DataFabric.active_shard(:city)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_activation_in_one_thread_does_not_change_another
|
11
|
+
assert_raises ArgumentError do
|
12
|
+
DataFabric.active_shard(:city)
|
13
|
+
end
|
14
|
+
DataFabric.activate_shard(:city => 'austin')
|
15
|
+
|
16
|
+
Thread.new do
|
17
|
+
assert_raises ArgumentError do
|
18
|
+
DataFabric.active_shard(:city)
|
19
|
+
end
|
20
|
+
DataFabric.activate_shard(:city => 'dallas')
|
21
|
+
assert_equal 'dallas', DataFabric.active_shard(:city)
|
22
|
+
end.join
|
23
|
+
end
|
24
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
ENV['RAILS_ENV']='test'
|
2
|
+
RAILS_ENV='test'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
# Bootstrap AR
|
8
|
+
gem 'activerecord', '=2.0.2'
|
9
|
+
require 'active_record'
|
10
|
+
require 'active_record/version'
|
11
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
12
|
+
ActiveRecord::Base.logger.level = Logger::WARN
|
13
|
+
ActiveRecord::Base.allow_concurrency = false
|
14
|
+
|
15
|
+
# Bootstrap DF
|
16
|
+
Dependencies.load_paths << File.join(File.dirname(__FILE__), '../lib')
|
17
|
+
require 'init'
|