lifestreamable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +11 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +103 -0
- data/Rakefile +21 -0
- data/generators/lifestreamable_migration/lifestreamable_migration_generator.rb +20 -0
- data/generators/lifestreamable_migration/templates/migration.rb +21 -0
- data/lib/lifestreamable/create_observer.rb +10 -0
- data/lib/lifestreamable/destroy_observer.rb +10 -0
- data/lib/lifestreamable/lifestream.rb +108 -0
- data/lib/lifestreamable/lifestreamable.rb +194 -0
- data/lib/lifestreamable/lifestreamed.rb +88 -0
- data/lib/lifestreamable/lifestreamer.rb +26 -0
- data/lib/lifestreamable/observer.rb +20 -0
- data/lib/lifestreamable/update_observer.rb +11 -0
- data/lib/lifestreamable.rb +9 -0
- data/lifestreamable.gemspec +52 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/test_helper.rb +3 -0
- data/test/test_lifestreamable.rb +13 -0
- metadata +84 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/PostInstall.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
= lifestreamable
|
2
|
+
|
3
|
+
* github.com/benoitgoyette/lifestreamable
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Lifestreamable is a rails gem that allows social network life lifestream operations. A lifestream is a series of events that occured and that are related to an owner.
|
8
|
+
It has been designed to collect data upfront in model observers to minimize the number of request done at display time. the goal being that if the lifestream dislays several types of data over several different models, then only a single query will be run to get all the data to display instead of querying all data for each model. this radiaclly cuts down on display time.
|
9
|
+
This is a port to a gem of the lifestream libraries that have been designed for the sports social network legrandclub.rds.ca
|
10
|
+
|
11
|
+
== FEATURES/PROBLEMS:
|
12
|
+
|
13
|
+
TODO:
|
14
|
+
add support for pagination.
|
15
|
+
add support for delayed jobs.
|
16
|
+
port to rails 3.0
|
17
|
+
write the tests
|
18
|
+
complete the doc on which options are available for lifestreamable and lifestreamed
|
19
|
+
|
20
|
+
== SYNOPSIS:
|
21
|
+
|
22
|
+
Ths lifestream modules is made up of 2 mixins, the lifestreamable, and lifestreamed modules.
|
23
|
+
The lifestreamed module is included for models that own events, while the lifestreamable module is included on each model that triggers the event.
|
24
|
+
|
25
|
+
for example, a user can write posts and comments, we want to report in the user's lifestream that the user has written posts and comments.
|
26
|
+
|
27
|
+
|
28
|
+
defining the owner class
|
29
|
+
class User < ActiveRecord::Base
|
30
|
+
lifestreamed
|
31
|
+
end
|
32
|
+
|
33
|
+
defining the event classes
|
34
|
+
class Post < ActiveRecord::Base
|
35
|
+
belongs_to :user
|
36
|
+
has_many :comments
|
37
|
+
lifestreamable :on=>[:create, :update, :destroy], data=>:get_data, :owner=>:user
|
38
|
+
|
39
|
+
def get_data # this method must return a data structure that is serializable by YAML
|
40
|
+
{
|
41
|
+
:user=>{:firstname=>self.user.firstname, :lastname=>self.user.lastname},
|
42
|
+
:post=>{:title=>self.title}
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Comment < ActiveRecord::Base
|
48
|
+
belongs_to :user
|
49
|
+
belongs_to :post
|
50
|
+
# another way to get the data is through a Proc
|
51
|
+
lifestreamable :on=>[:create, :update, :destroy], :owner=>:user, data=> lambda {|model|
|
52
|
+
{
|
53
|
+
:user=>{:firstname=>model.user.firstname, :lastname=>model.user.lastname},
|
54
|
+
:post=>{:title=>model.post.title},
|
55
|
+
:comment=>{:body=>model.body}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
get the lifestream from the owner
|
61
|
+
user=User.first
|
62
|
+
|
63
|
+
# get the lifestream for the user
|
64
|
+
lifestream = user.lifestream #=> returns an array of Lifestreamable::Lifesteam instances
|
65
|
+
|
66
|
+
#get the data that was stored
|
67
|
+
data = lifestream.first.object_data
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
== REQUIREMENTS:
|
72
|
+
|
73
|
+
* YAML
|
74
|
+
|
75
|
+
== INSTALL:
|
76
|
+
|
77
|
+
* sudo gem install lifestreamable
|
78
|
+
|
79
|
+
|
80
|
+
== LICENSE:
|
81
|
+
|
82
|
+
(The MIT License)
|
83
|
+
|
84
|
+
Copyright (c) 2010 Benoit Goyette
|
85
|
+
|
86
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
87
|
+
a copy of this software and associated documentation files (the
|
88
|
+
'Software'), to deal in the Software without restriction, including
|
89
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
90
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
91
|
+
permit persons to whom the Software is furnished to do so, subject to
|
92
|
+
the following conditions:
|
93
|
+
|
94
|
+
The above copyright notice and this permission notice shall be
|
95
|
+
included in all copies or substantial portions of the Software.
|
96
|
+
|
97
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
98
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
99
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
100
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
101
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
102
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
103
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/lifestreamable'
|
6
|
+
|
7
|
+
# Generate all the Rake tasks
|
8
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
9
|
+
# $hoe = Hoe.spec 'lifestreamable' do
|
10
|
+
# self.developer 'FIXME full name', 'FIXME email'
|
11
|
+
# self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
12
|
+
# self.rubyforge_name = self.name # TODO this is default value
|
13
|
+
# # self.extra_deps = [['activesupport','>= 2.0.2']]
|
14
|
+
#
|
15
|
+
# end
|
16
|
+
|
17
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
18
|
+
|
19
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
20
|
+
# remove_task :default
|
21
|
+
# task :default => [:spec, :features]
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class LifestreamableMigrationGenerator < Rails::Generator::Base
|
2
|
+
default_options :skip_migration => false
|
3
|
+
|
4
|
+
def manifest
|
5
|
+
record do |m|
|
6
|
+
unless options[:skip_migration]
|
7
|
+
m.migration_template "migration.rb", 'db/migrate', :migration_file_name => "create_lifestreams"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def add_options!(opt)
|
15
|
+
opt.separator ''
|
16
|
+
opt.separator 'Options:'
|
17
|
+
opt.on("--skip-migration", "Don't generate a migration") { |v| options[:skip_migration] = v }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateLifestreams < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :lifestreams do |t|
|
4
|
+
t.string :owner_type
|
5
|
+
t.integer :owner_id
|
6
|
+
t.string :stream_type
|
7
|
+
t.string :reference_type
|
8
|
+
t.integer :reference_id
|
9
|
+
t.text :object_data_hash
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index(:lifestreams, [:owner_id, :owner_type], :name=>'lifestreams_profil_id' )
|
14
|
+
add_index(:lifestreams, :stream_type, :name=>'lifestreams_stream_type' )
|
15
|
+
add_index(:lifestreams, [:reference_id, :reference_type], :name=>'lifestreams_stream_object' )
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.down
|
19
|
+
drop_table :lifestreams
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Lifestreamable
|
2
|
+
class CreateObserver < Lifestreamable::Observer
|
3
|
+
observe :"lifestreamable/dummy"
|
4
|
+
def after_create(model)
|
5
|
+
if model.lifestreamable?
|
6
|
+
Lifestreamable::Lifestreamer.push model.get_action_instead_of(:create), model.get_payload
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Lifestreamable
|
2
|
+
class DestroyObserver < Lifestreamable::Observer
|
3
|
+
observe :"lifestreamable/dummy"
|
4
|
+
def before_destroy(model)
|
5
|
+
if model.lifestreamable?
|
6
|
+
Lifestreamable::Lifestreamer.push model.get_action_instead_of(:destroy), model.get_payload
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Lifestreamable
|
2
|
+
class Lifestream < ActiveRecord::Base
|
3
|
+
DEFAULT_LIMIT_PER_PAGE = 25
|
4
|
+
DEFAULT_PAGE = 0
|
5
|
+
|
6
|
+
belongs_to :owner, :polymorphic => true
|
7
|
+
belongs_to :reference, :polymorphic => true
|
8
|
+
|
9
|
+
|
10
|
+
def object_data
|
11
|
+
YAML.load(self.object_data_hash)
|
12
|
+
end
|
13
|
+
alias_method :object_hash, :object_data
|
14
|
+
|
15
|
+
class << self
|
16
|
+
#
|
17
|
+
# CREATION DU LIFESTREAM
|
18
|
+
#
|
19
|
+
def process(action, struct)
|
20
|
+
# put explicitly the actions accepted
|
21
|
+
case action
|
22
|
+
when :create
|
23
|
+
create get_payload(struct)
|
24
|
+
when :update
|
25
|
+
l = Lifestream.find_by_reference_type_and_reference_id_and_stream_type(struct.reference_type, struct.reference_id, struct.stream_type)
|
26
|
+
l.update_attributes get_payload(struct) unless l.blank?
|
27
|
+
when :destroy
|
28
|
+
l = Lifestream.find_by_reference_type_and_reference_id_and_stream_type(struct.reference_type, struct.reference_id, struct.stream_type)
|
29
|
+
l.destroy unless l.blank?
|
30
|
+
else
|
31
|
+
raise LifestreamableException.new "unknown action #{action} in Lifestreamable::Lifestream.process"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# FINDERS
|
37
|
+
|
38
|
+
def find_by_reference_type_and_reference_id_and_stream_type(reference_type, reference_id, stream_type)
|
39
|
+
find(:last, :conditions=>['reference_type = ? and reference_id = ? and stream_type = ?', reference_type, reference_id, stream_type])
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_lifestream_for_owner(owner, *options)
|
43
|
+
opts = get_options_for_find owner, options[0]
|
44
|
+
results = find :all, opts
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_lifestream_for_group(owner, group, *options)
|
48
|
+
opts = get_options_for_group_find owner, group, options[0]
|
49
|
+
results = find :all, opts
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def get_options_for_find(owner, options)
|
55
|
+
opt = options.blank? ? {} : options.to_options
|
56
|
+
set_basic_options!(opt)
|
57
|
+
cond = get_condition_for_find(owner)
|
58
|
+
opt[:conditions] = if opt[:conditions].blank?
|
59
|
+
cond
|
60
|
+
else
|
61
|
+
cond[0]+= " AND #{opt[:conditions][0]}"
|
62
|
+
cond += opt[:conditions][1..-1]
|
63
|
+
cond
|
64
|
+
end
|
65
|
+
opt
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_options_for_group_find(owner, group, options)
|
69
|
+
opt = options.blank? ? {} : options.to_options
|
70
|
+
set_basic_options!(opt)
|
71
|
+
cond = get_condition_for_group_find(owner, group)
|
72
|
+
opt[:conditions] = if opt[:conditions].blank?
|
73
|
+
cond
|
74
|
+
else
|
75
|
+
cond[0]+= " AND #{opt[:conditions][0]}"
|
76
|
+
cond += opt[:conditions][1..-1]
|
77
|
+
cond
|
78
|
+
end
|
79
|
+
opt
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_basic_options!(opt)
|
83
|
+
opt[:limit]=opt[:per_page] ? opt.delete(:per_page) : DEFAULT_LIMIT_PER_PAGE
|
84
|
+
opt[:offset]=opt[:page] ? (opt.delete(:page)-1)*opt[:limit] : DEFAULT_PAGE
|
85
|
+
opt[:order]=opt[:order] ? opt[:order] : 'id desc'
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_condition_for_find(owner)
|
89
|
+
['owner_type = ? and owner_id = ? ', owner.class.name, owner.id]
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_condition_for_group_find(owner, group)
|
93
|
+
['owner_type = ? and owner_id in (?) ', owner.class.name, [owner.id] + group]
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_payload(struct)
|
97
|
+
hash_from_struct(struct)
|
98
|
+
end
|
99
|
+
|
100
|
+
def hash_from_struct(struct)
|
101
|
+
h = {}
|
102
|
+
struct.each_pair {|k,v| h[k.to_sym]=v}
|
103
|
+
h
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# Pour le lifestream, on ajoute une methode a ActiveRecord::Base
|
2
|
+
# pour savoir si on doit effacer une entree du lifestream a la
|
3
|
+
# place de faire un update.
|
4
|
+
# comme dans le cas de detail.youtube_username, si on efface
|
5
|
+
# la valeur, on veut effacer l'entree dans le lifestream.
|
6
|
+
# Meme chose pour l'insert, si on change le youtube user name,
|
7
|
+
# on veut que ca insere une nouvelle rangee dans le lifestream.
|
8
|
+
|
9
|
+
module Lifestreamable
|
10
|
+
Struct.new('LifestreamData', :reference_type, :reference_id, :owner_type, :owner_id, :stream_type, :object_data_hash)
|
11
|
+
TRUE_REGEX = /^[tT][rR][uU][eE]$/
|
12
|
+
FALSE_REGEX = /^[fF][aA][lL][sS][eE]$/
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
base.extend LifestreamableClassMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
module LifestreamableClassMethods
|
19
|
+
@@lifestream_options={}
|
20
|
+
|
21
|
+
def lifestream_options
|
22
|
+
@@lifestream_options
|
23
|
+
end
|
24
|
+
|
25
|
+
def lifestreamable(options)
|
26
|
+
include LifestreamableInstanceMethods
|
27
|
+
|
28
|
+
options.to_options
|
29
|
+
options[:on].each do |option_on|
|
30
|
+
case option_on
|
31
|
+
when :update
|
32
|
+
Lifestreamable::UpdateObserver.instance.add_class_observer self.class_name.constantize
|
33
|
+
when :create
|
34
|
+
Lifestreamable::CreateObserver.instance.add_class_observer self.class_name.constantize
|
35
|
+
when :destroy
|
36
|
+
Lifestreamable::DestroyObserver.instance.add_class_observer self.class_name.constantize
|
37
|
+
else
|
38
|
+
raise Exception.new("option \"#{option_on}\" is not supported for Lifestreamable")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@@lifestream_options = {:data=>options[:data], :on=>options[:on], :type=>options[:type], :owner=>options[:owner], :when=>options[:when], :filter=>options[:filter],
|
42
|
+
:destroy_instead_of_update=>options[:destroy_instead_of_update], :create_instead_of_update=>options[:create_instead_of_update],
|
43
|
+
:create_instead_of_destroy=>options[:create_instead_of_destroy], :update_instead_of_destroy=>options[:update_instead_of_destroy]}
|
44
|
+
end
|
45
|
+
|
46
|
+
def filter(lifestream)
|
47
|
+
puts "in lifestreamable.filter"
|
48
|
+
option = self.lifestream_options[:filter]
|
49
|
+
lifestream = case option
|
50
|
+
when Proc
|
51
|
+
option.call(self, lifestream)
|
52
|
+
when String, Symbol
|
53
|
+
send(option.to_s, lifestream) if respond_to?(option.to_s)
|
54
|
+
else
|
55
|
+
lifestream
|
56
|
+
end
|
57
|
+
lifestream
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
module LifestreamableInstanceMethods
|
63
|
+
def lifestream_options
|
64
|
+
self.class.lifestream_options
|
65
|
+
end
|
66
|
+
|
67
|
+
def lifestreamable?
|
68
|
+
case self.lifestream_options[:when]
|
69
|
+
when NilClass, TrueClass, :true, TRUE_REGEX
|
70
|
+
true
|
71
|
+
when FalseClass, :false, FALSE_REGEX
|
72
|
+
false
|
73
|
+
when Proc
|
74
|
+
self.lifestream_options[:when].call(self)
|
75
|
+
when String, Symbol
|
76
|
+
send(self.lifestream_options[:when].to_s)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_payload
|
81
|
+
reference_type, reference_id = get_reference
|
82
|
+
owner_type, owner_id = get_owner
|
83
|
+
stream_type = get_stream_type
|
84
|
+
data = get_lifestream_data.to_yaml
|
85
|
+
Struct::LifestreamData.new reference_type, reference_id, owner_type, owner_id, stream_type, data
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_action_instead_of(action)
|
89
|
+
case action
|
90
|
+
when :create
|
91
|
+
:create
|
92
|
+
when :update
|
93
|
+
if test_instead_option(lifestream_options[:create_instead_of_update])
|
94
|
+
:create
|
95
|
+
elsif test_instead_option(lifestream_options[:destroy_instead_of_update])
|
96
|
+
:destroy
|
97
|
+
else
|
98
|
+
:update
|
99
|
+
end
|
100
|
+
when :destroy
|
101
|
+
if test_instead_option(lifestream_options[:create_instead_of_destroy])
|
102
|
+
:create
|
103
|
+
elsif test_instead_option(lifestream_options[:destroy_instead_of_destroy])
|
104
|
+
:update
|
105
|
+
else
|
106
|
+
:destroy
|
107
|
+
end
|
108
|
+
else
|
109
|
+
raise LifestreamableException.new("The action #{action.to_s} is not a valid type of action")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
# if the option[:when] is not defined, then it's considered true
|
115
|
+
def get_reference
|
116
|
+
[self.class.name, self.id]
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_owner
|
120
|
+
return_vals = case self.lifestream_options[:owner]
|
121
|
+
when Proc
|
122
|
+
self.lifestream_options[:owner].call(self)
|
123
|
+
when String, Symbol
|
124
|
+
send(self.lifestream_options[:owner].to_s)
|
125
|
+
else
|
126
|
+
raise LifestreamableException.new("The lifestreamable :owner option is invalid")
|
127
|
+
end
|
128
|
+
|
129
|
+
case return_vals
|
130
|
+
when NilClass
|
131
|
+
LifestreamableException.new("The lifestreamable :owner option Proc must return either an ActiveRecord::Base subclass or an array of [class_name, id]")
|
132
|
+
when Array
|
133
|
+
if return_vals.length == 1
|
134
|
+
if return_vals.is_a?(ActiveRecord::Base)
|
135
|
+
return [return_vals.class.name, return_vals.id]
|
136
|
+
else
|
137
|
+
LifestreamableException.new("The lifestreamable :owner option Proc evaluation returned only 1 value, but it's not an ActiveRecord::Base")
|
138
|
+
end
|
139
|
+
else
|
140
|
+
return_vals[0,2]
|
141
|
+
end
|
142
|
+
when ActiveRecord::Base
|
143
|
+
return [return_vals.class.name, return_vals.id]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def get_stream_type
|
148
|
+
case self.lifestream_options[:type]
|
149
|
+
when NilClass
|
150
|
+
self.class.name.underscore
|
151
|
+
when Proc
|
152
|
+
self.lifestream_options[:type].call(self)
|
153
|
+
when String, Symbol
|
154
|
+
if self.respond_to?(self.lifestream_options[:type].to_s)
|
155
|
+
send(self.lifestream_options[:type].to_s)
|
156
|
+
else
|
157
|
+
self.lifestream_options[:type].to_s
|
158
|
+
end
|
159
|
+
else
|
160
|
+
raise LifestreamableException.new("The lifestreamable :type option is invalid")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_lifestream_data
|
165
|
+
case self.lifestream_options[:data]
|
166
|
+
when Proc
|
167
|
+
self.lifestream_options[:data].call(self)
|
168
|
+
when String, Symbol
|
169
|
+
send(self.lifestream_options[:data].to_s)
|
170
|
+
else
|
171
|
+
raise LifestreamableException.new("The lifestreamable :data option is invalid")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
def test_instead_option(option)
|
177
|
+
case option
|
178
|
+
when NilClass, FalseClass, :false, FALSE_REGEX
|
179
|
+
false
|
180
|
+
when TrueClass, :true, TRUE_REGEX
|
181
|
+
true
|
182
|
+
when Proc
|
183
|
+
option.call(self)
|
184
|
+
when String, Symbol
|
185
|
+
send(option.to_s)
|
186
|
+
end == true # make sure we return true/false
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
ActiveRecord::Base.observers << :"lifestreamable/update_observer"
|
192
|
+
ActiveRecord::Base.observers << :"lifestreamable/create_observer"
|
193
|
+
ActiveRecord::Base.observers << :"lifestreamable/destroy_observer"
|
194
|
+
ActiveRecord::Base.send(:include, Lifestreamable)
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Lifestreamable
|
2
|
+
module Lifestreamed
|
3
|
+
def self.included(base)
|
4
|
+
base.extend LifestreamedClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module LifestreamedClassMethods
|
8
|
+
@@lifestreamed_options={}
|
9
|
+
|
10
|
+
def lifestreamed_options
|
11
|
+
@@lifestreamed_options
|
12
|
+
end
|
13
|
+
|
14
|
+
def lifestreamed(*options)
|
15
|
+
include LifestreamedInstanceMethods
|
16
|
+
@@lifestreamed_options = options[0].blank? ? {} : options[0].to_options.clone if options[0]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module LifestreamedInstanceMethods
|
22
|
+
def lifestreamed_options
|
23
|
+
self.class.lifestreamed_options
|
24
|
+
end
|
25
|
+
|
26
|
+
def lifestream(*options)
|
27
|
+
opt, do_filter = get_options_and_filter(options[0])
|
28
|
+
lifestream = Lifestreamable::Lifestream.find_lifestream_for_owner(self, opt)
|
29
|
+
do_filter ? filter(lifestream) : lifestream
|
30
|
+
end
|
31
|
+
|
32
|
+
def group_lifestream(group, *options)
|
33
|
+
opt, do_filter = get_options_and_filter(options[0])
|
34
|
+
lifestream = Lifestreamable::Lifestream.find_lifestream_for_group(self, group, opt)
|
35
|
+
do_filter ? filter(lifestream) : lifestream
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def get_options_and_filter(options)
|
41
|
+
opt = options.blank? ? {} : options.to_options
|
42
|
+
order_opt = get_order_option(opt.has_key?(:order) ? opt[:order] : self.lifestreamed_options[:order] )
|
43
|
+
opt[:order]= order_opt unless order_opt.blank?
|
44
|
+
filter_option = opt.has_key?(:filter) ? opt.delete(:filter) : self.lifestreamed_options[:filter]
|
45
|
+
do_filter = get_filter_option( filter_option )
|
46
|
+
[opt, do_filter]
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_filter_option(option)
|
50
|
+
puts "getting filter option"
|
51
|
+
case option
|
52
|
+
when NilClass, FalseClass, :false, Lifestreamable::FALSE_REGEX
|
53
|
+
false
|
54
|
+
when TrueClass, :true, Lifestreamable::TRUE_REGEX
|
55
|
+
true
|
56
|
+
when Proc # make sure we return true/false
|
57
|
+
option.call(self) == true
|
58
|
+
when String, Symbol # make sure we return true/false
|
59
|
+
send(option.to_s) == true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_order_option(option)
|
64
|
+
case option
|
65
|
+
when Proc
|
66
|
+
option.call(self)
|
67
|
+
when String, Symbol
|
68
|
+
if self.respond_to?(option.to_s)
|
69
|
+
send(option.to_s)
|
70
|
+
else
|
71
|
+
option.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def filter(lifestream)
|
77
|
+
lf = lifestream.clone
|
78
|
+
types = lf.collect {|l| l.reference_type}
|
79
|
+
types.uniq!
|
80
|
+
types.each {|l|
|
81
|
+
lf = l.constantize.filter(lf) if l.constantize.respond_to?('filter')}
|
82
|
+
lf
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
ActiveRecord::Base.send(:include, Lifestreamable::Lifestreamed)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Lifestreamable
|
2
|
+
module Lifestreamer
|
3
|
+
@@stack=[]
|
4
|
+
def self.push(action, lifestream_struct)
|
5
|
+
@@stack.push [action, lifestream_struct]
|
6
|
+
@@stack.uniq!
|
7
|
+
end
|
8
|
+
def self.generate_lifestream
|
9
|
+
while (lifestream_entry=@@stack.shift)
|
10
|
+
begin
|
11
|
+
Lifestream.process(lifestream_entry[0], lifestream_entry[1])
|
12
|
+
rescue Exception => e
|
13
|
+
puts e.message, e.backtrace
|
14
|
+
# TODO PUT SOMETHING HERE!!!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
def self.clear
|
19
|
+
@@stack.clear
|
20
|
+
end
|
21
|
+
def self.inspect
|
22
|
+
@@stack.inspect
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
ActionController::Base.send(:after_filter, "Lifestreamable::Lifestreamer.generate_lifestream")
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Lifestreamable
|
2
|
+
class Observer < ActiveRecord::Observer
|
3
|
+
|
4
|
+
def add_class_observer(klass)
|
5
|
+
self.add_observer!(klass)
|
6
|
+
end
|
7
|
+
|
8
|
+
end
|
9
|
+
class Dummy
|
10
|
+
include Observable
|
11
|
+
class << self
|
12
|
+
def add_observer(*args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class LifestreamableException < Exception
|
20
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Lifestreamable
|
2
|
+
class UpdateObserver < Lifestreamable::Observer
|
3
|
+
observe :"lifestreamable/dummy"
|
4
|
+
def after_update(model)
|
5
|
+
if model.lifestreamable?
|
6
|
+
Lifestreamable::Lifestreamer.push model.get_action_instead_of(:update), model.get_payload
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require File.join(File.dirname(__FILE__),'lifestreamable/lifestreamable')
|
5
|
+
require File.join(File.dirname(__FILE__),'lifestreamable/lifestreamed')
|
6
|
+
|
7
|
+
module Lifestreamable
|
8
|
+
VERSION = '0.0.2'
|
9
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{lifestreamable}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Benoit Goyette"]
|
9
|
+
s.date = %q{2010-05-19}
|
10
|
+
s.description = %q{library to perform social network like lifetstream functions, this is the code used on the social network http://legrandclub.rds.ca}
|
11
|
+
s.email = %q{benoit.goyette@gmail.com}
|
12
|
+
s.files = ["History.txt",
|
13
|
+
"Manifest.txt",
|
14
|
+
"PostInstall.txt",
|
15
|
+
"README.rdoc",
|
16
|
+
"Rakefile",
|
17
|
+
"generators/lifestreamable_migration/lifestreamable_migration_generator.rb",
|
18
|
+
"generators/lifestreamable_migration/templates/migration.rb",
|
19
|
+
"lib/lifestreamable/create_observer.rb",
|
20
|
+
"lib/lifestreamable/destroy_observer.rb",
|
21
|
+
"lib/lifestreamable/lifestream.rb",
|
22
|
+
"lib/lifestreamable/lifestreamable.rb",
|
23
|
+
"lib/lifestreamable/lifestreamed.rb",
|
24
|
+
"lib/lifestreamable/lifestreamer.rb",
|
25
|
+
"lib/lifestreamable/observer.rb",
|
26
|
+
"lib/lifestreamable/update_observer.rb",
|
27
|
+
"lib/lifestreamable.rb",
|
28
|
+
"lifestreamable.gemspec",
|
29
|
+
"script/console",
|
30
|
+
"script/destroy",
|
31
|
+
"script/generate",
|
32
|
+
"test/test_helper.rb",
|
33
|
+
"test/test_lifestreamable.rb"]
|
34
|
+
s.homepage = %q{http://lab.pheromone.ca}
|
35
|
+
s.rdoc_options = ["--exclude", "."]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubyforge_project = %q{lifestreamable}
|
38
|
+
s.rubygems_version = %q{1.3.1}
|
39
|
+
s.summary = %q{a rails plugin to collect and report user social actions.}
|
40
|
+
|
41
|
+
|
42
|
+
# if s.respond_to? :specification_version then
|
43
|
+
# current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
# s.specification_version = 3
|
45
|
+
# if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
46
|
+
# else
|
47
|
+
# end
|
48
|
+
# else
|
49
|
+
# end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/lifestreamable.rb'}"
|
9
|
+
puts "Loading lifestreamable gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lifestreamable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Benoit Goyette
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-19 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: library to perform social network like lifetstream functions, this is the code used on the social network http://legrandclub.rds.ca
|
22
|
+
email: benoit.goyette@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- History.txt
|
31
|
+
- Manifest.txt
|
32
|
+
- PostInstall.txt
|
33
|
+
- README.rdoc
|
34
|
+
- Rakefile
|
35
|
+
- generators/lifestreamable_migration/lifestreamable_migration_generator.rb
|
36
|
+
- generators/lifestreamable_migration/templates/migration.rb
|
37
|
+
- lib/lifestreamable/create_observer.rb
|
38
|
+
- lib/lifestreamable/destroy_observer.rb
|
39
|
+
- lib/lifestreamable/lifestream.rb
|
40
|
+
- lib/lifestreamable/lifestreamable.rb
|
41
|
+
- lib/lifestreamable/lifestreamed.rb
|
42
|
+
- lib/lifestreamable/lifestreamer.rb
|
43
|
+
- lib/lifestreamable/observer.rb
|
44
|
+
- lib/lifestreamable/update_observer.rb
|
45
|
+
- lib/lifestreamable.rb
|
46
|
+
- lifestreamable.gemspec
|
47
|
+
- script/console
|
48
|
+
- script/destroy
|
49
|
+
- script/generate
|
50
|
+
- test/test_helper.rb
|
51
|
+
- test/test_lifestreamable.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://lab.pheromone.ca
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --exclude
|
59
|
+
- .
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project: lifestreamable
|
79
|
+
rubygems_version: 1.3.6
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: a rails plugin to collect and report user social actions.
|
83
|
+
test_files: []
|
84
|
+
|