st-elsewhere 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Brian A. Doll
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,50 @@
1
+ === St. Elsewhere
2
+
3
+ An ActiveRecord plugin to support relationships across different databases
4
+
5
+ http://emphaticsolutions.com/images/st.elsewhere.jpg
6
+
7
+ === The Scenario
8
+
9
+ For a variety of reasons, you might find yourself supporting multiple databases in your Rails application. Maybe you're connecting to a legacy database for a few models. Perhaps you have divided your Rails application into two parts, one database for your online catalog system and another for transactional data. Multiple database connections in Rails is {nothing new.}[http://www.google.com/search?hl=en&source=hp&q=multiple+database+connections+rails&aq=f&oq=&aqi=g4]
10
+
11
+ === The Problem
12
+
13
+ While there may be great benefits to connecting to multiple databases in your app, there are also costs. One example is that <b><tt>has_many :children, :through => parent_children</tt></b> does not work.
14
+
15
+ You'll encounter one of two errors, depending on your setup:
16
+ * If the schemas are different, you'll see something like: <tt>ActiveRecord::StatementInvalid: Mysql::Error: Table 'appname_transactional.parent_children' doesn't exist</tt>
17
+ * If the schemas are the same but data only exists in one schema or the other, you'll just get empty relationships.
18
+
19
+ === The Solution
20
+
21
+ St. Elsewhere adds a new class method (<tt>has_many_elsewhere</tt>) to support basic association methods across different database connections for ActiveRecord models.
22
+
23
+ Example:
24
+
25
+ class Hospital < AcitveRecord::Base
26
+ has_many :hospital_doctors
27
+ has_many_elsewhere :doctors, :through => :hospital_doctors
28
+ end
29
+
30
+ class HospitalDoctor < ActiveRecord::Base
31
+ belongs_to :hospital
32
+ belongs_to :doctor
33
+ end
34
+
35
+ class TransactionalBase < ActiveRecord::Base
36
+ self.abstract_class = true
37
+ establish_connection "#{RAILS_ENV}-transactional"
38
+ end
39
+
40
+ class Doctor < TransactionalBase
41
+ has_many :hospital_doctors
42
+ has_many :hospitals, :through => :hospital_doctors
43
+ end
44
+
45
+ The following conventional methods are available for Hospital:
46
+ hospital.doctors, hospital.doctors=, hospital.doctor_ids, hospital.doctor_ids=
47
+
48
+ === Inefficiencies
49
+
50
+ <tt>has_many_elsewhere</tt> is certainly much less efficient than a comparable has_many relationship. <tt>has_many :through</tt> relationships use SQL JOINs which while efficient, do not work across multiple database connections. St. Elsewhere implements much of the same resulting API methods in code, using less efficient SQL.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "st-elsewhere"
8
+ gem.summary = %Q{St. Elsewhere supports has_many :through relationships across different databases}
9
+ gem.description = %Q{This gem provides has_many_elsewhere, an ActiveRecord class method to support many to many relationships in Rails applications, across multiple database connections.}
10
+ gem.email = "brian@emphaticsolutions.com"
11
+ gem.homepage = "http://github.com/briandoll/st-elsewhere"
12
+ gem.authors = ["Brian Doll"]
13
+ # gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ task :test => :check_dependencies
29
+
30
+ task :default => :test
31
+
32
+ require 'rake/rdoctask'
33
+ Rake::RDocTask.new do |rdoc|
34
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
35
+
36
+ rdoc.rdoc_dir = 'rdoc'
37
+ rdoc.title = "st-elsewhere #{version}"
38
+ rdoc.rdoc_files.include('README*')
39
+ rdoc.rdoc_files.include('lib/**/*.rb')
40
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'st-elsewhere'
2
+ ActiveRecord::Base.extend StElsewhere
@@ -0,0 +1,107 @@
1
+ module StElsewhere
2
+
3
+ # Specifies a one-to-many association across database connections.
4
+ # This is currently an incomplete implementation and does not yet use all of the options supported by has_many
5
+ #
6
+ # The following methods for retrieval and query of collections of associated objects will be added:
7
+ #
8
+ # [collection<<(object, ...)]
9
+ # TODO: Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
10
+ # [collection=objects]
11
+ # Replaces the collections content by deleting and adding objects as appropriate.
12
+ # [collection_singular_ids]
13
+ # Returns an array of the associated objects' ids
14
+ # [collection_singular_ids=ids]
15
+ # Replace the collection with the objects identified by the primary keys in +ids+
16
+ # [collection.empty?]
17
+ # Returns +true+ if there are no associated objects.
18
+ # [collection.size]
19
+ # Returns the number of associated objects.
20
+ #
21
+ # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
22
+ # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
23
+ #
24
+ # === Example
25
+ #
26
+ # Example: A Firm class declares <tt>has_many_elsewhere :clients</tt>, which will add:
27
+ # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => ["firm_id = ?", id]</tt>)
28
+ # * <tt>Firm#clients=</tt>
29
+ # * <tt>Firm#client_ids</tt>
30
+ # * <tt>Firm#client_ids=</tt>
31
+ # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
32
+ # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
33
+ #
34
+ # === Supported options
35
+ # [:through]
36
+ # Specifies a Join Model through which to perform the query. You can only use a <tt>:through</tt> query through a
37
+ # <tt>belongs_to</tt> <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
38
+ #
39
+ # Option examples:
40
+ # has_many_elsewhere :subscribers, :through => :subscriptions
41
+ def has_many_elsewhere(association_id, options = {}, &extension)
42
+ association_class = association_id.to_s.classify.constantize
43
+ through = options[:through]
44
+ raise ArgumentError.new("You must include :through => association for has_many_elsewhere") if not through
45
+ collection_accessor_methods_elsewhere(association_id, association_class, through)
46
+ end
47
+
48
+ # Dynamically adds all accessor methods for the has_many_elsewhere association
49
+ def collection_accessor_methods_elsewhere(association_id, association_class, through)
50
+ association_singular = association_id.to_s.singularize
51
+ association_plural = association_id.to_s
52
+ through_association_singular = through.to_s.singularize
53
+
54
+ # Hospital#doctor_ids
55
+ define_method("#{association_singular}_ids") do
56
+ self.send("#{association_plural}").map{|a| a.id}
57
+ end
58
+
59
+ # Hospital#doctors
60
+ define_method("#{association_plural}") do
61
+ through_class = through.to_s.singularize.camelize.constantize
62
+ through_association_ids = self.send("#{through.to_s.singularize}_ids")
63
+ through_associations = through_class.find(through_association_ids)
64
+ through_associations.collect{|through_association| through_association.send("#{association_singular}")} || []
65
+ end
66
+
67
+ # Hospital#doctors=
68
+ define_method("#{association_plural}=") do |new_associations|
69
+ through_class = through.to_s.singularize.camelize.constantize
70
+ current_associations = self.send("#{through_association_singular}_ids")
71
+ removed_associations = current_associations - new_associations
72
+ new_associations = new_associations - current_associations
73
+
74
+ self.send("remove_#{association_singular}_associations", association_class, removed_associations)
75
+ self.send("add_#{association_singular}_associations", through_class, association_id, new_associations)
76
+ end
77
+
78
+ # Hospital#doctor_ids=
79
+ define_method("#{association_singular}_ids=") do |new_association_ids|
80
+ self.send("#{association_plural}=", new_association_ids)
81
+ end
82
+
83
+ # Hospital#remove_doctor_associations (private)
84
+ define_method("remove_#{association_singular}_associations") do |association_class, associations|
85
+ associations.each do |association|
86
+ association_class.delete(association)
87
+ end
88
+ end
89
+
90
+ # Hospital#add_doctor_associations (private)
91
+ define_method("add_#{association_singular}_associations") do |through_class, association_id, associations|
92
+ myself = "#{self.class.to_s.downcase}_id"
93
+ my_buddy = "#{association_singular}_id"
94
+ associations.each do |association|
95
+ my_buddy_id = Fixnum.eql?(association.class) ? association : association.id
96
+ new_association = through_class.new(myself => self.id, my_buddy => my_buddy_id)
97
+ new_association.save
98
+ end
99
+ end
100
+
101
+ private "remove_#{association_singular}_associations".to_sym, "add_#{association_singular}_associations".to_sym
102
+
103
+ end
104
+
105
+ private :collection_accessor_methods_elsewhere
106
+
107
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :foo do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ class StElsewhereTest < ActiveSupport::TestCase
4
+ # Replace this with your real tests.
5
+ test "the truth" do
6
+ assert true
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_support/test_case'
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: st-elsewhere
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Doll
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-14 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: This gem provides has_many_elsewhere, an ActiveRecord class method to support many to many relationships in Rails applications, across multiple database connections.
17
+ email: brian@emphaticsolutions.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - MIT-LICENSE
26
+ - README.rdoc
27
+ - Rakefile
28
+ - VERSION
29
+ - init.rb
30
+ - lib/st-elsewhere.rb
31
+ - tasks/st-elsewhere_tasks.rake
32
+ - test/st-elsewhere_test.rb
33
+ - test/test_helper.rb
34
+ has_rdoc: true
35
+ homepage: http://github.com/briandoll/st-elsewhere
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options:
40
+ - --charset=UTF-8
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.3.5
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: St. Elsewhere supports has_many :through relationships across different databases
62
+ test_files:
63
+ - test/st-elsewhere_test.rb
64
+ - test/test_helper.rb