kovyrin-db-charmer 1.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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /doc
2
+ /pkg
data/LICENSE ADDED
@@ -0,0 +1,340 @@
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 2, June 1991
3
+
4
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
6
+ Everyone is permitted to copy and distribute verbatim copies
7
+ of this license document, but changing it is not allowed.
8
+
9
+ Preamble
10
+
11
+ The licenses for most software are designed to take away your
12
+ freedom to share and change it. By contrast, the GNU General Public
13
+ License is intended to guarantee your freedom to share and change free
14
+ software--to make sure the software is free for all its users. This
15
+ General Public License applies to most of the Free Software
16
+ Foundation's software and to any other program whose authors commit to
17
+ using it. (Some other Free Software Foundation software is covered by
18
+ the GNU Library General Public License instead.) You can apply it to
19
+ your programs, too.
20
+
21
+ When we speak of free software, we are referring to freedom, not
22
+ price. Our General Public Licenses are designed to make sure that you
23
+ have the freedom to distribute copies of free software (and charge for
24
+ this service if you wish), that you receive source code or can get it
25
+ if you want it, that you can change the software or use pieces of it
26
+ in new free programs; and that you know you can do these things.
27
+
28
+ To protect your rights, we need to make restrictions that forbid
29
+ anyone to deny you these rights or to ask you to surrender the rights.
30
+ These restrictions translate to certain responsibilities for you if you
31
+ distribute copies of the software, or if you modify it.
32
+
33
+ For example, if you distribute copies of such a program, whether
34
+ gratis or for a fee, you must give the recipients all the rights that
35
+ you have. You must make sure that they, too, receive or can get the
36
+ source code. And you must show them these terms so they know their
37
+ rights.
38
+
39
+ We protect your rights with two steps: (1) copyright the software, and
40
+ (2) offer you this license which gives you legal permission to copy,
41
+ distribute and/or modify the software.
42
+
43
+ Also, for each author's protection and ours, we want to make certain
44
+ that everyone understands that there is no warranty for this free
45
+ software. If the software is modified by someone else and passed on, we
46
+ want its recipients to know that what they have is not the original, so
47
+ that any problems introduced by others will not reflect on the original
48
+ authors' reputations.
49
+
50
+ Finally, any free program is threatened constantly by software
51
+ patents. We wish to avoid the danger that redistributors of a free
52
+ program will individually obtain patent licenses, in effect making the
53
+ program proprietary. To prevent this, we have made it clear that any
54
+ patent must be licensed for everyone's free use or not licensed at all.
55
+
56
+ The precise terms and conditions for copying, distribution and
57
+ modification follow.
58
+
59
+ GNU GENERAL PUBLIC LICENSE
60
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
+
62
+ 0. This License applies to any program or other work which contains
63
+ a notice placed by the copyright holder saying it may be distributed
64
+ under the terms of this General Public License. The "Program", below,
65
+ refers to any such program or work, and a "work based on the Program"
66
+ means either the Program or any derivative work under copyright law:
67
+ that is to say, a work containing the Program or a portion of it,
68
+ either verbatim or with modifications and/or translated into another
69
+ language. (Hereinafter, translation is included without limitation in
70
+ the term "modification".) Each licensee is addressed as "you".
71
+
72
+ Activities other than copying, distribution and modification are not
73
+ covered by this License; they are outside its scope. The act of
74
+ running the Program is not restricted, and the output from the Program
75
+ is covered only if its contents constitute a work based on the
76
+ Program (independent of having been made by running the Program).
77
+ Whether that is true depends on what the Program does.
78
+
79
+ 1. You may copy and distribute verbatim copies of the Program's
80
+ source code as you receive it, in any medium, provided that you
81
+ conspicuously and appropriately publish on each copy an appropriate
82
+ copyright notice and disclaimer of warranty; keep intact all the
83
+ notices that refer to this License and to the absence of any warranty;
84
+ and give any other recipients of the Program a copy of this License
85
+ along with the Program.
86
+
87
+ You may charge a fee for the physical act of transferring a copy, and
88
+ you may at your option offer warranty protection in exchange for a fee.
89
+
90
+ 2. You may modify your copy or copies of the Program or any portion
91
+ of it, thus forming a work based on the Program, and copy and
92
+ distribute such modifications or work under the terms of Section 1
93
+ above, provided that you also meet all of these conditions:
94
+
95
+ a) You must cause the modified files to carry prominent notices
96
+ stating that you changed the files and the date of any change.
97
+
98
+ b) You must cause any work that you distribute or publish, that in
99
+ whole or in part contains or is derived from the Program or any
100
+ part thereof, to be licensed as a whole at no charge to all third
101
+ parties under the terms of this License.
102
+
103
+ c) If the modified program normally reads commands interactively
104
+ when run, you must cause it, when started running for such
105
+ interactive use in the most ordinary way, to print or display an
106
+ announcement including an appropriate copyright notice and a
107
+ notice that there is no warranty (or else, saying that you provide
108
+ a warranty) and that users may redistribute the program under
109
+ these conditions, and telling the user how to view a copy of this
110
+ License. (Exception: if the Program itself is interactive but
111
+ does not normally print such an announcement, your work based on
112
+ the Program is not required to print an announcement.)
113
+
114
+ These requirements apply to the modified work as a whole. If
115
+ identifiable sections of that work are not derived from the Program,
116
+ and can be reasonably considered independent and separate works in
117
+ themselves, then this License, and its terms, do not apply to those
118
+ sections when you distribute them as separate works. But when you
119
+ distribute the same sections as part of a whole which is a work based
120
+ on the Program, the distribution of the whole must be on the terms of
121
+ this License, whose permissions for other licensees extend to the
122
+ entire whole, and thus to each and every part regardless of who wrote it.
123
+
124
+ Thus, it is not the intent of this section to claim rights or contest
125
+ your rights to work written entirely by you; rather, the intent is to
126
+ exercise the right to control the distribution of derivative or
127
+ collective works based on the Program.
128
+
129
+ In addition, mere aggregation of another work not based on the Program
130
+ with the Program (or with a work based on the Program) on a volume of
131
+ a storage or distribution medium does not bring the other work under
132
+ the scope of this License.
133
+
134
+ 3. You may copy and distribute the Program (or a work based on it,
135
+ under Section 2) in object code or executable form under the terms of
136
+ Sections 1 and 2 above provided that you also do one of the following:
137
+
138
+ a) Accompany it with the complete corresponding machine-readable
139
+ source code, which must be distributed under the terms of Sections
140
+ 1 and 2 above on a medium customarily used for software interchange; or,
141
+
142
+ b) Accompany it with a written offer, valid for at least three
143
+ years, to give any third party, for a charge no more than your
144
+ cost of physically performing source distribution, a complete
145
+ machine-readable copy of the corresponding source code, to be
146
+ distributed under the terms of Sections 1 and 2 above on a medium
147
+ customarily used for software interchange; or,
148
+
149
+ c) Accompany it with the information you received as to the offer
150
+ to distribute corresponding source code. (This alternative is
151
+ allowed only for noncommercial distribution and only if you
152
+ received the program in object code or executable form with such
153
+ an offer, in accord with Subsection b above.)
154
+
155
+ The source code for a work means the preferred form of the work for
156
+ making modifications to it. For an executable work, complete source
157
+ code means all the source code for all modules it contains, plus any
158
+ associated interface definition files, plus the scripts used to
159
+ control compilation and installation of the executable. However, as a
160
+ special exception, the source code distributed need not include
161
+ anything that is normally distributed (in either source or binary
162
+ form) with the major components (compiler, kernel, and so on) of the
163
+ operating system on which the executable runs, unless that component
164
+ itself accompanies the executable.
165
+
166
+ If distribution of executable or object code is made by offering
167
+ access to copy from a designated place, then offering equivalent
168
+ access to copy the source code from the same place counts as
169
+ distribution of the source code, even though third parties are not
170
+ compelled to copy the source along with the object code.
171
+
172
+ 4. You may not copy, modify, sublicense, or distribute the Program
173
+ except as expressly provided under this License. Any attempt
174
+ otherwise to copy, modify, sublicense or distribute the Program is
175
+ void, and will automatically terminate your rights under this License.
176
+ However, parties who have received copies, or rights, from you under
177
+ this License will not have their licenses terminated so long as such
178
+ parties remain in full compliance.
179
+
180
+ 5. You are not required to accept this License, since you have not
181
+ signed it. However, nothing else grants you permission to modify or
182
+ distribute the Program or its derivative works. These actions are
183
+ prohibited by law if you do not accept this License. Therefore, by
184
+ modifying or distributing the Program (or any work based on the
185
+ Program), you indicate your acceptance of this License to do so, and
186
+ all its terms and conditions for copying, distributing or modifying
187
+ the Program or works based on it.
188
+
189
+ 6. Each time you redistribute the Program (or any work based on the
190
+ Program), the recipient automatically receives a license from the
191
+ original licensor to copy, distribute or modify the Program subject to
192
+ these terms and conditions. You may not impose any further
193
+ restrictions on the recipients' exercise of the rights granted herein.
194
+ You are not responsible for enforcing compliance by third parties to
195
+ this License.
196
+
197
+ 7. If, as a consequence of a court judgment or allegation of patent
198
+ infringement or for any other reason (not limited to patent issues),
199
+ conditions are imposed on you (whether by court order, agreement or
200
+ otherwise) that contradict the conditions of this License, they do not
201
+ excuse you from the conditions of this License. If you cannot
202
+ distribute so as to satisfy simultaneously your obligations under this
203
+ License and any other pertinent obligations, then as a consequence you
204
+ may not distribute the Program at all. For example, if a patent
205
+ license would not permit royalty-free redistribution of the Program by
206
+ all those who receive copies directly or indirectly through you, then
207
+ the only way you could satisfy both it and this License would be to
208
+ refrain entirely from distribution of the Program.
209
+
210
+ If any portion of this section is held invalid or unenforceable under
211
+ any particular circumstance, the balance of the section is intended to
212
+ apply and the section as a whole is intended to apply in other
213
+ circumstances.
214
+
215
+ It is not the purpose of this section to induce you to infringe any
216
+ patents or other property right claims or to contest validity of any
217
+ such claims; this section has the sole purpose of protecting the
218
+ integrity of the free software distribution system, which is
219
+ implemented by public license practices. Many people have made
220
+ generous contributions to the wide range of software distributed
221
+ through that system in reliance on consistent application of that
222
+ system; it is up to the author/donor to decide if he or she is willing
223
+ to distribute software through any other system and a licensee cannot
224
+ impose that choice.
225
+
226
+ This section is intended to make thoroughly clear what is believed to
227
+ be a consequence of the rest of this License.
228
+
229
+ 8. If the distribution and/or use of the Program is restricted in
230
+ certain countries either by patents or by copyrighted interfaces, the
231
+ original copyright holder who places the Program under this License
232
+ may add an explicit geographical distribution limitation excluding
233
+ those countries, so that distribution is permitted only in or among
234
+ countries not thus excluded. In such case, this License incorporates
235
+ the limitation as if written in the body of this License.
236
+
237
+ 9. The Free Software Foundation may publish revised and/or new versions
238
+ of the General Public License from time to time. Such new versions will
239
+ be similar in spirit to the present version, but may differ in detail to
240
+ address new problems or concerns.
241
+
242
+ Each version is given a distinguishing version number. If the Program
243
+ specifies a version number of this License which applies to it and "any
244
+ later version", you have the option of following the terms and conditions
245
+ either of that version or of any later version published by the Free
246
+ Software Foundation. If the Program does not specify a version number of
247
+ this License, you may choose any version ever published by the Free Software
248
+ Foundation.
249
+
250
+ 10. If you wish to incorporate parts of the Program into other free
251
+ programs whose distribution conditions are different, write to the author
252
+ to ask for permission. For software which is copyrighted by the Free
253
+ Software Foundation, write to the Free Software Foundation; we sometimes
254
+ make exceptions for this. Our decision will be guided by the two goals
255
+ of preserving the free status of all derivatives of our free software and
256
+ of promoting the sharing and reuse of software generally.
257
+
258
+ NO WARRANTY
259
+
260
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
+ REPAIR OR CORRECTION.
269
+
270
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
+ POSSIBILITY OF SUCH DAMAGES.
279
+
280
+ END OF TERMS AND CONDITIONS
281
+
282
+ How to Apply These Terms to Your New Programs
283
+
284
+ If you develop a new program, and you want it to be of the greatest
285
+ possible use to the public, the best way to achieve this is to make it
286
+ free software which everyone can redistribute and change under these terms.
287
+
288
+ To do so, attach the following notices to the program. It is safest
289
+ to attach them to the start of each source file to most effectively
290
+ convey the exclusion of warranty; and each file should have at least
291
+ the "copyright" line and a pointer to where the full notice is found.
292
+
293
+ <one line to give the program's name and a brief idea of what it does.>
294
+ Copyright (C) <year> <name of author>
295
+
296
+ This program is free software; you can redistribute it and/or modify
297
+ it under the terms of the GNU General Public License as published by
298
+ the Free Software Foundation; either version 2 of the License, or
299
+ (at your option) any later version.
300
+
301
+ This program is distributed in the hope that it will be useful,
302
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
303
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304
+ GNU General Public License for more details.
305
+
306
+ You should have received a copy of the GNU General Public License
307
+ along with this program; if not, write to the Free Software
308
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
309
+
310
+
311
+ Also add information on how to contact you by electronic and paper mail.
312
+
313
+ If the program is interactive, make it output a short notice like this
314
+ when it starts in an interactive mode:
315
+
316
+ Gnomovision version 69, Copyright (C) year name of author
317
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318
+ This is free software, and you are welcome to redistribute it
319
+ under certain conditions; type `show c' for details.
320
+
321
+ The hypothetical commands `show w' and `show c' should show the appropriate
322
+ parts of the General Public License. Of course, the commands you use may
323
+ be called something other than `show w' and `show c'; they could even be
324
+ mouse-clicks or menu items--whatever suits your program.
325
+
326
+ You should also get your employer (if you work as a programmer) or your
327
+ school, if any, to sign a "copyright disclaimer" for the program, if
328
+ necessary. Here is a sample; alter the names:
329
+
330
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
332
+
333
+ <signature of Ty Coon>, 1 April 1989
334
+ Ty Coon, President of Vice
335
+
336
+ This General Public License does not permit incorporating your program into
337
+ proprietary programs. If your program is a subroutine library, you may
338
+ consider it more useful to permit linking proprietary applications with the
339
+ library. If this is what you want to do, use the GNU Library General
340
+ Public License instead of this License.
data/Makefile ADDED
@@ -0,0 +1,2 @@
1
+ doc/files/README_rdoc.html: README.rdoc
2
+ rdoc README.rdoc
data/README.rdoc ADDED
@@ -0,0 +1,208 @@
1
+ = DB Charmer - ActiveRecord Connection Magic Plugin
2
+
3
+ +DbCharmer+ is a simple yet powerful plugin for ActiveRecord that does a few things:
4
+
5
+ 1. Allows you to easily manage AR models' connections (+switch_connection_to+ method)
6
+ 2. Allows you to switch AR models' default connections to a separate servers/databases
7
+ 3. Allows you to easily choose where your query should go (<tt>Model.on_db</tt> methods)
8
+ 4. Allows you to automatically send read queries to your slaves while masters would handle all the updates.
9
+ 5. Adds multiple databases migrations to ActiveRecord
10
+
11
+
12
+ == Installation ==
13
+
14
+ There are two options when approaching db-charmer installation:
15
+ * using gem (recommended)
16
+ * install as a Rails plugin
17
+
18
+ To install as a gem, add this to your environment.rb:
19
+
20
+ config.gem 'kovyrin-db-charmer', :lib => 'db_charmer',
21
+ :source => 'http://gems.github.com'
22
+
23
+ And then run the command:
24
+
25
+ sudo rake gems:install
26
+
27
+ To install db-charmer as a Rails plugin use this:
28
+
29
+ script/plugin install git://github.com/kovyrin/db-charmer.git
30
+
31
+
32
+ == Easy ActiveRecord Connection Management
33
+
34
+ As a part of this plugin we've added +switch_connection_to+ method that accepts many different kinds
35
+ of db connections and uses them on a model. We support:
36
+
37
+ 1. Strings and symbols as the names of connection configuration blocks in database.yml.
38
+ 2. ActiveRecord models (we'd use connection currently set up on a model).
39
+ 3. Database connections (<tt>Model.connection</tt>)
40
+ 4. Nil values to reset model to default connection.
41
+
42
+ Sample code:
43
+
44
+ class Foo < ActiveRecord::Model; end
45
+
46
+ Foo.switch_connection_to(:blah)
47
+ Foo.switch_connection_to('foo')
48
+ Foo.switch_connection_to(Bar)
49
+ Foo.switch_connection_to(Baz.connection)
50
+ Foo.switch_connection_to(nil)
51
+
52
+ The +switch_connection_to+ method has an optional second parameter +should_exist+ which is true
53
+ by default. This parameter is used when the method is called with a string or a symbol connection
54
+ name and there is no such connection configuration in the database.yml file. If this parameter
55
+ is true, an exception would be raised, if it is false, the error would be ignored and no connection
56
+ change would happen. This is really useful when in development mode or in tests you do not want to
57
+ create many different databases on your local machine and just want to put all your tables in one
58
+ database.
59
+
60
+
61
+ == Multiple DB Migrations
62
+
63
+ In every application that works with many databases, there is need in convenient schema migrations mechanism.
64
+
65
+ All Rails users already have this mechanism - rails migrations. So in +DbCharmer+, we've made it possible
66
+ to seamlessly use multiple databases in Rails migrations.
67
+
68
+ There are two methods available in migrations to operate on more than one database:
69
+
70
+ 1. Global connection change method - used to switch whole migration to a non-default database.
71
+ 2. Block-level connection change method - could be used to do only a part of a migration on a non-default db.
72
+
73
+ Migration class example (global connection rewrite):
74
+
75
+ class MultiDbTest < ActiveRecord::Migration
76
+ db_magic :connection => :second_db
77
+
78
+ def self.up
79
+ create_table :test_table, :force => true do |t|
80
+ t.string :test_string
81
+ t.timestamps
82
+ end
83
+ end
84
+
85
+ def self.down
86
+ drop_table :test_table
87
+ end
88
+ end
89
+
90
+ Migration class example (block-level connection rewrite):
91
+
92
+ class MultiDbTest < ActiveRecord::Migration
93
+ def self.up
94
+ on_db :second_db do
95
+ create_table :test_table, :force => true do |t|
96
+ t.string :test_string
97
+ t.timestamps
98
+ end
99
+ end
100
+ end
101
+
102
+ def self.down
103
+ on_db :second_db { drop_table :test_table }
104
+ end
105
+ end
106
+
107
+
108
+ By default in development and test environments you could skip this <tt>:second_db</tt>
109
+ connection from your database.yml files, but in production you'd specify it and
110
+ get the table created on a separate server and/or in a separate database.
111
+
112
+ This behaviour is controlled by the <tt>DbCharmer.migration_connections_should_exist</tt>
113
+ configuration attribute which could be set from a rails initializer.
114
+
115
+
116
+ == Using Models in Master-Slave Environments
117
+
118
+ Master-slave replication is the most popular scale-out technique in medium and large
119
+ database applications today. There are some rails plugins out there that help rails
120
+ developers to use slave servers in their models but none of there were flexible enough
121
+ for us to start using them in a huge application we work on.
122
+
123
+ So, we've been using ActsAsReadonlyable plugin for a long time and have developed a
124
+ lots of additions to its code over that time. Since that plugin has been abandoned
125
+ by its authors, we've decided to collect all of our master-slave code in one plugin
126
+ and release it for rails 2.2+. +DbCharmer+ adds the following features to Rails models:
127
+
128
+
129
+ === Auto-Switching all Reads to Slave(s)
130
+
131
+ When you create a model, you could use <tt>db_magic :slave => :blah</tt> or
132
+ <tt>db_magic :slaves => [ :foo, :bar ]</tt> commands in your model to set up reads
133
+ redirection mode when all your find/count/exist/etc methods will be reading data
134
+ from your slave (or a bunch of slaves in a round-robin manner). Here is an example:
135
+
136
+ class Foo < ActiveRecord::Base
137
+ db_magic :slave => :slave01
138
+ end
139
+
140
+ class Bar < ActiveRecord::Base
141
+ db_magic :slaves => [ :slave01, :slave02 ]
142
+ end
143
+
144
+
145
+ === Default Connection Switching
146
+
147
+ If you have more than one master-slave cluster (or simply more than one database)
148
+ in your database environment, then you might want to change the default database
149
+ connection of some of your models. You could do that by using
150
+ <tt>db_magic :connection => :foo</tt> call from your models. Example:
151
+
152
+ class Foo < ActiveRecord::Base
153
+ db_magic :connection => :foo
154
+ end
155
+
156
+ Sample model on a separate master-slave cluster (so, separate main connection +
157
+ a slave connection):
158
+
159
+ class Bar < ActiveRecord::Base
160
+ db_magic :connection => :bar, :slave => :bar_slave
161
+ end
162
+
163
+ === Per-Query Connection Management
164
+
165
+ Sometimes you have some select queries that you know you want to run on the master.
166
+ This could happen for example when you have just added some data and need to read
167
+ it back and not sure if it made it all the way to the slave yet or no. For this
168
+ situation an few others there are a few methods we've added to ActiveRecord models:
169
+
170
+ 1) +on_master+ - this method could be used in two forms: block form and proxy form.
171
+ In the block form you could force connection switch for a block of code:
172
+
173
+ User.on_master do
174
+ user = User.find_by_login('foo')
175
+ user.update_attributes!(:activated => true)
176
+ end
177
+
178
+ In the proxy form this method could be used to force one query to be performed on
179
+ the master database server:
180
+
181
+ Comment.on_master.last(:limit => 5)
182
+ User.on_master.find_by_activation_code(code)
183
+ User.on_master.exists?(:login => login, :password => password)
184
+
185
+ 2) +on_slave+ - this method is used to force a query to be run on a slave even in
186
+ situations when it's been previously forced to use the master. If there is more
187
+ than one slave, one would be selected randomly. Tis method has two forms as
188
+ well: block and proxy.
189
+
190
+ 3) <tt>on_db(connection)</tt> - this method is what makes two previous methods possible.
191
+ It is used to switch a model's connection to some db for a short block of code
192
+ or even for one statement (two forms). It accepts the same range of values as
193
+ the +switch_connection_to+ method does.
194
+
195
+
196
+ == Documentation
197
+
198
+ For more information on the plugin internals, please check out the source code. All the plugin's code is covered with tests that were placed in a separate staging rails project located at http://github.com/kovyrin/db-charmer-sandbox. The project has unit tests for all or at least the most of the parts of plugin's code.
199
+
200
+
201
+ == What Ruby and Rails implementations does it work for?
202
+
203
+ We've tested the plugin on MRI 1.8.6 with Rails 2.3.3. After the initial testing phase we're going to deploy this code in production.
204
+
205
+
206
+ == Who are the authors?
207
+
208
+ This plugin has been created in Scribd.com for our internal use and then the sources were opened for other people to use. All the code in this package has been developed by Alexey Kovyrin for Scribd.com and is released under the GPLv2 license. For more details, see the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = 'db-charmer'
5
+ gemspec.summary = 'db-charmer 1.0.1'
6
+ gemspec.description = 'ActiveRecord Connections Magic (slaves, multiple connections, etc)'
7
+ gemspec.email = 'alexey@kovyrin.net'
8
+ gemspec.homepage = 'http://github.com/kovyrin/db-charmer'
9
+ gemspec.authors = ['Alexey Kovyrin']
10
+
11
+ gemspec.add_dependency('rails', '>= 2.3.3')
12
+ end
13
+ rescue LoadError
14
+ puts 'Jeweler not available. Install it with: sudo gem install jeweler'
15
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,56 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{db-charmer}
8
+ s.version = "1.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Alexey Kovyrin"]
12
+ s.date = %q{2009-08-26}
13
+ s.description = %q{ActiveRecord Connections Magic (slaves, multiple connections, etc)}
14
+ s.email = %q{alexey@kovyrin.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "Makefile",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "db-charmer.gemspec",
27
+ "init.rb",
28
+ "lib/db_charmer.rb",
29
+ "lib/db_charmer/active_record_extensions.rb",
30
+ "lib/db_charmer/connection_factory.rb",
31
+ "lib/db_charmer/connection_proxy.rb",
32
+ "lib/db_charmer/connection_switch.rb",
33
+ "lib/db_charmer/db_magic.rb",
34
+ "lib/db_charmer/finder_overrides.rb",
35
+ "lib/db_charmer/multi_db_migrations.rb",
36
+ "lib/db_charmer/multi_db_proxy.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/kovyrin/db-charmer}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.5}
42
+ s.summary = %q{db-charmer 1.0.1}
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<rails>, [">= 2.3.3"])
50
+ else
51
+ s.add_dependency(%q<rails>, [">= 2.3.3"])
52
+ end
53
+ else
54
+ s.add_dependency(%q<rails>, [">= 2.3.3"])
55
+ end
56
+ end
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ # Placeholder to satisfy Rails.
2
+ #
3
+ # Do NOT add any require statements to this file. Doing
4
+ # so will cause Rails to load this plugin all of the time.
data/lib/db_charmer.rb ADDED
@@ -0,0 +1,44 @@
1
+ module DbCharmer
2
+ @@migration_connections_should_exist = Rails.env.production?
3
+ mattr_accessor :migration_connections_should_exist
4
+
5
+ def self.migration_connections_should_exist?
6
+ !! migration_connections_should_exist
7
+ end
8
+
9
+ @@connections_should_exist = Rails.env.production?
10
+ mattr_accessor :connections_should_exist
11
+
12
+ def self.connections_should_exist?
13
+ !! connections_should_exist
14
+ end
15
+
16
+ def self.logger
17
+ return Rails.logger if defined?(Rails)
18
+ @logger ||= Logger.new(STDERR)
19
+ end
20
+ end
21
+
22
+ require 'db_charmer/active_record_extensions'
23
+ require 'db_charmer/connection_factory'
24
+ require 'db_charmer/connection_proxy'
25
+ require 'db_charmer/connection_switch'
26
+ require 'db_charmer/db_magic'
27
+ require 'db_charmer/finder_overrides'
28
+ require 'db_charmer/multi_db_migrations'
29
+ require 'db_charmer/multi_db_proxy'
30
+
31
+ # Enable misc AR extensions
32
+ ActiveRecord::Base.extend(DbCharmer::ActiveRecordExtensions::ClassMethods)
33
+
34
+ # Enable connections switching in AR
35
+ ActiveRecord::Base.extend(DbCharmer::ConnectionSwitch::ClassMethods)
36
+
37
+ # Enable connection proxy in AR
38
+ ActiveRecord::Base.extend(DbCharmer::MultiDbProxy::ClassMethods)
39
+
40
+ # Enable multi-db migrations
41
+ ActiveRecord::Migration.extend(DbCharmer::MultiDbMigrations::ClassMethods)
42
+
43
+ # Enable the magic
44
+ ActiveRecord::Base.extend(DbCharmer::DbMagic::ClassMethods)
@@ -0,0 +1,75 @@
1
+ module DbCharmer
2
+ module ActiveRecordExtensions
3
+ module ClassMethods
4
+
5
+ def establish_real_connection_if_exists(name, should_exist = false)
6
+ config = configurations[RAILS_ENV][name.to_s]
7
+ if should_exist && !config
8
+ raise ArgumentError, "Invalid connection name (does not exist in database.yml): #{RAILS_ENV}/#{name}"
9
+ end
10
+ establish_connection(config) if config
11
+ end
12
+
13
+ #-----------------------------------------------------------------------------
14
+ @@db_charmer_opts = {}
15
+ def db_charmer_opts=(opts)
16
+ @@db_charmer_opts[self.to_s] = opts
17
+ end
18
+
19
+ def db_charmer_opts
20
+ @@db_charmer_opts[self.to_s] || {}
21
+ end
22
+
23
+ #-----------------------------------------------------------------------------
24
+ @@db_charmer_connection_proxies = {}
25
+ def db_charmer_connection_proxy=(proxy)
26
+ @@db_charmer_connection_proxies[self.to_s] = proxy
27
+ end
28
+
29
+ def db_charmer_connection_proxy
30
+ @@db_charmer_connection_proxies[self.to_s]
31
+ end
32
+
33
+ #-----------------------------------------------------------------------------
34
+ @@db_charmer_slaves = {}
35
+ def db_charmer_slaves=(slaves)
36
+ @@db_charmer_slaves[self.to_s] = slaves
37
+ end
38
+
39
+ def db_charmer_slaves
40
+ @@db_charmer_slaves[self.to_s] || []
41
+ end
42
+
43
+ def db_charmer_random_slave
44
+ return nil unless db_charmer_slaves.any?
45
+ db_charmer_slaves[rand(db_charmer_slaves.size)]
46
+ end
47
+
48
+ #-----------------------------------------------------------------------------
49
+ @@db_charmer_connection_levels = Hash.new(0)
50
+ def db_charmer_connection_level=(level)
51
+ @@db_charmer_connection_levels[self.to_s] = level
52
+ end
53
+
54
+ def db_charmer_connection_level
55
+ @@db_charmer_connection_levels[self.to_s] || 0
56
+ end
57
+
58
+ def db_charmer_top_level_connection?
59
+ db_charmer_connection_level.zero?
60
+ end
61
+
62
+ #-----------------------------------------------------------------------------
63
+ def hijack_connection!
64
+ # FIXME: make sure we do not do it more often then needed
65
+ # puts "DEBUG: Hijacking connection for #{self.to_s}"
66
+ class << self
67
+ def connection
68
+ db_charmer_connection_proxy || super
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,38 @@
1
+ module DbCharmer
2
+ class ConnectionFactory
3
+ @@connection_classes = {}
4
+
5
+ def self.reset!
6
+ @@connection_classes = {}
7
+ end
8
+
9
+ def self.connect(db_name, should_exist = false)
10
+ @@connection_classes[db_name.to_s] ||= establish_connection(db_name.to_s, should_exist)
11
+ DbCharmer.logger.warn("ConnectionFactory.connect(#{db_name}) = #{@@connection_classes[db_name.to_s]}")
12
+ return @@connection_classes[db_name.to_s]
13
+ end
14
+
15
+ def self.establish_connection(db_name, should_exist = false)
16
+ DbCharmer.logger.debug("Creating a connection proxy for #{db_name}")
17
+ abstract_class = generate_abstract_class(db_name, should_exist)
18
+ DbCharmer::ConnectionProxy.new(abstract_class)
19
+ end
20
+
21
+ def self.generate_abstract_class(db_name, should_exist = false)
22
+ DbCharmer.logger.info("Generating abstract connection class for #{db_name}: #{abstract_connection_class_name db_name}")
23
+
24
+ module_eval <<-EOF, __FILE__, __LINE__ + 1
25
+ class #{abstract_connection_class_name db_name} < ActiveRecord::Base
26
+ self.abstract_class = true
27
+ establish_real_connection_if_exists(:#{db_name}, #{!!should_exist})
28
+ end
29
+ EOF
30
+
31
+ abstract_connection_class_name(db_name).constantize
32
+ end
33
+
34
+ def self.abstract_connection_class_name(db_name)
35
+ "::AutoGeneratedAbstractConnectionClass#{db_name.camelize}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ module DbCharmer
2
+ class ConnectionProxy < BlankSlate
3
+ def initialize(abstract_class)
4
+ @abstract_connection_class = abstract_class
5
+ end
6
+
7
+ def method_missing(meth, *args, &block)
8
+ DbCharmer.logger.debug("Proxying #{meth} call to #{@abstract_connection_class}")
9
+ @abstract_connection_class.retrieve_connection.send(meth, *args, &block)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ module DbCharmer
2
+ module ConnectionSwitch
3
+ module ClassMethods
4
+ def coerce_to_connection_proxy(conn, should_exist = true)
5
+ return nil if conn.nil?
6
+
7
+ if conn.kind_of?(Symbol) || conn.kind_of?(String)
8
+ return DbCharmer::ConnectionFactory.connect(conn, should_exist)
9
+ end
10
+
11
+ if conn.respond_to?(:db_charmer_connection_proxy)
12
+ return conn.db_charmer_connection_proxy
13
+ end
14
+
15
+ if conn.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
16
+ return conn
17
+ end
18
+
19
+ raise "Unsupported connection type: #{conn.class}"
20
+ end
21
+
22
+ def switch_connection_to(conn, require_config_to_exist = true)
23
+ # puts "DEBUG: Assigning connection proxy for #{self}"
24
+ self.db_charmer_connection_proxy = coerce_to_connection_proxy(conn, require_config_to_exist)
25
+ self.hijack_connection!
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ module DbCharmer
2
+ module DbMagic
3
+ module ClassMethods
4
+ def db_magic(opt = {})
5
+ # Make sure we could use our connections management here
6
+ hijack_connection!
7
+
8
+ # Should requested connections exist in the config?
9
+ should_exist = opt[:should_exist] || DbCharmer.connections_should_exist?
10
+
11
+ # Main connection management
12
+ db_magic_connection(opt[:connection], should_exist) if opt[:connection]
13
+
14
+ # Set up slaves pool
15
+ opt[:slaves] ||= []
16
+ opt[:slaves] << opt[:slave] if opt[:slave]
17
+ db_magic_slaves(opt[:slaves], should_exist) if opt[:slaves].any?
18
+
19
+ # Setup inheritance magic
20
+ setup_children_magic(opt)
21
+ end
22
+
23
+ private
24
+
25
+ def setup_children_magic(opt)
26
+ self.db_charmer_opts = opt.clone
27
+
28
+ def self.inherited(child)
29
+ child.db_magic(self.db_charmer_opts)
30
+ super
31
+ end
32
+ end
33
+
34
+ def db_magic_connection(conn, should_exist = false)
35
+ switch_connection_to(conn, should_exist)
36
+ end
37
+
38
+ def db_magic_slaves(slaves, should_exist = false)
39
+ self.db_charmer_slaves = slaves.collect do |slave|
40
+ coerce_to_connection_proxy(slave, should_exist)
41
+ end
42
+
43
+ self.extend(DbCharmer::FinderOverrides::ClassMethods)
44
+ self.send(:include, DbCharmer::FinderOverrides::InstanceMethods)
45
+ self.extend(DbCharmer::MultiDbProxy::MasterSlaveClassMethods)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,47 @@
1
+ module DbCharmer
2
+ module FinderOverrides
3
+ module ClassMethods
4
+ SLAVE_METHODS = [ :find_by_sql, :count_by_sql, :calculate ]
5
+ MASTER_METHODS = [ :update, :create, :delete, :destroy, :delete_all, :destroy_all, :update_all, :update_counters ]
6
+
7
+ SLAVE_METHODS.each do |slave_method|
8
+ class_eval <<-EOF
9
+ def #{slave_method}(*args, &block)
10
+ first_level_on_slave do
11
+ super(*args, &block)
12
+ end
13
+ end
14
+ EOF
15
+ end
16
+
17
+ MASTER_METHODS.each do |master_method|
18
+ class_eval <<-EOF
19
+ def #{master_method}(*args, &block)
20
+ on_master do
21
+ super(*args, &block)
22
+ end
23
+ end
24
+ EOF
25
+ end
26
+
27
+ private
28
+
29
+ def first_level_on_slave
30
+ if db_charmer_top_level_connection?
31
+ on_slave { yield }
32
+ else
33
+ yield
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ module InstanceMethods
40
+ def reload(*args, &block)
41
+ self.class.on_master do
42
+ super(*args, &block)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,42 @@
1
+ module DbCharmer
2
+ module MultiDbMigrations
3
+ class MigrationAbstractClass < ActiveRecord::Base
4
+ abstract_class = true
5
+ hijack_connection!
6
+ end
7
+
8
+ module ClassMethods
9
+ def hijack_connection!
10
+ class << self
11
+ def connection
12
+ puts "DEBUG: Retrieving migration connection"
13
+ MigrationAbstractClass.connection
14
+ end
15
+ end
16
+ end
17
+
18
+ def on_db(db_name)
19
+ hijack_connection!
20
+ announce "Switching connection to #{db_name}"
21
+ old_proxy = MigrationAbstractClass.db_charmer_connection_proxy
22
+ MigrationAbstractClass.switch_connection_to(db_name, DbCharmer.migration_connections_should_exist?)
23
+ yield
24
+ ensure
25
+ announce "Checking all database connections"
26
+ ActiveRecord::Base.verify_active_connections!
27
+ announce "Switching connection back to default"
28
+ MigrationAbstractClass.switch_connection_to(old_proxy)
29
+ end
30
+
31
+ def works_on_db(db_name)
32
+ hijack_connection!
33
+ MigrationAbstractClass.switch_connection_to(db_name, DbCharmer.migration_connections_should_exist?)
34
+ end
35
+
36
+ def db_magic(opts = {})
37
+ raise ArgumentError, "No connection name - no magic!" unless opts[:connection]
38
+ works_on_db(opts[:connection])
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ module DbCharmer
2
+ module MultiDbProxy
3
+ class OnDbProxy < BlankSlate
4
+ def initialize(model_class, slave)
5
+ @model = model_class
6
+ @slave = slave
7
+ end
8
+
9
+ private
10
+
11
+ def method_missing(meth, *args, &block)
12
+ @model.on_db(@slave) do |m|
13
+ m.__send__(meth, *args, &block)
14
+ end
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def on_db(con)
20
+ # Chain call
21
+ return OnDbProxy.new(self, con) unless block_given?
22
+
23
+ # Block call
24
+ begin
25
+ self.db_charmer_connection_level += 1
26
+ old_proxy = db_charmer_connection_proxy
27
+ switch_connection_to(con, DbCharmer.migration_connections_should_exist?)
28
+ yield(self)
29
+ ensure
30
+ switch_connection_to(old_proxy)
31
+ self.db_charmer_connection_level -= 1
32
+ end
33
+ end
34
+ end
35
+
36
+ module MasterSlaveClassMethods
37
+ def on_slave(con = nil, &block)
38
+ con ||= db_charmer_random_slave
39
+ raise ArgumentError, "No slaves found in the class and no slave connection given" unless con
40
+ on_db(con, &block)
41
+ end
42
+
43
+ def on_master(&block)
44
+ on_db(nil, &block)
45
+ end
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kovyrin-db-charmer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alexey Kovyrin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-26 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.3
24
+ version:
25
+ description: ActiveRecord Connections Magic (slaves, multiple connections, etc)
26
+ email: alexey@kovyrin.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .gitignore
36
+ - LICENSE
37
+ - Makefile
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - db-charmer.gemspec
42
+ - init.rb
43
+ - lib/db_charmer.rb
44
+ - lib/db_charmer/active_record_extensions.rb
45
+ - lib/db_charmer/connection_factory.rb
46
+ - lib/db_charmer/connection_proxy.rb
47
+ - lib/db_charmer/connection_switch.rb
48
+ - lib/db_charmer/db_magic.rb
49
+ - lib/db_charmer/finder_overrides.rb
50
+ - lib/db_charmer/multi_db_migrations.rb
51
+ - lib/db_charmer/multi_db_proxy.rb
52
+ has_rdoc: false
53
+ homepage: http://github.com/kovyrin/db-charmer
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --charset=UTF-8
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.2.0
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: db-charmer 1.0.1
78
+ test_files: []
79
+