primer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +17 -0
- data/README.rdoc +74 -35
- data/example/README.rdoc +33 -23
- data/example/application.rb +2 -2
- data/example/console.rb +133 -0
- data/example/db/blog.sqlite3 +0 -0
- data/example/{models → db}/connection.rb +0 -0
- data/example/environment.rb +17 -4
- data/example/models/comment.rb +5 -0
- data/example/models/post.rb +5 -0
- data/example/script/setup_database.rb +8 -2
- data/example/views/comments.erb +6 -0
- data/example/views/layout.erb +1 -1
- data/example/views/show.erb +5 -1
- data/lib/primer/bus/amqp.rb +7 -5
- data/lib/primer/bus/amqp.rbc +1045 -0
- data/lib/primer/bus/memory.rbc +344 -0
- data/lib/primer/bus.rb +3 -2
- data/lib/primer/bus.rbc +872 -0
- data/lib/primer/cache/memory.rbc +1443 -0
- data/lib/primer/cache/redis.rb +2 -2
- data/lib/primer/cache/redis.rbc +1643 -0
- data/lib/primer/cache.rb +2 -16
- data/lib/primer/cache.rbc +1502 -0
- data/lib/primer/enabler.rbc +414 -0
- data/lib/primer/helpers.rbc +1438 -0
- data/lib/primer/lazyness.rb +63 -0
- data/lib/primer/lazyness.rbc +1442 -0
- data/lib/primer/real_time.rbc +1999 -0
- data/lib/primer/route_set.rb +1 -0
- data/lib/primer/route_set.rbc +1475 -0
- data/lib/primer/watcher/active_record_macros.rb +13 -3
- data/lib/primer/watcher/active_record_macros.rbc +1796 -0
- data/lib/primer/watcher/macros.rb +11 -14
- data/lib/primer/watcher/macros.rbc +1628 -0
- data/lib/primer/watcher.rb +2 -7
- data/lib/primer/watcher.rbc +1416 -0
- data/lib/primer/worker/active_record_agent.rb +30 -33
- data/lib/primer/worker/active_record_agent.rbc +2840 -0
- data/lib/primer/worker/changes_agent.rb +20 -0
- data/lib/primer/worker/changes_agent.rbc +578 -0
- data/lib/primer/worker.rb +20 -3
- data/lib/primer/worker.rbc +1254 -0
- data/lib/primer.rb +10 -2
- data/lib/primer.rbc +944 -0
- data/spec/db/test.sqlite3 +0 -0
- data/spec/models/artist.rb +3 -2
- data/spec/models/artist.rbc +288 -0
- data/spec/models/blog_post.rb +3 -1
- data/spec/models/blog_post.rbc +181 -0
- data/spec/models/calendar.rbc +209 -0
- data/spec/models/concert.rbc +211 -0
- data/spec/models/performance.rbc +177 -0
- data/spec/models/person.rb +1 -0
- data/spec/models/person.rbc +306 -0
- data/spec/models/watchable.rbc +363 -0
- data/spec/primer/bus_spec.rbc +940 -0
- data/spec/primer/cache_spec.rb +3 -3
- data/spec/primer/cache_spec.rbc +8535 -0
- data/spec/primer/helpers/erb_spec.rb +14 -14
- data/spec/primer/helpers/erb_spec.rbc +2485 -0
- data/spec/primer/lazyness_spec.rb +61 -0
- data/spec/primer/lazyness_spec.rbc +1408 -0
- data/spec/primer/watcher/active_record_spec.rb +15 -15
- data/spec/primer/watcher/active_record_spec.rbc +5202 -0
- data/spec/primer/watcher_spec.rbc +2645 -0
- data/spec/schema.rbc +775 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/spec_helper.rbc +1193 -0
- data/spec/templates/page.erb +0 -1
- metadata +77 -70
- 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
|
5
|
-
and denormalization and messages and offline processing and it's hard to
|
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
|
14
|
-
possible, at least for typical ActiveRecord usage, and Primer includes an
|
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
|
-
|
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
|
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
|
66
|
-
|
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.
|
68
|
+
Primer::Worker::ChangesAgent.run!
|
69
69
|
|
70
70
|
# If you're using ActiveRecord
|
71
|
-
Primer::Worker::ActiveRecordAgent.
|
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
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
169
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
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
|
17
|
+
simple blog post model:
|
16
18
|
|
17
|
-
class
|
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
|
-
|
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
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
68
|
-
|
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
|
|
data/example/application.rb
CHANGED
@@ -14,12 +14,12 @@ class Application < Sinatra::Base
|
|
14
14
|
helpers { include Primer::Helpers::ERB }
|
15
15
|
|
16
16
|
get '/' do
|
17
|
-
@posts =
|
17
|
+
@posts = Post.all
|
18
18
|
erb :index
|
19
19
|
end
|
20
20
|
|
21
21
|
get '/posts/:id' do
|
22
|
-
@post =
|
22
|
+
@post = Post.find(params[:id])
|
23
23
|
erb :show
|
24
24
|
end
|
25
25
|
end
|
data/example/console.rb
ADDED
@@ -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
|
data/example/environment.rb
CHANGED
@@ -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 + '/
|
10
|
-
require dir + '/models/
|
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
|
-
|
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
|
-
|
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
|
|
@@ -1,11 +1,17 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__)) + '/../
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/../db/connection'
|
2
2
|
|
3
3
|
ActiveRecord::Schema.define do |version|
|
4
|
-
create_table :
|
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
|
|
data/example/views/layout.erb
CHANGED
data/example/views/show.erb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
<h3><%= @post.title.upcase %></h3>
|
2
|
-
<h4>
|
2
|
+
<h4>
|
3
|
+
By <%= @post.author %> —
|
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" %>
|
data/lib/primer/bus/amqp.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require '
|
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(
|
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
|
-
|
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 =
|
36
|
+
data = Primer.deserialize(message)
|
35
37
|
distribute(*data)
|
36
38
|
end
|
37
39
|
@bound = true
|