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.
- 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
|