primer 0.1.0 → 0.2.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.
Files changed (72) hide show
  1. data/History.txt +17 -0
  2. data/README.rdoc +74 -35
  3. data/example/README.rdoc +33 -23
  4. data/example/application.rb +2 -2
  5. data/example/console.rb +133 -0
  6. data/example/db/blog.sqlite3 +0 -0
  7. data/example/{models → db}/connection.rb +0 -0
  8. data/example/environment.rb +17 -4
  9. data/example/models/comment.rb +5 -0
  10. data/example/models/post.rb +5 -0
  11. data/example/script/setup_database.rb +8 -2
  12. data/example/views/comments.erb +6 -0
  13. data/example/views/layout.erb +1 -1
  14. data/example/views/show.erb +5 -1
  15. data/lib/primer/bus/amqp.rb +7 -5
  16. data/lib/primer/bus/amqp.rbc +1045 -0
  17. data/lib/primer/bus/memory.rbc +344 -0
  18. data/lib/primer/bus.rb +3 -2
  19. data/lib/primer/bus.rbc +872 -0
  20. data/lib/primer/cache/memory.rbc +1443 -0
  21. data/lib/primer/cache/redis.rb +2 -2
  22. data/lib/primer/cache/redis.rbc +1643 -0
  23. data/lib/primer/cache.rb +2 -16
  24. data/lib/primer/cache.rbc +1502 -0
  25. data/lib/primer/enabler.rbc +414 -0
  26. data/lib/primer/helpers.rbc +1438 -0
  27. data/lib/primer/lazyness.rb +63 -0
  28. data/lib/primer/lazyness.rbc +1442 -0
  29. data/lib/primer/real_time.rbc +1999 -0
  30. data/lib/primer/route_set.rb +1 -0
  31. data/lib/primer/route_set.rbc +1475 -0
  32. data/lib/primer/watcher/active_record_macros.rb +13 -3
  33. data/lib/primer/watcher/active_record_macros.rbc +1796 -0
  34. data/lib/primer/watcher/macros.rb +11 -14
  35. data/lib/primer/watcher/macros.rbc +1628 -0
  36. data/lib/primer/watcher.rb +2 -7
  37. data/lib/primer/watcher.rbc +1416 -0
  38. data/lib/primer/worker/active_record_agent.rb +30 -33
  39. data/lib/primer/worker/active_record_agent.rbc +2840 -0
  40. data/lib/primer/worker/changes_agent.rb +20 -0
  41. data/lib/primer/worker/changes_agent.rbc +578 -0
  42. data/lib/primer/worker.rb +20 -3
  43. data/lib/primer/worker.rbc +1254 -0
  44. data/lib/primer.rb +10 -2
  45. data/lib/primer.rbc +944 -0
  46. data/spec/db/test.sqlite3 +0 -0
  47. data/spec/models/artist.rb +3 -2
  48. data/spec/models/artist.rbc +288 -0
  49. data/spec/models/blog_post.rb +3 -1
  50. data/spec/models/blog_post.rbc +181 -0
  51. data/spec/models/calendar.rbc +209 -0
  52. data/spec/models/concert.rbc +211 -0
  53. data/spec/models/performance.rbc +177 -0
  54. data/spec/models/person.rb +1 -0
  55. data/spec/models/person.rbc +306 -0
  56. data/spec/models/watchable.rbc +363 -0
  57. data/spec/primer/bus_spec.rbc +940 -0
  58. data/spec/primer/cache_spec.rb +3 -3
  59. data/spec/primer/cache_spec.rbc +8535 -0
  60. data/spec/primer/helpers/erb_spec.rb +14 -14
  61. data/spec/primer/helpers/erb_spec.rbc +2485 -0
  62. data/spec/primer/lazyness_spec.rb +61 -0
  63. data/spec/primer/lazyness_spec.rbc +1408 -0
  64. data/spec/primer/watcher/active_record_spec.rb +15 -15
  65. data/spec/primer/watcher/active_record_spec.rbc +5202 -0
  66. data/spec/primer/watcher_spec.rbc +2645 -0
  67. data/spec/schema.rbc +775 -0
  68. data/spec/spec_helper.rb +3 -0
  69. data/spec/spec_helper.rbc +1193 -0
  70. data/spec/templates/page.erb +0 -1
  71. metadata +77 -70
  72. data/example/models/blog_post.rb +0 -4
data/History.txt ADDED
@@ -0,0 +1,17 @@
1
+ === 0.2.0 / 2011-07-14
2
+
3
+ * New Lazyness module to avoid ActiveRecord database calls when not needed
4
+ * Handle namespaced ActiveRecord classes when traversing associations
5
+ * Allow Watcher to be added to ActiveRecord classes before associations
6
+ * Ensure compatibility with latest Sinatra router
7
+ * Use AMQP::Channel instead of MQ when using newer amqp gems
8
+ * Add EventMachine-compatible console for platforms with blocking IRBs
9
+ * Move cache updating logic into Worker::ChangesAgent
10
+
11
+
12
+ === 0.1.0 / 2010-11-25
13
+
14
+ * Initial release. Includes caching, method logging, cache expiry and
15
+ regeneration, offline workers, real-time page updates and ActiveRecord
16
+ integration for updating object graphs
17
+
data/README.rdoc CHANGED
@@ -1,18 +1,18 @@
1
1
  = Primer
2
2
 
3
3
  This is an experiment to bring automatic cache expiry and regeneration to Rails.
4
- At Songkick[http://www.songkick.com], we have a ton of code that deals with caches
5
- and denormalization and messages and offline processing and it's hard to maintain.
6
- I want to get rid of it. All of it.
4
+ At Songkick[http://www.songkick.com], we have a ton of code that deals with
5
+ caches and denormalization and messages and offline processing and it's hard to
6
+ maintain. I want to get rid of it. All of it.
7
7
 
8
8
 
9
9
  == What?
10
10
 
11
11
  Inspired by LunaScript[http://asana.com/luna] and Fun[http://marcuswest.in/read/fun-intro/],
12
12
  I figured Ruby could figure out which values a computation uses, and use that to
13
- expire caches for you without having to write any expiry code. This turns out to be
14
- possible, at least for typical ActiveRecord usage, and Primer includes an engine
15
- for that.
13
+ expire caches for you without having to write any expiry code. This turns out to
14
+ be possible, at least for typical ActiveRecord usage, and Primer includes an
15
+ engine for that.
16
16
 
17
17
  Primer currently lets you do the following:
18
18
 
@@ -22,14 +22,14 @@ Primer currently lets you do the following:
22
22
  * Declare how caches should be recalculated for eager cache population
23
23
  * Update pages in real time when their data is updated
24
24
 
25
- It does all this without you having to write a single cache sweeper. You
26
- just declare how to render your site, Primer does the rest.
25
+ It does all this without you having to write a single cache sweeper. You just
26
+ declare how to render your site, Primer does the rest.
27
27
 
28
28
 
29
29
  == Enough waffle, show me the code!
30
30
 
31
31
  The following is the minimal, 'hello world' use case. Get your ActiveRecord
32
- model, put a mixn in it (<em>after</em> declaring the model's associations):
32
+ model, put a mixn in it:
33
33
 
34
34
  class BlogPost < ActiveRecord::Base
35
35
  include Primer::Watcher
@@ -62,30 +62,29 @@ output is cached, the block will not be called again. The cache is invalidated
62
62
  when (and only when) the title of <tt>@post</tt> changes; Primer figures this
63
63
  out and you don't need to write any cache sweeping code.
64
64
 
65
- Finally you need to bind the cache to the event bus, unless you want to run the
66
- cache monitoring work in a background process (see below):
65
+ Finally you need to run the cache agent, unless you want to run the cache
66
+ monitoring work in a background process (see below):
67
67
 
68
- Primer.cache.bind_to_bus
68
+ Primer::Worker::ChangesAgent.run!
69
69
 
70
70
  # If you're using ActiveRecord
71
- Primer::Worker::ActiveRecordAgent.bind_to_bus
71
+ Primer::Worker::ActiveRecordAgent.run!
72
72
 
73
73
 
74
74
  === Declaring cache generators
75
75
 
76
76
  You may have noticed that Primer forces the use of path-style keys for your
77
- cache. Instead of wrapping code you want to memoize in a block, you can
78
- declare how to calculate it elsewhere and use a router to map cache keys
79
- to calculations. For example we could rewrite our post title example like
80
- this:
77
+ cache. Instead of wrapping code you want to memoize in a block, you can declare
78
+ how to calculate it elsewhere and use a router to map cache keys to calculations.
79
+ For example we could rewrite our post title example like this:
81
80
 
82
81
  # views/posts/show.html.erb
83
82
  # note '=' sign here, not used with block form
84
83
 
85
84
  <%= primer "/posts/#{@post.id}/title" %>
86
85
 
87
- Then you can declare how to calculate this in a router attached to your
88
- cache object:
86
+ Then you can declare how to calculate this in a router attached to your cache
87
+ object:
89
88
 
90
89
  Primer.cache.routes do
91
90
  get "/posts/:id/title" do
@@ -96,19 +95,59 @@ cache object:
96
95
 
97
96
  The advantage of this is that the cache now has a way to generate cache values
98
97
  outside of your rendering stack, meaning that instead of just invalidating the
99
- cache it can actually calculate the new value so the cache is always ready
100
- for incoming requests.
98
+ cache it can actually calculate the new value so the cache is always ready for
99
+ incoming requests.
101
100
 
102
- It also means you can generate cache content offline; running the following
103
- will generate the cache of the first post's title:
101
+ It also means you can generate cache content offline; running the following will
102
+ generate the cache of the first post's title:
104
103
 
105
104
  Primer.cache.compute("/posts/1/title")
106
105
 
107
106
 
107
+ === Lazy loading
108
+
109
+ A common problem when caching view fragments is not knowing whether you need to
110
+ load database objects in the controller. If the parts of the view that use that
111
+ object are already cached, there's no point loading the object since it won't be
112
+ needed to render the page.
113
+
114
+ Rails has some lazy-loading capability built in, for example if you have a model
115
+ that says <tt>BlogPost.has_many :comments</tt>, a call to <tt>blog_post.comments</tt>
116
+ won't actually load the comments until you call <tt>each</tt> on that collection
117
+ to read the data. Primer introduces the same idea for all database calls.
118
+
119
+ If you <tt>include Primer::Lazyness</tt> in a model, then <tt>Model.find(id)</tt>
120
+ will not call the database. It just returns an object with the ID you asked for,
121
+ and won't actually load the model from the database until you try to access
122
+ other properties that we cannot infer from the <tt>find()</tt> call.
123
+
124
+ For example, this makes it easy to load a model in the controller and use its ID
125
+ to key cache fragments, safe in the knowledge that no unnecessary database calls
126
+ will be made.
127
+
128
+ BlogPost < ActiveRecord::Base
129
+ include Primer::Lazyness
130
+ end
131
+
132
+ # Does not call the database
133
+ post = BlogPost.find(1)
134
+
135
+ # Does not call the database unless the title is not cached
136
+ primer "/posts/#{post.id}/title" do
137
+ post.title
138
+ end
139
+
140
+ Primer also lazy-loads on other properties, for example if I call
141
+ <tt>BlogPost.find_by_title("Hello World")</tt> then Primer will create an object
142
+ whose <tt>title</tt> is <tt>"Hello World"</tt> without calling the database, but
143
+ then if I ask for the object's <tt>id</tt> or other properties the real model is
144
+ loaded.
145
+
146
+
108
147
  === Throttling
109
148
 
110
- Let's say you have a cache value that depends on many little bits of model
111
- data, a common situation in web front-ends. For example:
149
+ Let's say you have a cache value that depends on many little bits of model data,
150
+ a common situation in web front-ends. For example:
112
151
 
113
152
  Primer.cache.routes do
114
153
  get "/posts/:id/summary" do
@@ -158,15 +197,15 @@ To make a background worker, you just need a file like this:
158
197
  Primer.worker!
159
198
 
160
199
  Running <tt>ruby worker.rb</tt> will start a process in the shell that listens
161
- for change notifications and updates the cache for you. You can start as many
162
- of these as you like to spread the load out.
200
+ for change notifications and updates the cache for you. You can start as many of
201
+ these as you like to spread the load out.
163
202
 
164
203
 
165
204
  === Real-time page updates
166
205
 
167
- If you want to be properly web-scale, you'll need to be updating your pages
168
- in real time as your data changes. Primer lets you update any fragment generated
169
- by a block-less <tt>primer</tt> call in your view automatically.
206
+ If you want to be properly web-scale, you'll need to be updating your pages in
207
+ real time as your data changes. Primer lets you update any fragment generated by
208
+ a block-less <tt>primer</tt> call in your view automatically.
170
209
 
171
210
  All you need to do is place some middleware in your Rack config:
172
211
 
@@ -181,10 +220,10 @@ Add the client-side script to your templates (this must be in the <tt>HEAD</tt>)
181
220
 
182
221
  <script type="text/javascript" src="/primer.js"></script>
183
222
 
184
- Then configure it wherever your data model gets used to tell it you want to
185
- use real-time updates and where the messaging server is. You should also set
186
- a password - this will stop third parties being able to publish to the message
187
- bus and inject arbitrary HTML into your pages.
223
+ Then configure it wherever your data model gets used to tell it you want to use
224
+ real-time updates and where the messaging server is. You should also set a
225
+ password - this will stop third parties being able to publish to the message bus
226
+ and inject arbitrary HTML into your pages.
188
227
 
189
228
  Primer.real_time = true
190
229
  Primer::RealTime.bayeux_server = 'http://localhost:9292'
@@ -208,6 +247,6 @@ welcome, pull requests considered, bug reports likely to gather dust.
208
247
 
209
248
  == License
210
249
 
211
- Copyright (c) 2010 Songkick.com, James Coglan. Named by the inimitable
250
+ Copyright (c) 2010-2011 Songkick.com, James Coglan. Named by the inimitable
212
251
  grillpanda[http://github.com/grillpanda]. Released under the MIT license.
213
252
 
data/example/README.rdoc CHANGED
@@ -4,17 +4,19 @@ This is a simple app that demonstrates how you can use Primer. It's based on
4
4
  ActiveRecord for the model and Sinatra for the front-end. To start it up,
5
5
  install some gems, build the database then use Rack:
6
6
 
7
- gem install redis amqp faye sinatra activerecord sqlite3-ruby
7
+ bundle install
8
8
  ruby script/setup_database.rb
9
+ redis-server
10
+ rabbitmq-server
9
11
  rackup -s thin -E production config.ru
10
12
 
11
13
 
12
14
  === models
13
15
 
14
16
  Let's take a look at the internals. In the +models+ directory, you'll see a
15
- simple BlogPost model:
17
+ simple blog post model:
16
18
 
17
- class BlogPost < ActiveRecord::Base
19
+ class Post < ActiveRecord::Base
18
20
  include Primer::Watcher
19
21
  end
20
22
 
@@ -23,47 +25,55 @@ That's all we need for Primer to monitor how our data changes.
23
25
 
24
26
  === public, views
25
27
 
26
- The +public+ and +views+ directories are used by the Sinatra app, and should
27
- be pretty self-explanatory. Note which bits of the views are cached, and which
28
+ The +public+ and +views+ directories are used by the Sinatra app, and should be
29
+ pretty self-explanatory. Note which bits of the views are cached, and which
28
30
  parts don't use blocks - we set these keys up in <tt>environment.rb</tt>.
29
31
 
30
32
 
31
33
  === application.rb
32
34
 
33
- The <tt>application.rb</tt> file is a simple Sinatra app - it loads the
34
- model from <tt>environment.rb</tt> and just sets up a couple of pages.
35
+ The <tt>application.rb</tt> file is a simple Sinatra app - it loads the model
36
+ from <tt>environment.rb</tt> and just sets up a couple of pages.
35
37
 
36
38
 
37
39
  === environment.rb
38
40
 
39
- The meat of the system is in <tt>environment.rb</tt>. This file loads the
40
- gems we need, loads our models, and configures Primer. See that file for more
41
- details, but note that it uses an AMQP bus - this means we can compute cache
42
- changes outside the app process. You'll need to start <tt>worker.rb</tt> so
43
- that your changes get processed.
41
+ The meat of the system is in <tt>environment.rb</tt>. This file loads the gems
42
+ we need, loads our models, and configures Primer. See that file for more details,
43
+ but note that it uses an AMQP bus - this means we can compute cache changes
44
+ outside the app process. You'll need to start <tt>worker.rb</tt> so that your
45
+ changes get processed.
44
46
 
45
47
 
46
48
  === worker.rb
47
49
 
48
50
  This file just loads <tt>environment.rb</tt>, then tells Primer to start a
49
- worker. The call to <tt>Primer.worker!</tt> is blocking, and makes Primer
50
- pick up messages from the AMQP bus and process them. You can start as many
51
- of these as you like to distribute the work. Just run:
51
+ worker. The call to <tt>Primer.worker!</tt> is blocking, and makes Primer pick
52
+ up messages from the AMQP bus and process them. You can start as many of these
53
+ as you like to distribute the work. Just run:
52
54
 
53
55
  ruby worker.rb
54
56
 
55
57
  If you don't run any of these, messages will build up in AMQP until you start
56
- a worker. Each data change message goes to only one worker process so you
57
- won't duplicate any work.
58
+ a worker. Each data change message goes to only one worker process so you won't
59
+ duplicate any work.
58
60
 
59
61
 
60
- === console
62
+ === console.rb
61
63
 
62
- You can interact with the model by starting IRB with the app's environment:
64
+ You can interact with the model by starting IRB with the app's environment.
65
+ Because EventMachine does not play nice with IRB, you may need to use the custom
66
+ console to script the application:
63
67
 
64
- irb -r ./environment.rb
68
+ # in one shell:
69
+ ruby console.rb server
70
+
71
+ # in another shell
72
+ ruby console.rb client
73
+ >> require './environment'
74
+ >> etc.
65
75
 
66
- Go and create posts and change their data to see how the front-end reacts.
67
- To get the real-time updates to work you must have a worker running to
68
- update caches; this is not done by the web app process.
76
+ Go and create posts and change their data to see how the front-end reacts. To
77
+ get the real-time updates to work you must have a worker running to update
78
+ caches; this is not done by the web app process.
69
79
 
@@ -14,12 +14,12 @@ class Application < Sinatra::Base
14
14
  helpers { include Primer::Helpers::ERB }
15
15
 
16
16
  get '/' do
17
- @posts = BlogPost.all
17
+ @posts = Post.all
18
18
  erb :index
19
19
  end
20
20
 
21
21
  get '/posts/:id' do
22
- @post = BlogPost.find(params[:id])
22
+ @post = Post.find(params[:id])
23
23
  erb :show
24
24
  end
25
25
  end
@@ -0,0 +1,133 @@
1
+ # EventMachine-aware console by Aman Gupta
2
+ # https://gist.github.com/145471
3
+
4
+ require 'rubygems'
5
+ require 'eventmachine'
6
+ require 'stringio'
7
+
8
+ module Console
9
+ PROMPT = "\n>> ".freeze
10
+
11
+ def post_init
12
+ send_data PROMPT
13
+ send_data "\0"
14
+ end
15
+
16
+ def receive_data data
17
+ return close_connection if data =~ /exit|quit/
18
+
19
+ begin
20
+ @ret, @out, $stdout = :exception, $stdout, StringIO.new
21
+ @ret = eval(data, scope, '(rirb)')
22
+ rescue StandardError, ScriptError, Exception, SyntaxError
23
+ $! = RuntimeError.new("unknown exception raised") unless $!
24
+ print $!.class, ": ", $!, "\n"
25
+
26
+ trace = []
27
+ $!.backtrace.each do |line|
28
+ trace << "\tfrom #{line}"
29
+ break if line =~ /\(rirb\)/
30
+ end
31
+
32
+ puts trace
33
+ ensure
34
+ $stdout, @out = @out, $stdout
35
+ @out.rewind
36
+ @out = @out.read
37
+ end
38
+
39
+ send_data @out unless @out.empty?
40
+ send_data "=> #{@ret.inspect}" unless @ret == :exception
41
+ send_data "\0\n>> \0"
42
+ end
43
+
44
+ # def send_data data
45
+ # p ['server send', data]
46
+ # super
47
+ # end
48
+
49
+ def scope
50
+ @scope ||= instance_eval{ binding }
51
+ end
52
+
53
+ def handle_error
54
+ $! = RuntimeError.new("unknown exception raised") unless $!
55
+ print $!.class, ": ", $!, "\n"
56
+
57
+ trace = []
58
+ $!.backtrace.each do |line|
59
+ trace << "\\tfrom \#{line}"
60
+ break if line =~ /\(rirb\)/
61
+ end
62
+
63
+ puts trace
64
+ end
65
+
66
+ def self.start port = 7331
67
+ EM.run{
68
+ @server ||= EM.start_server '127.0.0.1', port, self
69
+ }
70
+ end
71
+
72
+ def self.stop
73
+ @server.close_connection if @server
74
+ @server = nil
75
+ end
76
+ end
77
+
78
+ module RIRB
79
+ def connection_completed
80
+ p 'connected to console'
81
+ end
82
+
83
+ def receive_data data
84
+ # p ['receive', data]
85
+ (@buffer ||= BufferedTokenizer.new("\0")).extract(data).each do |d|
86
+ process(d)
87
+ end
88
+ end
89
+
90
+ def process data
91
+ if data.strip == '>>'
92
+ while l = Readline.readline('>> ')
93
+ unless l.nil? or l.strip.empty?
94
+ Readline::HISTORY.push(l)
95
+ send_data l
96
+ break
97
+ end
98
+ end
99
+ else
100
+ puts data
101
+ end
102
+ end
103
+
104
+ def unbind
105
+ p 'disconnected'
106
+ EM.stop_event_loop
107
+ end
108
+
109
+ def self.connect host = 'localhost', port = 7331
110
+ require 'readline'
111
+ EM.run{
112
+ trap('INT'){ exit }
113
+ @connection.close_connection if @connection
114
+ @connection = EM.connect host, port, self do |c|
115
+ c.instance_eval{ @host, @port = host, port }
116
+ end
117
+ }
118
+ end
119
+ attr_reader :host, :port
120
+ end
121
+
122
+ if __FILE__ == $0
123
+ EM.run{
124
+ if ARGV[0] == 'server'
125
+ Console.start
126
+ elsif ARGV[0] == 'client'
127
+ RIRB.connect
128
+ else
129
+ puts "#{$0} <server|client>"
130
+ EM.stop
131
+ end
132
+ }
133
+ end
Binary file
File without changes
@@ -6,13 +6,19 @@ require 'active_record'
6
6
  require dir + '/../lib/primer'
7
7
 
8
8
  # Load database config and models
9
- require dir + '/models/connection'
10
- require dir + '/models/blog_post'
9
+ require dir + '/db/connection'
10
+ require dir + '/models/post'
11
+ require dir + '/models/comment'
11
12
 
12
13
  # Configure Primer with a Redis cache and AMQP bus
13
14
  Primer.cache = Primer::Cache::Redis.new
14
15
  Primer.bus = Primer::Bus::AMQP.new(:queue => 'blog_changes')
15
16
 
17
+ # If you don't want to use AMQP, delete the above bus config
18
+ # and add these lines to run invalidation work in this process:
19
+ # Primer::Worker::ActiveRecordAgent.run!
20
+ # Primer::Worker::ChangesAgent.run!
21
+
16
22
  # Enable real-time page updates
17
23
  Primer.real_time = true
18
24
  Primer::RealTime.bayeux_server = 'http://0.0.0.0:9292'
@@ -21,11 +27,18 @@ Primer::RealTime.password = 'omg_rofl_scale'
21
27
  # Set up cache generation routes
22
28
  Primer.cache.routes do
23
29
  get '/posts/:id/date' do
24
- BlogPost.find(params[:id]).created_at.strftime('%A %e %B %Y')
30
+ post = Post.find(params[:id])
31
+ post.created_at.strftime('%A %e %B %Y')
25
32
  end
26
33
 
27
34
  get '/posts/:id/title' do
28
- BlogPost.find(params[:id]).title.upcase
35
+ post = Post.find(params[:id])
36
+ post.title.upcase
37
+ end
38
+
39
+ get '/posts/:id/comments' do
40
+ @post = Post.find(params[:id])
41
+ ERB.new(File.read(dir + '/views/comments.erb')).result(binding)
29
42
  end
30
43
  end
31
44
 
@@ -0,0 +1,5 @@
1
+ class Comment < ActiveRecord::Base
2
+ include Primer::Watcher
3
+ belongs_to :post
4
+ end
5
+
@@ -0,0 +1,5 @@
1
+ class Post < ActiveRecord::Base
2
+ include Primer::Watcher
3
+ has_many :comments
4
+ end
5
+
@@ -1,11 +1,17 @@
1
- require File.expand_path(File.dirname(__FILE__)) + '/../models/connection'
1
+ require File.expand_path(File.dirname(__FILE__)) + '/../db/connection'
2
2
 
3
3
  ActiveRecord::Schema.define do |version|
4
- create_table :blog_posts, :force => true do |t|
4
+ create_table :posts, :force => true do |t|
5
5
  t.timestamps
6
6
  t.string :title
7
7
  t.text :body
8
8
  t.string :author
9
9
  end
10
+
11
+ create_table :comments, :force => true do |t|
12
+ t.timestamps
13
+ t.belongs_to :post
14
+ t.text :body
15
+ end
10
16
  end
11
17
 
@@ -0,0 +1,6 @@
1
+ <h4>Comments</h4>
2
+
3
+ <% @post.comments.each do |comment| %>
4
+ <h5><%= comment.created_at.strftime('%A %e %B %Y') %></h5>
5
+ <p><%= comment.body %></p>
6
+ <% end %>
@@ -18,7 +18,7 @@
18
18
  </div></div>
19
19
 
20
20
  <div class="footer"><div class="sub">
21
- <p>Copyright &copy; 2010 That&rsquo;s What She Said</p>
21
+ <p>Copyright &copy; 2011 I Can&rsquo;t Even Words</p>
22
22
  </div></div>
23
23
 
24
24
  </body>
@@ -1,7 +1,11 @@
1
1
  <h3><%= @post.title.upcase %></h3>
2
- <h4>By <%= @post.author %> &mdash; <%= primer "/posts/#{@post.id}/date", :span %></h4>
2
+ <h4>
3
+ By <%= @post.author %> &mdash;
4
+ <%= primer "/posts/#{@post.id}/date", :span %>
5
+ </h4>
3
6
 
4
7
  <% primer "/posts/#{@post.id}/body" do %>
5
8
  <p><%= @post.body %></p>
6
9
  <% end %>
7
10
 
11
+ <%= primer "/posts/#{@post.id}/comments" %>
@@ -1,4 +1,5 @@
1
- require 'mq'
1
+ require 'amqp'
2
+ require 'mq' unless defined? AMQP::Channel
2
3
 
3
4
  module Primer
4
5
  class Bus
@@ -10,8 +11,8 @@ module Primer
10
11
  end
11
12
 
12
13
  def publish(topic, message)
13
- tuple = [topic, message]
14
- queue.publish(YAML.dump(tuple))
14
+ tuple = [topic.to_s, message]
15
+ queue.publish(Primer.serialize(tuple))
15
16
  end
16
17
 
17
18
  def subscribe(*args, &block)
@@ -25,13 +26,14 @@ module Primer
25
26
  Faye.ensure_reactor_running!
26
27
  return @queue if defined?(@queue)
27
28
  raise "I need a queue name!" unless @config[:queue]
28
- @queue = MQ.new.queue(@config[:queue])
29
+ amqp_klass = defined?(MQ) ? MQ : ::AMQP::Channel
30
+ @queue = amqp_klass.new.queue(@config[:queue])
29
31
  end
30
32
 
31
33
  def bind
32
34
  return if @bound
33
35
  queue.subscribe do |message|
34
- data = YAML.load(message)
36
+ data = Primer.deserialize(message)
35
37
  distribute(*data)
36
38
  end
37
39
  @bound = true