magic_multi_connections 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.
@@ -0,0 +1,20 @@
1
+ print "Using native Postgresql\n"
2
+ require 'logger'
3
+
4
+ ActiveRecord::Base.logger = Logger.new("debug.log")
5
+
6
+ db_connection_options = {
7
+ :adapter => "postgresql",
8
+ :encoding => "utf8",
9
+ :database => 'magic_multi_connections_unittest'
10
+ }
11
+
12
+ db_extra_connection_options = {
13
+ :adapter => "postgresql",
14
+ :encoding => "utf8",
15
+ :database => 'magic_multi_connections_extra_unittest'
16
+ }
17
+
18
+
19
+ ActiveRecord::Base.configurations = { 'production' => db_connection_options, 'contact_repo' => db_extra_connection_options }
20
+ ActiveRecord::Base.establish_connection(:production)
@@ -0,0 +1,3 @@
1
+ module ContactRepository
2
+ establish_connection :contact_repo
3
+ end
@@ -0,0 +1,7 @@
1
+ CREATE TABLE "people" (
2
+ "id" SERIAL,
3
+ "name" varchar(255),
4
+ "email" varchar(255),
5
+ "age" int,
6
+ PRIMARY KEY ("id")
7
+ );
@@ -0,0 +1,5 @@
1
+ first:
2
+ id: 1
3
+ name: Dr Nic
4
+ age: 30
5
+ email: drnicwilliams@gmail.com
@@ -0,0 +1,2 @@
1
+ class Person < ActiveRecord::Base
2
+ end
@@ -0,0 +1,71 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_record'
4
+ require 'active_record/fixtures'
5
+
6
+ begin
7
+ require 'connection'
8
+ rescue MissingSourceFile => e
9
+ # required for tests not run via test_#{adapter} rake tests, e.g. autotest
10
+ adapter = 'postgresql' #'sqlite'
11
+ require "#{File.dirname(__FILE__)}/connections/native_#{adapter}/connection"
12
+ end
13
+
14
+ require File.dirname(__FILE__) + '/../lib/magic_multi_connections'
15
+
16
+
17
+ models = %w[person contact_repository]
18
+ models.each { |model| require File.join(File.dirname(__FILE__), 'fixtures', model) }
19
+
20
+ class Test::Unit::TestCase #:nodoc:
21
+ self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
22
+ self.use_instantiated_fixtures = false
23
+ self.use_transactional_fixtures = true #(ENV['AR_NO_TX_FIXTURES'] != "yes")
24
+
25
+ def create_fixtures(*table_names, &block)
26
+ Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block)
27
+ end
28
+
29
+ def assert_date_from_db(expected, actual, message = nil)
30
+ # SQL Server doesn't have a separate column type just for dates,
31
+ # so the time is in the string and incorrectly formatted
32
+ if current_adapter?(:SQLServerAdapter)
33
+ assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
34
+ elsif current_adapter?(:SybaseAdapter)
35
+ assert_equal expected.to_s, actual.to_date.to_s, message
36
+ else
37
+ assert_equal expected.to_s, actual.to_s, message
38
+ end
39
+ end
40
+
41
+ def assert_queries(num = 1)
42
+ ActiveRecord::Base.connection.class.class_eval do
43
+ self.query_count = 0
44
+ alias_method :execute, :execute_with_query_counting
45
+ end
46
+ yield
47
+ ensure
48
+ ActiveRecord::Base.connection.class.class_eval do
49
+ alias_method :execute, :execute_without_query_counting
50
+ end
51
+ assert_equal num, ActiveRecord::Base.connection.query_count, "#{ActiveRecord::Base.connection.query_count} instead of #{num} queries were executed."
52
+ end
53
+
54
+ def assert_no_queries(&block)
55
+ assert_queries(0, &block)
56
+ end
57
+ end
58
+
59
+ def current_adapter?(type)
60
+ ActiveRecord::ConnectionAdapters.const_defined?(type) &&
61
+ ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
62
+ end
63
+
64
+ ActiveRecord::Base.connection.class.class_eval do
65
+ cattr_accessor :query_count
66
+ alias_method :execute_without_query_counting, :execute
67
+ def execute_with_query_counting(sql, name = nil)
68
+ self.query_count += 1
69
+ execute_without_query_counting(sql, name)
70
+ end
71
+ end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ module NormalModule; end
4
+
5
+ class TestMagicMultiConnection < Test::Unit::TestCase
6
+
7
+ def setup
8
+ create_fixtures :people
9
+ end
10
+
11
+ def test_classes
12
+ assert_nothing_raised(Exception) { Person }
13
+ assert_equal('ActiveRecord::Base', Person.active_connection_name)
14
+ assert(normal_person = Person.find(1), "Cannot get Person instances")
15
+ assert_nothing_raised(Exception) { ContactRepository::Person }
16
+ assert_equal(Person, ContactRepository::Person.superclass)
17
+ assert_equal('ContactRepository::Person', ContactRepository::Person.name)
18
+ assert_equal('ContactRepository::Person', ContactRepository::Person.active_connection_name)
19
+ assert_equal(0, ContactRepository::Person.count)
20
+ end
21
+
22
+ def test_normal_modules_shouldnt_do_anything
23
+ assert_raise(NameError) { NormalModule::Person }
24
+
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ module A
4
+ module B
5
+ module C
6
+ class Z
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ class TestParentModule < Test::Unit::TestCase
13
+
14
+ def test_method
15
+ assert_equal(A::B::C, A::B::C::Z.parent_module)
16
+ assert_equal(Object, A.parent_module)
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ module Preexisting
4
+ class Person < ActiveRecord::Base
5
+ end
6
+
7
+ Foo = "foo" # just to ensure it doesn't do anything stupid with non-classes
8
+
9
+ class NotAciveRecord; end # similarly, this class should be ignored
10
+ end
11
+
12
+ module AnotherPre
13
+ end
14
+
15
+ class TestPreexistingModule < Test::Unit::TestCase
16
+
17
+ def setup
18
+ create_fixtures :people
19
+ end
20
+
21
+ def test_update_existing_classes
22
+ assert_equal("ActiveRecord::Base", Preexisting::Person.active_connection_name)
23
+ Preexisting.establish_connection :contact_repo
24
+ assert_equal("Preexisting::Person", Preexisting::Person.active_connection_name)
25
+ end
26
+
27
+ # Rails can dynamically load classes when requested
28
+ def test_update_on_load
29
+ AnotherPre.establish_connection :contact_repo
30
+ AnotherPre.class_eval <<-EOS
31
+ class Person < ActiveRecord::Base
32
+ def tester; end
33
+ end
34
+ EOS
35
+ assert(AnotherPre::Person.instance_methods.include?("tester"), "AnotherPre::Person should include #tester method")
36
+ assert_equal("AnotherPre::Person", AnotherPre::Person.name)
37
+ assert_equal("AnotherPre::Person", AnotherPre::Person.active_connection_name)
38
+ end
39
+ end
@@ -0,0 +1,340 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
6
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7
+ <title>
8
+ Magic Multi-Connections
9
+ </title>
10
+ <script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
11
+ <style>
12
+
13
+ </style>
14
+ <script type="text/javascript">
15
+ window.onload = function() {
16
+ settings = {
17
+ tl: { radius: 10 },
18
+ tr: { radius: 10 },
19
+ bl: { radius: 10 },
20
+ br: { radius: 10 },
21
+ antiAlias: true,
22
+ autoPad: true,
23
+ validTags: ["div"]
24
+ }
25
+ var versionBox = new curvyCorners(settings, document.getElementById("version"));
26
+ versionBox.applyCornersToAll();
27
+ }
28
+ </script>
29
+ </head>
30
+ <body>
31
+ <div id="main">
32
+ <p><a href="/">&#x21A9; More Magic</a></p>
33
+ <h1 class=primary>Magic Multi-Connections</h1>
34
+ <div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/magicmodels"; return false'>
35
+ Get Version
36
+ <a href="http://rubyforge.org/projects/magicmodels" class="numbers">1.0.0</a>
37
+ </div>
38
+ <h1>&#x2192; Ruby on Rails</h1>
39
+
40
+
41
+ <h1>&#x2192; ActiveRecords</h1>
42
+
43
+
44
+ <h2>What</h2>
45
+
46
+
47
+ <p>ActiveRecord models are allowed one connection to a database at a time, per class. Ruby on Rails sets up the default connection based on your database.yml configuration to automatically select <strong>development</strong>, <strong>test</strong> or <strong>production</strong>.</p>
48
+
49
+
50
+ <p>But, what if you want to access two or more databases &#8211; have 2+ connections open &#8211; at the same time. ActiveRecord requires that you subclass <code>ActiveRecord::Base</code>.</p>
51
+
52
+
53
+ <p>That prevents you doing migrations from one database to another. It prevents you using one set of model classes on two or more databases with the same schema.</p>
54
+
55
+
56
+ <p>Magic Multi-Connections allows you to write your models once, and use them for multiple database Rails databases at the same time. How? Using magical namespacing.</p>
57
+
58
+
59
+ <p><pre class="syntax"><span class="keyword">class </span><span class="class">Person</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">;</span> <span class="keyword">end</span>
60
+ <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">.</span><span class="ident">establish_connection</span> <span class="symbol">:production</span>
61
+ <span class="constant">Person</span><span class="punct">.</span><span class="ident">connection</span> <span class="comment"># =&gt; production</span>
62
+
63
+ <span class="keyword">module </span><span class="module">ContactRepository</span>
64
+ <span class="ident">establish_connection</span> <span class="symbol">:contact_repo</span>
65
+ <span class="keyword">end</span>
66
+ <span class="constant">ContactRepository</span><span class="punct">::</span><span class="constant">Person</span><span class="punct">.</span><span class="ident">connection</span> <span class="comment"># =&gt; contact_repo</span>
67
+
68
+ <span class="ident">old_person</span> <span class="punct">=</span> <span class="constant">ContactRepository</span><span class="punct">::</span><span class="constant">Person</span><span class="punct">.</span><span class="ident">find_by_email</span><span class="punct">(</span><span class="ident">email</span><span class="punct">)</span>
69
+ <span class="ident">person</span> <span class="punct">=</span> <span class="ident">old_person</span><span class="punct">.</span><span class="ident">create_as</span><span class="punct">(</span><span class="constant">Person</span><span class="punct">)</span>
70
+ </pre></p>
71
+
72
+
73
+ <p>You do not have to redefine your models for the multi-connection module <code>ContactRepository</code>, they are automatically picked up for you. Magically.</p>
74
+
75
+
76
+ <h2>Installing</h2>
77
+
78
+
79
+ <p><pre class="syntax"><span class="ident">sudo</span> <span class="ident">gem</span> <span class="ident">install</span> <span class="ident">magic_multi_connections</span></pre></p>
80
+
81
+
82
+ <p>Rails: Add the following to the bottom of your <code>environment.rb</code> file</p>
83
+
84
+
85
+ <p><pre class="syntax"><span class="ident">require</span> <span class="punct">'</span><span class="string">magic_multi_connections</span><span class="punct">'</span></pre></p>
86
+
87
+
88
+ <p>Ruby scripts: Add the following to the top of your script</p>
89
+
90
+
91
+ <p><pre class="syntax"><span class="ident">require</span> <span class="punct">'</span><span class="string">rubygems</span><span class="punct">'</span>
92
+ <span class="ident">require</span> <span class="punct">'</span><span class="string">magic_multi_connections</span><span class="punct">'</span></pre></p>
93
+
94
+
95
+ <h2>Demonstration with Rails</h2>
96
+
97
+
98
+ <p>A quick demonstration within Rails to provide a parallel &#8220;private&#8221; database for an application.</p>
99
+
100
+
101
+ <h3>1. Create rails app</h3>
102
+
103
+
104
+ <p>Using sqlite3 here, but use your preferred db:</p>
105
+
106
+
107
+ <pre>&gt; rails privacy -d sqlite3
108
+ &gt; cd privacy
109
+ &gt; ruby script/generate model Person
110
+ &gt; cp config/environments/development.rb config/environments/private.rb
111
+ </pre>
112
+
113
+ <p>The last line allows us to play with our <strong>private</strong> database within the console and rake tasks.</p>
114
+
115
+
116
+ <h3>2. Edit <strong>config/database.yml</strong> and add our private database:</h3>
117
+
118
+
119
+ <p>Add the following to the bottom of <strong>config/database.yml</strong></p>
120
+
121
+
122
+ <p><pre class="syntax">
123
+ <span class="key">private</span><span class="punct">:</span>
124
+ <span class="key">adapter</span><span class="punct">:</span> sqlite3
125
+ <span class="key">database</span><span class="punct">:</span> db/private.sqlite3
126
+ </pre></p>
127
+
128
+
129
+ <h3>3. Create a database schema</h3>
130
+
131
+
132
+ <p>Edit <strong>db/migrate/001_create_people.rb</strong></p>
133
+
134
+
135
+ <p><pre class="syntax"><span class="keyword">class </span><span class="class">CreatePeople</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Migration</span>
136
+ <span class="keyword">def </span><span class="method">self.up</span>
137
+ <span class="ident">create_table</span> <span class="symbol">:people</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">t</span><span class="punct">|</span>
138
+ <span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:name</span><span class="punct">,</span> <span class="symbol">:string</span>
139
+ <span class="keyword">end</span>
140
+ <span class="keyword">end</span>
141
+
142
+ <span class="keyword">def </span><span class="method">self.down</span>
143
+ <span class="ident">drop_table</span> <span class="symbol">:people</span>
144
+ <span class="keyword">end</span>
145
+ <span class="keyword">end</span>
146
+ </pre></p>
147
+
148
+
149
+ <p>From the command line, migrate this to our <strong>development</strong> and <strong>private</strong> databases:</p>
150
+
151
+
152
+ <pre>&gt; rake db:migrate
153
+ &gt; rake db:migrate RAILS_ENV=private</pre>
154
+
155
+ <h3>4. Add some data to databases</h3>
156
+
157
+
158
+ <p><pre class="syntax"><span class="punct">&gt;</span> <span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">console</span> <span class="ident">development</span>
159
+ <span class="punct">&gt;&gt;</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:name</span> <span class="punct">=&gt;</span> <span class="punct">'</span><span class="string">Nic</span><span class="punct">')</span>
160
+ <span class="punct">&gt;&gt;</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:name</span> <span class="punct">=&gt;</span> <span class="punct">'</span><span class="string">Banjo</span><span class="punct">')</span>
161
+ <span class="punct">&gt;&gt;</span> <span class="ident">exit</span>
162
+ <span class="punct">&gt;</span> <span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">console</span> <span class="ident">private</span>
163
+ <span class="punct">&gt;&gt;</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:name</span> <span class="punct">=&gt;</span> <span class="punct">'</span><span class="string">Super Magical Nic</span><span class="punct">')</span>
164
+ <span class="punct">&gt;&gt;</span> <span class="ident">exit</span></pre></p>
165
+
166
+
167
+ <p>Now it should be obvious which database our app is accessing.</p>
168
+
169
+
170
+ <h3>5. Update environment.rb</h3>
171
+
172
+
173
+ <p>Edit <strong>config/environment.rb</strong> to include the library and create the <strong>Private</strong> module.</p>
174
+
175
+
176
+ <p>Add the following to the end of the file.</p>
177
+
178
+
179
+ <p><pre class="syntax">
180
+ <span class="ident">require</span> <span class="punct">&quot;</span><span class="string">magic_multi_connections</span><span class="punct">&quot;</span>
181
+
182
+ <span class="keyword">module </span><span class="module">Private</span>
183
+ <span class="ident">establish_connection</span> <span class="symbol">:private</span>
184
+ <span class="keyword">end</span>
185
+ </pre></p>
186
+
187
+
188
+ <p>This tells the <strong>Private</strong> module that any model class that is requested will be assigned a connection to the <strong>private</strong> database, as defined in the <strong>config/database.yml</strong> specification.</p>
189
+
190
+
191
+ <h3>6. Setup a controller</h3>
192
+
193
+
194
+ <p>Create a <strong>people</strong> controller with a <strong>index</strong> action</p>
195
+
196
+
197
+ <pre>&gt; ruby script/generate controller people index</pre>
198
+
199
+ <p>Edit your controller <strong>app/controllers/people_controller.rb</strong></p>
200
+
201
+
202
+ <p><pre class="syntax"><span class="keyword">class </span><span class="class">PeopleController</span> <span class="punct">&lt;</span> <span class="constant">ApplicationController</span>
203
+
204
+ <span class="ident">before_filter</span> <span class="symbol">:check_private</span>
205
+
206
+ <span class="keyword">def </span><span class="method">index</span>
207
+ <span class="attribute">@people</span> <span class="punct">=</span> <span class="attribute">@mod</span><span class="punct">::</span><span class="constant">Person</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:all</span><span class="punct">)</span>
208
+ <span class="keyword">end</span>
209
+
210
+ <span class="ident">private</span>
211
+ <span class="keyword">def </span><span class="method">check_private</span>
212
+ <span class="attribute">@mod</span> <span class="punct">=</span> <span class="ident">params</span><span class="punct">[</span><span class="symbol">:private</span><span class="punct">]</span> <span class="punct">?</span> <span class="constant">Private</span> <span class="punct">:</span> <span class="constant">Object</span>
213
+ <span class="keyword">end</span>
214
+ <span class="keyword">end</span>
215
+ </pre></p>
216
+
217
+
218
+ <p>The <strong>check_private</strong> action is a hack to demonstrate one method for selecting the database. In reality, a stupid one for hiding a &#8220;private&#8221; database, but you get the point.</p>
219
+
220
+
221
+ <p>After <strong>check_private</strong> is called, <strong>@mod</strong> is either the <strong>Object</strong> (default) module or the <strong>Private</strong> module. The <strong>Person</strong> class is accessible through either of them.</p>
222
+
223
+
224
+ <p>Yes, <code>@mod::Person</code> is uglier than just <code>Person</code>. Sorry.</p>
225
+
226
+
227
+ <h3>7. Setup the index.rhtml view</h3>
228
+
229
+
230
+ <p>Edit <strong>app/views/people/index.rhtml</strong></p>
231
+
232
+
233
+ <p><pre class="syntax">&lt;h1&gt;&lt;%= @mod::Person %&gt;&lt;/h1&gt;
234
+ &lt;h2&gt;&lt;%= @mod::Person.active_connection_name %&gt;&lt;/h2&gt;
235
+
236
+ &lt;ol&gt;
237
+ &lt;% @people.each do |person| -%&gt;
238
+ &lt;li&gt;&lt;%= &quot;#{person.name} - #{person.class}&quot; %&gt;&lt;/li&gt;
239
+ &lt;% end -%&gt;
240
+ &lt;/ol&gt;
241
+ </pre></p>
242
+
243
+
244
+ <h3>8. Test our multi-connection Rails app</h3>
245
+
246
+
247
+ <p>Launch the app</p>
248
+
249
+
250
+ <pre>&gt; mongrel_rails start</pre>
251
+
252
+ <p>In your browser, go to <a href="http://localhost:3000/people">http://localhost:3000/people</a> and see the list of people: Nic and Banjo.</p>
253
+
254
+
255
+ <p>Now, to see the private database, go to <a href="http://localhost:3000/people?private=1">http://localhost:3000/people?private=1</a> and see the private list. Its so private, I won&#8217;t show it here.</p>
256
+
257
+
258
+ <p>Note: you may need to refresh the private url to see the results. Perhaps Rails is caching the <span class="caps">SQL</span> even though its a different connection to a different database. If you know what&#8217;s happening here, please <a href="mailto:drnicwilliams@gmail.com?subject=MMC+caching+problem">email me</a> or the <a href="mailto:magicmodels@googlegroups.com?subject=MMC+caching+problem">forum</a>. Thanks.</p>
259
+
260
+
261
+ <h3>9. End</h3>
262
+
263
+
264
+ <p>There ends our example of a Rails application using one model class to access multiple databases cleanly.</p>
265
+
266
+
267
+ <h2>Pre-existing modules</h2>
268
+
269
+
270
+ <p>In Rails, model files are placed in the <strong>app/models</strong> folder. If you place them in a subfolder, say <strong>app/models/admin</strong>, then those model classes are access via module namespaces.</p>
271
+
272
+
273
+ <p>So, <strong>app/models/admin/page.rb</strong> represents the <code>Admin::Page</code> class.</p>
274
+
275
+
276
+ <p>Magic Multi-Connections works for these model classes as well.</p>
277
+
278
+
279
+ <p><pre class="syntax"><span class="constant">Admin</span><span class="punct">.</span><span class="ident">establish_connection</span> <span class="symbol">:admin_dev</span>
280
+ <span class="constant">Admin</span><span class="punct">::</span><span class="constant">Page</span><span class="punct">.</span><span class="ident">active_connection_name</span> <span class="comment"># =&gt; &quot;Admin::Page&quot;</span>
281
+ </pre></p>
282
+
283
+
284
+ <h2>Other tricks</h2>
285
+
286
+
287
+ <p>The Magic Multi-Connections includes <code>diff</code> and <code>diff?</code> methods extracted from the <a href="http://tfletcher.com/svn/rails-plugins/riff/README">Riff plugin</a> by <a href="http://tfletcher.com/">Tim Fletcher</a>, so that <code>id</code> values are ignored when comparing objects from different connections/namespaces.</p>
288
+
289
+
290
+ <p><pre class="syntax"><span class="comment"># Detect differences between two activerecords, e.g.</span>
291
+ <span class="ident">new_person</span> <span class="punct">=</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">find_by_username</span><span class="punct">('</span><span class="string">drnic</span><span class="punct">')</span>
292
+ <span class="ident">old_person</span> <span class="punct">=</span> <span class="constant">ContactRepository</span><span class="punct">::</span><span class="constant">Person</span><span class="punct">.</span><span class="ident">find_by_username</span><span class="punct">('</span><span class="string">drnic</span><span class="punct">')</span>
293
+
294
+ <span class="ident">new_person</span><span class="punct">.</span><span class="ident">diff?</span><span class="punct">(</span><span class="ident">old_person</span><span class="punct">)</span> <span class="comment"># =&gt; true</span>
295
+
296
+ <span class="ident">new_person</span><span class="punct">.</span><span class="ident">diff</span><span class="punct">(</span><span class="ident">old_person</span><span class="punct">)</span> <span class="comment"># =&gt; { :name =&gt; ['Dr Nic', 'Nic'], :age =&gt; [32, 20] }</span>
297
+ </pre></p>
298
+
299
+
300
+ <h2>Dr Nic&#8217;s Blog</h2>
301
+
302
+
303
+ <p><a href="http://www.drnicwilliams.com">http://www.drnicwilliams.com</a> &#8211; for future announcements and
304
+ other stories and things.</p>
305
+
306
+
307
+ <h2>Forum</h2>
308
+
309
+
310
+ <p>Discussion about the Magic Multi-Connections is on the Magic Models forum:</p>
311
+
312
+
313
+ <p><a href="http://groups.google.com/group/magicmodels">http://groups.google.com/group/magicmodels</a></p>
314
+
315
+
316
+ <h2>Licence</h2>
317
+
318
+
319
+ <p>This code is free to use under the terms of the <span class="caps">MIT</span> licence.</p>
320
+
321
+
322
+ <h2>Contact</h2>
323
+
324
+
325
+ <p>Comments are welcome. Send an email to <a href="mailto:drnicwilliams@gmail.com">Dr Nic Williams</a>.</p>
326
+ <p class="coda">
327
+ <a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 9th April 2007<br>
328
+ Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
329
+ </p>
330
+ </div>
331
+
332
+ <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
333
+ </script>
334
+ <script type="text/javascript">
335
+ _uacct = "UA-567811-2";
336
+ urchinTracker();
337
+ </script>
338
+
339
+ </body>
340
+ </html>