redcrumbs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +159 -0
- data/Rakefile +1 -0
- data/app/models/redcrumbs/crumb/expiry.rb +23 -0
- data/app/models/redcrumbs/crumb/getters.rb +55 -0
- data/app/models/redcrumbs/crumb/setters.rb +25 -0
- data/app/models/redcrumbs/crumb.rb +64 -0
- data/lib/generators/redcrumbs/install_generator.rb +12 -0
- data/lib/generators/redcrumbs/templates/initializer.rb +21 -0
- data/lib/redcrumbs/config.rb +19 -0
- data/lib/redcrumbs/creation.rb +31 -0
- data/lib/redcrumbs/engine.rb +8 -0
- data/lib/redcrumbs/options.rb +27 -0
- data/lib/redcrumbs/users.rb +35 -0
- data/lib/redcrumbs/version.rb +3 -0
- data/lib/redcrumbs.rb +63 -0
- data/redcrumbs.gemspec +23 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# Redcrumbs
|
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.
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
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.
|
10
|
+
|
11
|
+
For a more complete versioning system see the excellent [vestal_versions](https://github.com/laserlemon/vestal_versions) gem.
|
12
|
+
|
13
|
+
Please note, this is early stage stuff. We're not using it in production just yet.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Assuming you've got Redis installed and running on your system just add this to your Gemfile:
|
18
|
+
|
19
|
+
```
|
20
|
+
gem 'redcrumbs'
|
21
|
+
```
|
22
|
+
|
23
|
+
Then run the generator to create the initializer file. No migrations necessary!
|
24
|
+
|
25
|
+
```
|
26
|
+
$ rails g redcrumbs:install
|
27
|
+
```
|
28
|
+
|
29
|
+
Done! Look in `config/initializers/redcrumbs.rb` for customisation options.
|
30
|
+
|
31
|
+
## Example
|
32
|
+
|
33
|
+
Start tracking a model by adding `redcrumbed` to the class:
|
34
|
+
|
35
|
+
```
|
36
|
+
class Venue < ActiveRecord::Base
|
37
|
+
redcrumbed :only => [:name, :latlng]
|
38
|
+
|
39
|
+
validates :name, :presence => true
|
40
|
+
validates :latlng, :uniqueness => true
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
And that's pretty much it! Now you can do this:
|
45
|
+
|
46
|
+
```
|
47
|
+
> venue = Venue.last
|
48
|
+
=> #<Venue id: 1, name: "Belfast City Hall" ... >
|
49
|
+
|
50
|
+
> venue.update_attributes(:name => "City Hall, Belfast")
|
51
|
+
=> #<Venue id: 1, name: "City Hall, Belfast" ... >
|
52
|
+
|
53
|
+
> venue.crumbs
|
54
|
+
=> [#<Crumb id: 34 ... >, #<Crumb id: 42 ... >, #<Crumb id: 53 ... >]
|
55
|
+
|
56
|
+
> crumb = venue.crumbs.last
|
57
|
+
=> #<Crumb id: 53 ... >
|
58
|
+
|
59
|
+
> crumb.modifications
|
60
|
+
=> {"name" => ["Belfast City Hall", "City Hall, Belfast"]}
|
61
|
+
|
62
|
+
> crumb.subject
|
63
|
+
=> #<Venue id: 1, name: "City Hall, Belfast" ... >
|
64
|
+
|
65
|
+
```
|
66
|
+
|
67
|
+
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:
|
68
|
+
|
69
|
+
```
|
70
|
+
> user = User.find(2)
|
71
|
+
=> #<User id: 2, name: "Jon" ... >
|
72
|
+
|
73
|
+
> venue = user.venues.last
|
74
|
+
=> #<Venue id: 1, name: "City Hall, Belfast", user_id: 2 ... >
|
75
|
+
|
76
|
+
> venue.update_attributes(:name => "Halla na Cathrach, Bhéal Feirste")
|
77
|
+
=> #<Venue id: 1, name: "Halla na Cathrach, Bhéal Feirste", user_id: 2 ... >
|
78
|
+
|
79
|
+
> crumb = venue.crumbs.last
|
80
|
+
=> #<Crumb id: 54 ... >
|
81
|
+
|
82
|
+
> crumb.modifications
|
83
|
+
=> {"name" => ["City Hall, Belfast", "Halla na Cathrach, Bhéal Feirste"]}
|
84
|
+
|
85
|
+
> crumb.creator
|
86
|
+
=> #<User id: 2, name: "Jon" ... >
|
87
|
+
|
88
|
+
# and really cool, returns a limited (default 100) array of crumbs affecting a user in reverse order:
|
89
|
+
> user.crumbs(:limit => 20)
|
90
|
+
=> [#<Crumb id: 64 ... >, #<Crumb id: 53 ... >, #<Crumb id: 42 ... > ... ]
|
91
|
+
|
92
|
+
# or if you just want the crumbs created by the user
|
93
|
+
> user.crumbs_by
|
94
|
+
|
95
|
+
# or affecting the user
|
96
|
+
> user.crumbs_for
|
97
|
+
|
98
|
+
```
|
99
|
+
|
100
|
+
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:
|
101
|
+
|
102
|
+
```
|
103
|
+
class User < ActiveRecord::Base
|
104
|
+
belongs_to :alliance
|
105
|
+
has_many :venues
|
106
|
+
end
|
107
|
+
|
108
|
+
class Venue < ActiveRecord::Base
|
109
|
+
redcrumbed :only => [:name, :latlng]
|
110
|
+
|
111
|
+
belongs_to :user
|
112
|
+
|
113
|
+
validates :name, :presence => true
|
114
|
+
validates :latlng, :uniqueness => true
|
115
|
+
|
116
|
+
def creator
|
117
|
+
user.alliance
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
## Conditional control
|
123
|
+
|
124
|
+
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:
|
125
|
+
|
126
|
+
```
|
127
|
+
class Venue < ActiveRecord::Base
|
128
|
+
redcrumbed :only => [:name, :latlng], :if => :has_user?
|
129
|
+
|
130
|
+
def has_user?
|
131
|
+
!!user_id
|
132
|
+
end
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
## Attribute storage
|
137
|
+
|
138
|
+
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
|
+
|
140
|
+
`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.
|
141
|
+
|
142
|
+
```
|
143
|
+
class Venue
|
144
|
+
redcrumbed :only => [:name, :latlng], :store => [:id, :name]
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
So now if you call `crumb.subject` instead of loading the Venue from your database it will instantiate a new Venue with the same `id` and `name` attributes. You can always retrieve the original by calling `crumb.full_subject`.
|
149
|
+
|
150
|
+
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.
|
151
|
+
|
152
|
+
|
153
|
+
## To-do
|
154
|
+
|
155
|
+
Lots of refactoring, tests and new features.
|
156
|
+
|
157
|
+
## License
|
158
|
+
|
159
|
+
Created by John Hope (c) 2012 for Project Zebra. Released under MIT License (http://www.opensource.org/licenses/mit-license.php).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Redcrumbs
|
2
|
+
module Crumb::Expiry
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def mortal?
|
6
|
+
!!Redcrumbs.mortality
|
7
|
+
end
|
8
|
+
|
9
|
+
def expire_at
|
10
|
+
Time.now + Redcrumbs.mortality
|
11
|
+
end
|
12
|
+
|
13
|
+
def time_to_live
|
14
|
+
REDIS.ttl(redis_key) if mortal?
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def set_mortality
|
20
|
+
REDIS.expireat(redis_key, expire_at.to_i) if mortal?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Redcrumbs
|
2
|
+
module Crumb::Getters
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def subject
|
6
|
+
if !self.stored_subject.blank?
|
7
|
+
subject_from_storage
|
8
|
+
elsif subject_type && subject_id
|
9
|
+
self._subject ||= full_subject
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def full_subject
|
14
|
+
subject_type.classify.constantize.find(subject_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def subject_from_storage
|
18
|
+
new_subject = subject_type.constantize.new(self.stored_subject)
|
19
|
+
new_subject.id = self.stored_subject["id"] if self.stored_subject.has_key?("id")
|
20
|
+
new_subject
|
21
|
+
end
|
22
|
+
|
23
|
+
def creator
|
24
|
+
if !self.stored_creator.blank?
|
25
|
+
creator_class.new(self.stored_creator)
|
26
|
+
elsif !self.creator_id.blank?
|
27
|
+
self._creator ||= full_creator
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def creator_class
|
32
|
+
Redcrumbs.creator_class_sym.to_s.classify.constantize
|
33
|
+
end
|
34
|
+
|
35
|
+
def full_creator
|
36
|
+
creator_class.where(Redcrumbs.creator_primary_key => self.creator_id).first
|
37
|
+
end
|
38
|
+
|
39
|
+
def target
|
40
|
+
if !self.stored_target.blank?
|
41
|
+
target_class.new(self.stored_target)
|
42
|
+
elsif !self.target_id.blank?
|
43
|
+
self._target ||= full_target
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def target_class
|
48
|
+
Redcrumbs.target_class_sym.to_s.classify.constantize
|
49
|
+
end
|
50
|
+
|
51
|
+
def full_target
|
52
|
+
target_class.where(Redcrumbs.target_primary_key => self.creator_id).first
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Redcrumbs
|
2
|
+
module Crumb::Setters
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def subject=(subject)
|
6
|
+
self.stored_subject = subject.storeable_attributes
|
7
|
+
self.subject_type = subject.class.to_s
|
8
|
+
self.subject_id = subject.id
|
9
|
+
end
|
10
|
+
|
11
|
+
def creator=(creator)
|
12
|
+
unless !creator
|
13
|
+
self.stored_creator = creator.attributes.select {|attribute| Redcrumbs.store_creator_attributes.include?(attribute.to_sym)}
|
14
|
+
self.creator_id = creator[Redcrumbs.creator_primary_key]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def target=(target)
|
19
|
+
unless !target
|
20
|
+
self.stored_target = target.attributes.select {|attribute| Redcrumbs.store_target_attributes.include?(attribute.to_sym)}
|
21
|
+
self.target_id = target[Redcrumbs.target_primary_key]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-types'
|
3
|
+
require 'dm-timestamps'
|
4
|
+
require 'dm-redis-adapter'
|
5
|
+
|
6
|
+
## Note to self: Syntax to grab all by an attribute is - Notification.all(:subject_type => "User")
|
7
|
+
## The attribute must be indexed as with subject_type below
|
8
|
+
|
9
|
+
module Redcrumbs
|
10
|
+
class Crumb
|
11
|
+
REDIS = Redis.new
|
12
|
+
|
13
|
+
include DataMapper::Resource
|
14
|
+
include Crumb::Getters
|
15
|
+
include Crumb::Setters
|
16
|
+
include Crumb::Expiry
|
17
|
+
|
18
|
+
DataMapper.setup(:default, {:adapter => "redis"})
|
19
|
+
|
20
|
+
property :id, Serial
|
21
|
+
property :subject_id, Integer, :index => true
|
22
|
+
property :subject_type, String, :index => true
|
23
|
+
property :modifications, Json, :default => "{}"
|
24
|
+
property :created_at, DateTime
|
25
|
+
property :updated_at, DateTime
|
26
|
+
property :stored_creator, Json
|
27
|
+
property :stored_target, Json
|
28
|
+
property :stored_subject, Json
|
29
|
+
property :creator_id, Integer, :index => true
|
30
|
+
property :target_id, Integer, :index => true
|
31
|
+
|
32
|
+
DataMapper.finalize
|
33
|
+
|
34
|
+
after :save, :set_mortality
|
35
|
+
|
36
|
+
attr_accessor :_subject, :_creator, :_target
|
37
|
+
|
38
|
+
def initialize(params = {})
|
39
|
+
if self.subject = params[:subject]
|
40
|
+
self.target = self.subject.target if self.subject.respond_to?(:target)
|
41
|
+
self.creator = self.subject.creator if self.subject.respond_to?(:creator)
|
42
|
+
end
|
43
|
+
self.modifications = params[:modifications] unless !params[:modifications]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Remember to change the respond_to? argument when moving from user/target class to dynamic with user as default
|
47
|
+
def self.build_from(subject)
|
48
|
+
unless subject.watched_changes.empty?
|
49
|
+
params = {:modifications => subject.watched_changes}
|
50
|
+
params.merge!({:subject => subject})
|
51
|
+
new(params)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def redis_key
|
56
|
+
"redcrumbs_crumbs:#{id}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Designed to mimic ActiveRecord's count. Probably not performant and only should be used for tests really
|
60
|
+
def self.count
|
61
|
+
REDIS.keys("redcrumbs_crumbs:*").size - 8
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Redcrumbs
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../templates", __FILE__)
|
5
|
+
|
6
|
+
# all public methods in here will be run in order
|
7
|
+
def add_redcrumbs_initializer
|
8
|
+
template "initializer.rb", "config/initializers/redcrumbs.rb"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Redcrumbs.setup do |config|
|
2
|
+
# When a change is made to a redcrumbed model the user associated with that model will
|
3
|
+
# automatically be stored on the Crumb object. By default Redcrumbs will look for a User
|
4
|
+
# object using 'id' as the primary key but you can override that here.
|
5
|
+
#
|
6
|
+
# config.creator_class_sym = :user
|
7
|
+
# config.creator_primary_key = 'id'
|
8
|
+
# config.target_class_sym = :user
|
9
|
+
# config.target_primary_key = 'id'
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# If you're using the crumbs to report news back to a user you can store creator and target
|
13
|
+
# attributes on the crumb object to avoid having to touch your main database at all. Keep it
|
14
|
+
# sensible and evaluate whether the additional space used in Redis is really worth the time saving.
|
15
|
+
#
|
16
|
+
# config.store_creator_attributes = [:id, :name, :email]
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# Set the mortality to make crumbs automatically expire in time. Default is infinity.
|
20
|
+
# config.mortality = 30.days
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Redcrumbs
|
2
|
+
mattr_accessor :creator_class_sym
|
3
|
+
mattr_accessor :creator_primary_key
|
4
|
+
mattr_accessor :target_class_sym
|
5
|
+
mattr_accessor :target_primary_key
|
6
|
+
|
7
|
+
mattr_accessor :store_creator_attributes
|
8
|
+
mattr_accessor :store_target_attributes
|
9
|
+
|
10
|
+
mattr_accessor :mortality
|
11
|
+
|
12
|
+
@@creator_class_sym ||= :user
|
13
|
+
@@creator_primary_key ||= 'id'
|
14
|
+
@@target_class_sym ||= :user
|
15
|
+
@@target_primary_key ||= 'id'
|
16
|
+
|
17
|
+
@@store_creator_attributes ||= []
|
18
|
+
@@store_target_attributes ||= []
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Redcrumbs
|
2
|
+
module Creation
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module InstanceMethods
|
6
|
+
def crumbs
|
7
|
+
Crumb.all(:subject_type => self.class.to_s, :subject_id => self.id)
|
8
|
+
end
|
9
|
+
|
10
|
+
def watched_changes
|
11
|
+
changes.reject {|k,v| !self.class.redcrumbs_options[:only].include?(k.to_sym)}
|
12
|
+
end
|
13
|
+
|
14
|
+
def storeable_attributes
|
15
|
+
attributes.reject {|k,v| !self.class.redcrumbs_options[:store].include?(k.to_sym)}
|
16
|
+
end
|
17
|
+
|
18
|
+
def watched_changes_empty?
|
19
|
+
watched_changes.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def notify_changes
|
23
|
+
unless watched_changes.empty?
|
24
|
+
n = Crumb.build_from(self)
|
25
|
+
n.save
|
26
|
+
end
|
27
|
+
yield
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Redcrumbs
|
2
|
+
module Options
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# prepare_redcrumbed_options prepares class level options that customise the behaviour of
|
7
|
+
# redcrumbed. See documentation for a full explanation of redcrumbed options.
|
8
|
+
def prepare_redcrumbed_options(options)
|
9
|
+
defaults = {
|
10
|
+
:only => [],
|
11
|
+
:store => []
|
12
|
+
}
|
13
|
+
|
14
|
+
options.reverse_merge!(defaults)
|
15
|
+
|
16
|
+
options[:only] = Array(options[:only])
|
17
|
+
options[:store] = Array(options[:store])
|
18
|
+
|
19
|
+
class_inheritable_accessor :redcrumbs_options
|
20
|
+
class_inheritable_accessor :redcrumbs_callback_options
|
21
|
+
|
22
|
+
self.redcrumbs_options = options.dup
|
23
|
+
self.redcrumbs_callback_options = options.dup.select {|k,v| [:if, :unless].include?(k.to_sym)}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Redcrumbs
|
2
|
+
# Provides methods for giving user context to crumbs. Retrieves crumbs created by a user (creator) or
|
3
|
+
# affecting a user (target)
|
4
|
+
module Users
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module InstanceMethods
|
8
|
+
# Retrieves crumbs related to the user
|
9
|
+
def crumbs_for
|
10
|
+
Crumb.all(:target_id => self[Redcrumbs.target_primary_key], :order => [:created_at.desc])
|
11
|
+
end
|
12
|
+
|
13
|
+
# Retrieves crumbs created by the user
|
14
|
+
def crumbs_by
|
15
|
+
Crumb.all(:creator_id => self[Redcrumbs.creator_primary_key], :order => [:created_at.desc])
|
16
|
+
end
|
17
|
+
|
18
|
+
# A limitable collection of both crumbs_for and crumbs_by
|
19
|
+
# This is an unforunate hack to get over the redis dm adapter's non-support of addition (OR) queries
|
20
|
+
def crumbs_as_user(opts = {})
|
21
|
+
opts[:limit] ||= 100
|
22
|
+
arr = crumbs_for
|
23
|
+
arr += crumbs_by
|
24
|
+
arr.all(:limit => opts[:limit])
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creator method defines who should be considered the creator when a model is updated. This
|
28
|
+
# can be overridden in the redcrumbed model to define who the creator should be. Defaults
|
29
|
+
# to the current user (or creator class) associated with the model.
|
30
|
+
def creator
|
31
|
+
send(Redcrumbs.creator_class_sym) if respond_to?(Redcrumbs.creator_class_sym)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/redcrumbs.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/dependencies/autoload'
|
3
|
+
require "redcrumbs/version"
|
4
|
+
require 'redcrumbs/engine'
|
5
|
+
require 'dm-core'
|
6
|
+
|
7
|
+
# Redcrumbs implements dirty models to track changes to ActiveRecord models in a way that is fast and
|
8
|
+
# unobtrusive. By storing the data in Redis instead of a SQL database the footprint is greatly reduced, no
|
9
|
+
# schema changes are necessary and we can harness all the advantages of a key value store; such as key expiry.
|
10
|
+
#
|
11
|
+
# Author:: John Hope
|
12
|
+
# Copyright:: Copyright (c) 2012 John Hope for Project Zebra
|
13
|
+
# License:: MIT License (http://www.opensource.org/licenses/mit-license.php)
|
14
|
+
#
|
15
|
+
# To start tracking a model use the 'redcrumbed' method:
|
16
|
+
#
|
17
|
+
# class Venue
|
18
|
+
# redcrumbed, :only => [:name, :latlng]
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# venue = Venue.last
|
22
|
+
# venue.crumbs(:limit => 20)
|
23
|
+
# crumb = venue.crumbs.last
|
24
|
+
# crumb.creator
|
25
|
+
# => #<User ... >
|
26
|
+
# crumb.modifications
|
27
|
+
# => {"name"=>["Belfast City Hall", "The City Hall, Belfast"]}
|
28
|
+
#
|
29
|
+
# See the documentation for more details on how to customise and use Redcrumbs.
|
30
|
+
|
31
|
+
module Redcrumbs
|
32
|
+
extend ActiveSupport::Concern
|
33
|
+
extend ActiveSupport::Autoload
|
34
|
+
|
35
|
+
autoload :Options
|
36
|
+
autoload :Config
|
37
|
+
autoload :Users
|
38
|
+
autoload :Creation
|
39
|
+
|
40
|
+
include Config
|
41
|
+
|
42
|
+
def self.setup
|
43
|
+
yield self
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.included(base)
|
47
|
+
base.extend(ClassMethods)
|
48
|
+
end
|
49
|
+
|
50
|
+
module ClassMethods
|
51
|
+
def redcrumbed(options = {})
|
52
|
+
include Options
|
53
|
+
include Users
|
54
|
+
include Creation
|
55
|
+
|
56
|
+
prepare_redcrumbed_options(options)
|
57
|
+
|
58
|
+
around_save :notify_changes, self.redcrumbs_callback_options
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
ActiveRecord::Base.class_eval { include Redcrumbs }
|
data/redcrumbs.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "redcrumbs/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "redcrumbs"
|
7
|
+
s.version = Redcrumbs::VERSION
|
8
|
+
s.authors = ["John Hope"]
|
9
|
+
s.email = ["info@midhirrecords.com"]
|
10
|
+
s.homepage = "https://github.com/projectzebra/Redcrumbs"
|
11
|
+
s.summary = %q{Fast and unobtrusive activity tracking of ActiveRecord models using DataMapper and Redis}
|
12
|
+
s.description = %q{Fast and unobtrusive activity tracking of ActiveRecord models using DataMapper and Redis}
|
13
|
+
|
14
|
+
s.rubyforge_project = "redcrumbs"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'data_mapper', '>= 1.2.0'
|
22
|
+
s.add_dependency 'dm-redis-adapter', '>= 0.6.2'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redcrumbs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John Hope
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-21 00:00:00.000000000 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: data_mapper
|
17
|
+
requirement: &2152286280 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.2.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2152286280
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: dm-redis-adapter
|
28
|
+
requirement: &2152285780 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2152285780
|
37
|
+
description: Fast and unobtrusive activity tracking of ActiveRecord models using DataMapper
|
38
|
+
and Redis
|
39
|
+
email:
|
40
|
+
- info@midhirrecords.com
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- app/models/redcrumbs/crumb.rb
|
50
|
+
- app/models/redcrumbs/crumb/expiry.rb
|
51
|
+
- app/models/redcrumbs/crumb/getters.rb
|
52
|
+
- app/models/redcrumbs/crumb/setters.rb
|
53
|
+
- lib/generators/redcrumbs/install_generator.rb
|
54
|
+
- lib/generators/redcrumbs/templates/initializer.rb
|
55
|
+
- lib/redcrumbs.rb
|
56
|
+
- lib/redcrumbs/config.rb
|
57
|
+
- lib/redcrumbs/creation.rb
|
58
|
+
- lib/redcrumbs/engine.rb
|
59
|
+
- lib/redcrumbs/options.rb
|
60
|
+
- lib/redcrumbs/users.rb
|
61
|
+
- lib/redcrumbs/version.rb
|
62
|
+
- redcrumbs.gemspec
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: https://github.com/projectzebra/Redcrumbs
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project: redcrumbs
|
84
|
+
rubygems_version: 1.6.2
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Fast and unobtrusive activity tracking of ActiveRecord models using DataMapper
|
88
|
+
and Redis
|
89
|
+
test_files: []
|