redcrumbs 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,4 +1,5 @@
1
1
  *.gem
2
+ *.rdb
2
3
  .DS_Store
3
4
  .bundle
4
5
  Gemfile.lock
@@ -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
@@ -2,3 +2,9 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in redcrumbs.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'codeclimate-test-reporter', :group => :test, :require => nil
8
+ gem 'rspec', '~> 3.0'
9
+ gem 'sqlite3', '~> 1.0'
10
+ end
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
- Fast and unobtrusive activity tracking of ActiveRecord models using Redis and DataMapper.
4
-
5
- Redcrumbs is designed for high-traffic applications that need to track changes to their tables without making additional writes to the database. It is especially useful where the saved history needs to be expired over time and is not mission critical data. The emphasis is on reducing response times and easing the load on your main database.
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
- Note: Now compatible with Rails 3.1+ only.
8
+ Fast and unobtrusive activity tracking of ActiveRecord models using Redis and DataMapper.
8
9
 
9
- User context is built in and fully customisable and this makes Redcrumbs particularly useful for reporting relevant activity to users as it happens in your app.
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
- Redcrumbs is used for the 'News' feature in Project Zebra games but could also be used as the basis of a fast versioning or reporting system.
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
- For a more complete versioning system see the excellent [vestal_versions](https://github.com/laserlemon/vestal_versions) gem.
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
- Assuming you've got Redis installed and running on your system just add this to your Gemfile:
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. No migrations necessary!
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
- ## Example
35
+
36
+ ## Getting Started
34
37
 
35
38
  Start tracking a model by adding `redcrumbed` to the class:
36
39
 
37
- ```
38
- class Venue < ActiveRecord::Base
39
- redcrumbed :only => [:name, :latlng]
40
+ ```ruby
41
+ class Game < ActiveRecord::Base
42
+ redcrumbed :only => [:name, :highscore]
40
43
 
41
44
  validates :name, :presence => true
42
- validates :latlng, :uniqueness => true
45
+ validates :highscore, :presence => true
43
46
  end
44
47
  ```
45
48
 
46
- And that's pretty much it! Now you can do this:
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
- > venue.update_attributes(:name => "City Hall, Belfast")
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
- > crumb = venue.crumbs.last
63
+ ```ruby
64
+ crumb = game.crumbs.last
59
65
  => #<Crumb id: 53 ... >
60
66
 
61
- > crumb.modifications
62
- => {"name" => ["Belfast City Hall", "City Hall, Belfast"]}
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
- Not too shabby. But crumbs can also track the user that made the change (creator), and even a secondary user affected by the change (target). By default the creator is considered to be the user associated with the object:
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
- > venue.update_attributes(:name => "Halla na Cathrach, Bhéal Feirste")
79
- => #<Venue id: 1, name: "Halla na Cathrach, Bhéal Feirste", user_id: 2 ... >
78
+ ## Creating a HTML activity feed
80
79
 
81
- > crumb = venue.crumbs.last
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
- > crumb.modifications
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
- > crumb.creator
88
- => #<User id: 2, name: "Jon" ... >
84
+ Here's an example of a simple text helper:
89
85
 
90
- # and really cool, returns a limited (default 100) array of crumbs affecting a user in reverse order:
91
- > user.crumbs_as_user(:limit => 20)
92
- => [#<Crumb id: 64 ... >, #<Crumb id: 53 ... >, #<Crumb id: 42 ... > ... ]
93
-
94
- # or if you just want the crumbs created by the user
95
- > user.crumbs_by
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
- # or affecting the user
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
- class Venue < ActiveRecord::Base
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
- ## Conditional control
121
+ #### Whodunnit?
125
122
 
126
- 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:
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 Venue < ActiveRecord::Base
130
- redcrumbed :only => [:name, :latlng], :if => :has_user?
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 has_user?
133
- !!user_id
131
+ def creator
132
+ high_scorer
134
133
  end
135
134
  end
136
135
  ```
137
136
 
138
- ## Attribute storage
137
+ To get the creator and target of a crumb:
139
138
 
140
- It's not best practice but since the emphasis is on easing the load on our main database we have bent a few rules in order to reduce the calls on the database to, ideally, zero. In any given app you may be tracking several models and this results in a lot of SQL we could do without.
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
- `redcrumbed` accepts a `:store` option to which you can pass a hash of options similar to that of the ActiveRecord `as_json` method. These are attributes of the subject that you'd like to store on the crumb object itself. Use it sparingly if you know that, for example, you are only ever going to really use a couple of attributes of the subject and you want to avoid loading the whole thing from the database.
146
+ #### Querying user activity
145
147
 
146
- Examples:
148
+ As you'd expect you can also grab all the activities affecting a user.
147
149
 
148
- ```
149
- class Venue
150
- redcrumbed :only => [:name, :latlng], :store => {:only => [:id, :name]}
151
- end
150
+ ```ruby
151
+ # Activities created by a user
152
+ player.crumbs_by
152
153
  ```
153
154
 
154
- ```
155
- class Venue
156
- redcrumbed :only => [:name, :latlng], :store => {:except => [:updated_at, :created_at]}
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
- class Venue
162
- redcrumbed :only => [:name, :latlng], :store => {:only => [:id, :name], :methods => [:checkins]}
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
- #### Versions < 0.3.0
179
+ #### Attribute storage
167
180
 
168
- `redcrumbed` accepts a `:store` option to which you can pass an array of attributes of the subject that you'd like to store on the crumb object itself. Use it sparingly if you know that, for example, you are only ever going to really use a couple of attributes of the subject and you want to avoid loading the whole thing from the database.
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 Venue
172
- redcrumbed :only => [:name, :latlng], :store => [:id, :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
- #### Using the stored object
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
- So now if you call `crumb.subject` instead of loading the Venue from your database it will instantiate a new Venue with the only the attributes you have stored. You can always retrieve the original by calling `crumb.full_subject`.
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
- _ If you plan to use the `methods` option to store data on the Crumb you should only use it to store attr_accessors unless you won't be instantiating the subject itself _
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
- #### Creator and Target storage
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
- As you might expect, you can also do this for the creator and target of the crumb. See the redcrumbs.rb initializer for how to set this as a global configuration.
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
- Lots of refactoring, tests and new features.
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)) (c) 2012 for Project Zebra ([@projectzebra](http://www.twitter.com/projectzebra)). Released under MIT License (http://www.opensource.org/licenses/mit-license.php).
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