redcrumbs 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +1 -0
- data/.travis.yml +33 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +140 -105
- data/gemfiles/Gemfile.rails-3.1.x +12 -0
- data/gemfiles/Gemfile.rails-3.2.x +12 -0
- data/gemfiles/Gemfile.rails-4.0.x +12 -0
- data/gemfiles/Gemfile.rails-4.1.x +12 -0
- data/lib/generators/redcrumbs/templates/initializer.rb +9 -26
- data/lib/redcrumbs.rb +12 -7
- data/lib/redcrumbs/config.rb +55 -5
- data/lib/redcrumbs/creation.rb +60 -47
- data/lib/redcrumbs/crumb.rb +100 -0
- data/lib/redcrumbs/options.rb +8 -3
- data/lib/redcrumbs/serializable_association.rb +193 -0
- data/lib/redcrumbs/users.rb +27 -37
- data/lib/redcrumbs/version.rb +1 -1
- data/redcrumbs.gemspec +11 -9
- data/spec/redcrumbs/config_spec.rb +168 -0
- data/spec/redcrumbs/creation_spec.rb +271 -0
- data/spec/redcrumbs/crumb_spec.rb +254 -0
- data/spec/redcrumbs/options_spec.rb +70 -0
- data/spec/redcrumbs/serializable_association_spec.rb +101 -0
- data/spec/redcrumbs/users_spec.rb +55 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/models.rb +34 -0
- data/spec/support/schema.rb +26 -0
- metadata +106 -34
- data/app/models/redcrumbs/crumb.rb +0 -68
- data/app/models/redcrumbs/crumb/expiry.rb +0 -23
- data/app/models/redcrumbs/crumb/getters.rb +0 -74
- data/app/models/redcrumbs/crumb/setters.rb +0 -28
- data/lib/redcrumbs/engine.rb +0 -8
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjE2OWRjNDVjYzIxNjNkYzY1MzU4MWY1N2RhMWJmOGQwMjZlZDk1Zg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDQ4NDVkYWM1ZTZkOWQ4NzQ4MTJiOTEwZDAxNDFjOTk2N2YxNGFmOQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
M2EzZWVhNjc1ZTA3N2MzNjk0ZjhjNmQxODQ0OTJkNDRkODUzZmMwNWU1Yjlk
|
10
|
+
YmRkMzY3NjlmYjM5ZjIxNDcxNDYwNzZiOWRjYTQ2YzcwNTQxNzEzMDg1Njgx
|
11
|
+
YWViZDMxMGRjOWZhMzU2MjU3NzQyOWUzOTc4NDg4YjJlNDA0M2Q=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZjU5Y2M3ZWEwMTlkYmZjMDQ5MDAwNjc3ZGViOWM5ZmFkODcxNzc1ODQ4MmRi
|
14
|
+
ZGEyNDIwYjMzNjUzMGY4MWZkY2NiYWZjMWE0ZDk3NmU0ZWU3NmM0MjMzZDZm
|
15
|
+
NDNmZWFhZDNjNjYxNTdlNTliNjhiY2NkMzRmYzNlNDY0MjJjMDM=
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 1.9.3
|
5
|
+
- 2.0.0
|
6
|
+
- 2.1.0
|
7
|
+
- ruby-head
|
8
|
+
|
9
|
+
gemfile:
|
10
|
+
- gemfiles/Gemfile.rails-3.1.x
|
11
|
+
- gemfiles/Gemfile.rails-3.2.x
|
12
|
+
- gemfiles/Gemfile.rails-4.0.x
|
13
|
+
- gemfiles/Gemfile.rails-4.1.x
|
14
|
+
|
15
|
+
sudo: false
|
16
|
+
|
17
|
+
services:
|
18
|
+
- redis-server
|
19
|
+
|
20
|
+
script: bundle exec rspec
|
21
|
+
|
22
|
+
branches:
|
23
|
+
only:
|
24
|
+
- master
|
25
|
+
- version_5.0
|
26
|
+
|
27
|
+
matrix:
|
28
|
+
allow_failures:
|
29
|
+
- rvm: ruby-head
|
30
|
+
|
31
|
+
addons:
|
32
|
+
code_climate:
|
33
|
+
repo_token: 10b4b9067afdf62b56253fa8b4fb38ecc7e9b7fc8bb3e45e6d954ef6e3190445
|
data/Gemfile
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 John M. Hope
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,193 +1,228 @@
|
|
1
1
|
# Redcrumbs
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
[![Build Status](https://travis-ci.org/JonMidhir/Redcrumbs.svg?branch=master)](https://travis-ci.org/JonMidhir/Redcrumbs)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/JonMidhir/Redcrumbs/badges/gpa.svg)](https://codeclimate.com/github/JonMidhir/Redcrumbs)
|
5
|
+
[![Test Coverage](https://codeclimate.com/github/JonMidhir/Redcrumbs/badges/coverage.svg)](https://codeclimate.com/github/JonMidhir/Redcrumbs)
|
6
|
+
[![Dependency Status](https://gemnasium.com/JonMidhir/Redcrumbs.svg)](https://gemnasium.com/JonMidhir/Redcrumbs)
|
6
7
|
|
7
|
-
|
8
|
+
Fast and unobtrusive activity tracking of ActiveRecord models using Redis and DataMapper.
|
8
9
|
|
9
|
-
|
10
|
+
Redcrumbs is designed to make it easy to start generating activity feeds in your application using Redis as a back-end.
|
10
11
|
|
11
|
-
|
12
|
+
Introducing activity feeds can come at significant cost, increasing the number of writes to your primary datastore across many controller actions - sometimes when previously only reads were being performed. Activity feeds have their own characteristics too; they're often not mission critical data, expirable over time and queried in predictable ways.
|
12
13
|
|
13
|
-
|
14
|
+
It turns out Redis is an ideal solution. Super fast to write to and read from and with Memcached-style key expiration built in, leaving your primary database to focus on the business logic.
|
14
15
|
|
15
|
-
Please note, this is early stage stuff. We're not using it in production just yet.
|
16
16
|
|
17
17
|
## Installation
|
18
18
|
|
19
|
-
|
19
|
+
You'll need access to a [Redis](http://redis.io) server running locally, remotely or from a managed service; such as [Redis Labs](https://redislabs.com/).
|
20
20
|
|
21
|
-
|
21
|
+
Add the Gem to your Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
22
24
|
gem 'redcrumbs'
|
23
25
|
```
|
24
26
|
|
25
|
-
Then run the generator to create the initializer file.
|
27
|
+
Then run the generator to create the initializer file.
|
26
28
|
|
27
|
-
```
|
29
|
+
```sh
|
28
30
|
$ rails g redcrumbs:install
|
29
31
|
```
|
30
32
|
|
31
33
|
Done! Look in `config/initializers/redcrumbs.rb` for customisation options.
|
32
34
|
|
33
|
-
|
35
|
+
|
36
|
+
## Getting Started
|
34
37
|
|
35
38
|
Start tracking a model by adding `redcrumbed` to the class:
|
36
39
|
|
37
|
-
```
|
38
|
-
class
|
39
|
-
redcrumbed :only => [:name, :
|
40
|
+
```ruby
|
41
|
+
class Game < ActiveRecord::Base
|
42
|
+
redcrumbed :only => [:name, :highscore]
|
40
43
|
|
41
44
|
validates :name, :presence => true
|
42
|
-
validates :
|
45
|
+
validates :highscore, :presence => true
|
43
46
|
end
|
44
47
|
```
|
45
48
|
|
46
|
-
|
49
|
+
That's all you need to get started. `Game` objects will now start generating activities when their `name` or `highscore` attributes are updated.
|
50
|
+
|
47
51
|
|
52
|
+
```ruby
|
53
|
+
game = Game.last
|
54
|
+
=> #<Game id: 1, name: "Paperboy" ... >
|
55
|
+
|
56
|
+
game.update_attributes(:name => "Paperperson")
|
57
|
+
=> #<Game id: 1, name: "Paperperson" ... >
|
48
58
|
```
|
49
|
-
> venue = Venue.last
|
50
|
-
=> #<Venue id: 1, name: "Belfast City Hall" ... >
|
51
59
|
|
52
|
-
|
53
|
-
=> #<Venue id: 1, name: "City Hall, Belfast" ... >
|
60
|
+
Activities are objects of class `Crumb` and contain all the data you need to find out about what has changed in the update.
|
54
61
|
|
55
|
-
> venue.crumbs
|
56
|
-
=> [#<Crumb id: 34 ... >, #<Crumb id: 42 ... >, #<Crumb id: 53 ... >]
|
57
62
|
|
58
|
-
|
63
|
+
```ruby
|
64
|
+
crumb = game.crumbs.last
|
59
65
|
=> #<Crumb id: 53 ... >
|
60
66
|
|
61
|
-
|
62
|
-
=> {"name" => ["
|
63
|
-
|
64
|
-
> crumb.subject
|
65
|
-
=> #<Venue id: 1, name: "City Hall, Belfast" ... >
|
67
|
+
crumb.modifications
|
68
|
+
=> {"name" => ["Paperboy", "Paperperson"]}
|
66
69
|
|
67
70
|
```
|
68
71
|
|
69
|
-
|
72
|
+
The `.crumbs` method shown here is available to any class that is `redcrumbed`. It is just a DataMapper collection and you can use it to construct any queries you like. For example, to get the last 10 activities on `game`:
|
70
73
|
|
74
|
+
```ruby
|
75
|
+
game.crumbs.all(:order => :created_at.desc, :limit => 10)
|
71
76
|
```
|
72
|
-
> user = User.find(2)
|
73
|
-
=> #<User id: 2, name: "Jon" ... >
|
74
|
-
|
75
|
-
> venue = user.venues.last
|
76
|
-
=> #<Venue id: 1, name: "City Hall, Belfast", user_id: 2 ... >
|
77
77
|
|
78
|
-
|
79
|
-
=> #<Venue id: 1, name: "Halla na Cathrach, Bhéal Feirste", user_id: 2 ... >
|
78
|
+
## Creating a HTML activity feed
|
80
79
|
|
81
|
-
|
82
|
-
=> #<Crumb id: 54 ... >
|
80
|
+
Redcrumbs doesn't provide any helpers to turn crumbs into translated text or HTML views but this is extremely easy to do once you're set up and creating activities.
|
83
81
|
|
84
|
-
|
85
|
-
=> {"name" => ["City Hall, Belfast", "Halla na Cathrach, Bhéal Feirste"]}
|
82
|
+
Now that we know how to get the most recent activities associated with an object we just need to create a helper to translate these into readable text or HTML. Crumbs have a `subject` association that gives you access to the original object. This is useful when you need access to attributes that aren't in the modifications hash.
|
86
83
|
|
87
|
-
|
88
|
-
=> #<User id: 2, name: "Jon" ... >
|
84
|
+
Here's an example of a simple text helper:
|
89
85
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
86
|
+
```ruby
|
87
|
+
module ActivityHelper
|
88
|
+
def activity_text_from(crumb)
|
89
|
+
modifications = crumb.modifications
|
90
|
+
|
91
|
+
message = 'Someone '
|
92
|
+
|
93
|
+
fragments = []
|
94
|
+
fragments << "set a highscore of #{modifications['highscore']}" if modifications.has_key?('highscore')
|
95
|
+
|
96
|
+
if modifications.has_key?('name')
|
97
|
+
fragments << "renamed #{modifications['name']} to #{modifications['name']}"
|
98
|
+
else
|
99
|
+
fragments[0] += " at #{crumb.subject.name}"
|
100
|
+
end
|
101
|
+
|
102
|
+
message += fragments.to_sentence
|
103
|
+
message += '.'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
96
107
|
|
97
|
-
|
98
|
-
> user.crumbs_for
|
108
|
+
And examples of its output:
|
99
109
|
|
100
110
|
```
|
111
|
+
"Someone renamed Paperboy to Paperperson."
|
112
|
+
"Someone set a highscore of 19840 at Paperperson."
|
113
|
+
"Someone set a highscore of 21394 at Paperperson and renamed Paperperson to I WIN NOOBS."
|
114
|
+
```
|
101
115
|
|
102
|
-
You can customise just what should be considered a creator or target globally across your app by editing a few lines in the redcrumbs initializer. Or you can override the creator and target methods if you want class-specific control:
|
103
116
|
|
104
|
-
|
105
|
-
class User < ActiveRecord::Base
|
106
|
-
belongs_to :alliance
|
107
|
-
has_many :venues
|
108
|
-
end
|
117
|
+
## User context
|
109
118
|
|
110
|
-
|
111
|
-
redcrumbed :only => [:name, :latlng]
|
112
|
-
|
113
|
-
belongs_to :user
|
114
|
-
|
115
|
-
validates :name, :presence => true
|
116
|
-
validates :latlng, :uniqueness => true
|
117
|
-
|
118
|
-
def creator
|
119
|
-
user.alliance
|
120
|
-
end
|
121
|
-
end
|
122
|
-
```
|
119
|
+
Simply reporting that 'Someone did xyz' isn't very useful, so Redcrumbs has user context baked in.
|
123
120
|
|
124
|
-
|
121
|
+
#### Whodunnit?
|
125
122
|
|
126
|
-
|
123
|
+
Crumbs can track the user that made the change (or any object really) as `creator`, and even a secondary user affected by the change as `target`. You simply define methods called `creator` and `target` on the subject class that return the corresponding object:
|
127
124
|
|
128
|
-
```
|
129
|
-
class
|
130
|
-
redcrumbed :only => [:name, :
|
125
|
+
```ruby
|
126
|
+
class Game < ActiveRecord::Base
|
127
|
+
redcrumbed :only => [:name, :highscore]
|
128
|
+
|
129
|
+
has_one :high_scorer, class_name: 'Player'
|
131
130
|
|
132
|
-
def
|
133
|
-
|
131
|
+
def creator
|
132
|
+
high_scorer
|
134
133
|
end
|
135
134
|
end
|
136
135
|
```
|
137
136
|
|
138
|
-
|
137
|
+
To get the creator and target of a crumb:
|
139
138
|
|
140
|
-
|
139
|
+
crumb.creator
|
140
|
+
=> #<Player id: 394 ...>
|
141
|
+
|
142
|
+
crumb.target
|
143
|
+
=> #<ComputerPlayer id: 3 ...>
|
141
144
|
|
142
|
-
#### Versions >= 0.3.0
|
143
145
|
|
144
|
-
|
146
|
+
#### Querying user activity
|
145
147
|
|
146
|
-
|
148
|
+
As you'd expect you can also grab all the activities affecting a user.
|
147
149
|
|
148
|
-
```
|
149
|
-
|
150
|
-
|
151
|
-
end
|
150
|
+
```ruby
|
151
|
+
# Activities created by a user
|
152
|
+
player.crumbs_by
|
152
153
|
```
|
153
154
|
|
154
|
-
```
|
155
|
-
|
156
|
-
|
157
|
-
end
|
155
|
+
```ruby
|
156
|
+
# Activities targetting a user
|
157
|
+
player.crumbs_for
|
158
158
|
```
|
159
159
|
|
160
|
+
```ruby
|
161
|
+
# All activities affecting a user
|
162
|
+
player.crumbs_as_user
|
160
163
|
```
|
161
|
-
|
162
|
-
|
164
|
+
|
165
|
+
## Advanced Options
|
166
|
+
|
167
|
+
#### Conditional control
|
168
|
+
|
169
|
+
You can pass `:if` and `:unless` options to the redcrumbed method to control when an action should be tracked in the same way you would for an ActiveRecord callback. For example, if you only want to track activity _after_ a game has been created:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
class Game < ActiveRecord::Base
|
173
|
+
redcrumbed :only => [:name, :highscore], :unless => :new_record?
|
174
|
+
|
175
|
+
#...
|
163
176
|
end
|
164
177
|
```
|
165
178
|
|
166
|
-
####
|
179
|
+
#### Attribute storage
|
167
180
|
|
168
|
-
|
181
|
+
In many cases to assemble your feed you'll only ever need the `modifications` made to an object plus a couple of common attributes; such as `name` or `id`. When this is the case you can avoid loading the subject from the database entirely by storing those attributes on the crumb itself.
|
169
182
|
|
170
|
-
```
|
171
|
-
class
|
172
|
-
redcrumbed :only => [:name, :
|
183
|
+
```ruby
|
184
|
+
class Game < ActiveRecord::Base
|
185
|
+
redcrumbed :only => [:name, :highscore], :store => {:only => [:id, :name]}
|
186
|
+
|
187
|
+
#...
|
173
188
|
end
|
174
189
|
```
|
175
190
|
|
176
|
-
|
191
|
+
Now when you call `crumb.subject` you will get an instance of `Game` with only the `:id` and `:name` attributes set. If you need the full object you can always load it fully from the database by calling `crumb.full_subject`.
|
192
|
+
|
193
|
+
<blockquote>
|
194
|
+
Note: Be careful using this. The tradeoff is bloat. You will get fewer Redis keys per megabyte. An :except option is available instead of :only but its use is not advised.</blockquote>
|
195
|
+
|
196
|
+
#### Creator / Target storage
|
177
197
|
|
178
|
-
|
198
|
+
Similarly to __attribute storage__ above, you can store properties of the `creator` and `target` on the crumb to avoid having to load them from the database. These attributes can only be set globally in the initialization file. Since these objects can differ wildly from model to model this only works when they share some common attributes.
|
179
199
|
|
180
|
-
|
200
|
+
For example a photo might be _created_ by a `User` or an event by a `UserGroup`. If both objects had `:id` and `:name` attributes, for example, you could store these.
|
181
201
|
|
182
|
-
|
202
|
+
The usual warnings apply. However, by combining this with __attribute storage__ it's possible to return multiple activity feeds without touching the primary datastore!
|
183
203
|
|
184
|
-
|
204
|
+
## Changes since v0.4.9
|
205
|
+
- Key mortality works.
|
206
|
+
- No longer adding `creator` according to initializer options.
|
207
|
+
- More robust Redis assignment.
|
208
|
+
- Support for Redis Namespaces.
|
209
|
+
- Major refactor.
|
210
|
+
- Decent test coverage.
|
185
211
|
|
212
|
+
## Compatibility
|
213
|
+
Tested against:
|
214
|
+
- Ruby 1.9.3 to 2.1.0
|
215
|
+
- ActiveRecord 3.1 to 4.1
|
186
216
|
|
187
217
|
## To-do
|
188
218
|
|
189
|
-
|
219
|
+
Allow swapping out the backend to mongo or other key value stores.
|
220
|
+
|
221
|
+
## Testing
|
222
|
+
|
223
|
+
Running tests requires a redis server to be running on the local machine with access over port 6379.
|
224
|
+
Run tests with `bundle exec rspec`.
|
190
225
|
|
191
226
|
## License
|
192
227
|
|
193
|
-
Created by John Hope ([@midhir](http://www.twitter.com/midhir))
|
228
|
+
Created by John Hope ([@midhir](http://www.twitter.com/midhir)) for Project Zebra ([@projectzebra](http://www.twitter.com/projectzebra)) (c) 2014. Released under MIT License (http://www.opensource.org/licenses/mit-license.php).
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'activerecord', '~> 3.1'
|
4
|
+
gem 'activesupport', '~> 3.1'
|
5
|
+
|
6
|
+
gemspec :path => '..'
|
7
|
+
|
8
|
+
group :test do
|
9
|
+
gem 'codeclimate-test-reporter', :group => :test, :require => nil
|
10
|
+
gem 'rspec', '~> 3.0'
|
11
|
+
gem 'sqlite3', '~> 1.0'
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'activerecord', '~> 3.2'
|
4
|
+
gem 'activesupport', '~> 3.2'
|
5
|
+
|
6
|
+
gemspec :path => '..'
|
7
|
+
|
8
|
+
group :test do
|
9
|
+
gem 'codeclimate-test-reporter', :group => :test, :require => nil
|
10
|
+
gem 'rspec', '~> 3.0'
|
11
|
+
gem 'sqlite3', '~> 1.0'
|
12
|
+
end
|