pfeed 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.document +5 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +20 -0
  4. data/LICENSE.txt +20 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.markdown +115 -0
  7. data/README.rdoc +231 -0
  8. data/Rakefile +44 -0
  9. data/VERSION +1 -0
  10. data/app/models/pfeed_delivery.rb +4 -0
  11. data/app/models/pfeed_item.rb +172 -0
  12. data/app/models/pfeeds/user_updated_attribute.rb +14 -0
  13. data/app/views/pfeeds/_pfeed.html.erb +14 -0
  14. data/app/views/pfeeds/_pfeed_item.html.erb +3 -0
  15. data/app/views/pfeeds/_user_updated_attribute.html.erb +4 -0
  16. data/db/migrate/0000_create_pfeed_items.rb +18 -0
  17. data/db/migrate/0001_create_pfeed_deliveries.rb +15 -0
  18. data/init.rb +18 -0
  19. data/install.rb +1 -0
  20. data/lib/generator/pfeed_customization/USAGE +10 -0
  21. data/lib/generator/pfeed_customization/pfeed_customization_generator.rb +29 -0
  22. data/lib/generator/pfeed_customization/templates/pfeed_model.rb +5 -0
  23. data/lib/generator/pfeed_customization/templates/pfeed_view.html.erb +5 -0
  24. data/lib/pfeed.rb +29 -0
  25. data/lib/pfeed/pfeed.rb +102 -0
  26. data/lib/pfeed/pfeed_utils.rb +21 -0
  27. data/lib/pfeed_utils.rb +21 -0
  28. data/lib/tasks/pfeed.rake +54 -0
  29. data/pfeed.gemspec +93 -0
  30. data/pfeed/.document +5 -0
  31. data/pfeed/Gemfile +13 -0
  32. data/pfeed/LICENSE.txt +20 -0
  33. data/pfeed/Rakefile +53 -0
  34. data/pfeed/test/helper.rb +18 -0
  35. data/pfeed/test/test_pfeed.rb +7 -0
  36. data/test/bk_lib/pfeed_test.rb +57 -0
  37. data/test/bk_lib/pfeed_utils_test.rb +11 -0
  38. data/test/helper.rb +20 -0
  39. data/test/lib/pfeed_test.rb +57 -0
  40. data/test/lib/pfeed_utils_test.rb +11 -0
  41. data/test/test_helper.rb +71 -0
  42. data/test/test_pfeed.rb +9 -0
  43. data/uninstall.rb +1 -0
  44. metadata +164 -0
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,4 @@
1
+ class PfeedDelivery < ActiveRecord::Base
2
+ belongs_to :pfeed_receiver, :polymorphic => true
3
+ belongs_to :pfeed_item
4
+ end
@@ -0,0 +1,172 @@
1
+ class PfeedItem < ActiveRecord::Base
2
+
3
+ serialize :data, Hash
4
+ serialize :participants, Array
5
+
6
+ belongs_to :originator, :polymorphic => true
7
+ belongs_to :participant, :polymorphic => true
8
+
9
+ has_many :pfeed_deliveries, :dependent => :destroy
10
+
11
+ attr_accessor :temp_references # this is an temporary Hash to hold references to temporary Objects
12
+
13
+ def self.log(ar_obj,method_name,method_name_in_past_tense,returned_result,*args_supplied_to_method,&block_supplied_to_method)
14
+
15
+ #puts "#{ar_obj.class.to_s},#{method_name},#{method_name_in_past_tense},#{returned_result},#{args_supplied_to_method.length}"
16
+
17
+ # optional :if => :test, or :unless => :test
18
+ if if_cond = ar_obj.pfeed_options[:if]
19
+ return unless ar_obj.send(if_cond)
20
+ elsif unless_cond = ar_obj.pfeed_options[:unless]
21
+ return if ar_obj.send(unless_cond)
22
+ end
23
+
24
+ raise ArgumentError, "originator object must to be saved" if ar_obj.new_record?
25
+
26
+ temp_references = Hash.new
27
+ temp_references[:originator] = ar_obj
28
+ temp_references[:participant] = nil
29
+ temp_references[:participant] = args_supplied_to_method[0] if args_supplied_to_method && args_supplied_to_method.length >= 1 && args_supplied_to_method[0].class < ActiveRecord::Base
30
+
31
+ pfeed_class_name = "#{ar_obj.class.to_s.underscore}_#{method_name_in_past_tense}".camelize # may be I could use .classify
32
+ constructor_options = { :originator_id => temp_references[:originator].id , :originator_type => temp_references[:originator].class.to_s , :participant_id => (temp_references[:participant] ? temp_references[:participant].id : nil) , :participant_type => (temp_references[:participant] ? temp_references[:participant].class.to_s : nil) } # there is a reason why I didnt use {:originator => temp_references[:originator]} , if originator is new record it might get saved here un intentionally
33
+
34
+
35
+ p_item = new_pfeed_item(pfeed_class_name, constructor_options, temp_references)
36
+ p_item.pack_data(method_name,method_name_in_past_tense,returned_result,*args_supplied_to_method,&block_supplied_to_method)
37
+
38
+
39
+ p_item.save!
40
+ #puts "Trying to deliver to #{ar_obj} #{ar_obj.pfeed_audience_hash[method_name.to_sym]}"
41
+ p_item.attempt_delivery(ar_obj,ar_obj.pfeed_audience_hash[method_name.to_sym]) # attempting the delivery of the feed
42
+ end
43
+
44
+ @@dj = (defined? Delayed) == "constant" && (instance_methods.include? 'send_later') #this means Delayed_job exists , so make use of asynchronous delivery of pfeed
45
+
46
+ def attempt_delivery (ar_obj,method_name_arr)
47
+ return if method_name_arr.empty?
48
+
49
+ if @@dj
50
+ send_later(:deliver,ar_obj,method_name_arr)
51
+ else # regular instant delivery
52
+ send(:deliver,ar_obj,method_name_arr)
53
+ end
54
+ end
55
+
56
+ def deliver(ar_obj,method_name_arr)
57
+ method_name_arr.map { |method_name|
58
+ ar_obj.send(method_name)
59
+ }.flatten.uniq.map {|o| deliver_to(o) }.compact
60
+ end
61
+
62
+ def deliver_to(result_obj)
63
+ return nil unless result_obj != nil && begin
64
+ result_obj.is_pfeed_receiver
65
+ rescue NoMethodError
66
+ raise NoMethodError, "you must use the receives_pfeed macro for class: #{result_obj.class}"
67
+ end
68
+
69
+ if !result_obj.new_record?
70
+ delivery = PfeedDelivery.new
71
+ delivery.pfeed_item = self
72
+ delivery.pfeed_receiver = result_obj
73
+ delivery.save!
74
+ end
75
+
76
+ return result_obj
77
+ end
78
+
79
+ def accessible?
80
+ true
81
+ end
82
+
83
+ def view_template_name
84
+ "#{self.class.to_s.underscore}".split("/").last
85
+ end
86
+
87
+ def audience
88
+ # return list of objects to whom feed gets delivered
89
+ end
90
+
91
+ def pack_data(method_name,method_name_in_past_tense,returned_result,*args_supplied_to_method,&block_supplied_to_method)
92
+ self.data = {} if ! self.data
93
+ action_string = method_name_in_past_tense.humanize.downcase
94
+ hash_to_be_merged = {:action_string => action_string, :originator_identity => guess_identification(originator)}
95
+
96
+ if current_user = Thread.current[:current_user]
97
+ hash_to_be_merged.merge!(:current_user_identity => guess_identification(current_user))
98
+ end
99
+
100
+ self.data.merge! hash_to_be_merged
101
+ end
102
+
103
+ IDENTIFICATIONS = {}
104
+ def guess_identification(ar_obj)
105
+ if identifier = ar_obj.respond_to?(:pfeed_options) && ar_obj.pfeed_options[:pfeed_identification]
106
+ return ar_obj.send(identifier)
107
+ end
108
+
109
+ if attribute = IDENTIFICATIONS[ar_obj.class]
110
+ if (identi = ar_obj.read_attribute(attribute)).blank?
111
+ identi = ar_obj.send(attribute) rescue nil
112
+ end
113
+ return identi if identi
114
+ end
115
+
116
+ possible_attributes = ["username","login","name","company_name","first_name","last_name","login_name","login_id","given_name","nick_name","nick","short_name"]
117
+
118
+ possible_attributes = self.data[:config][:identifications] + possible_attributes if self.data[:config] && self.data[:config][:identifications] && self.data[:config][:identifications].is_a?(Array)
119
+ matched_name = ar_obj.attribute_names & possible_attributes # intersection of two sets
120
+
121
+ identi = nil
122
+ matched_name.each do |attribute|
123
+ result = ar_obj.read_attribute(attribute)
124
+ next unless result.present? && result.kind_of?(String)
125
+ IDENTIFICATIONS[ar_obj.class] = attribute
126
+ identi = result
127
+ break
128
+ end
129
+
130
+ if identi.blank?
131
+ possible_attributes.each do |attribute|
132
+ next unless ar_obj.respond_to? attribute
133
+ result = ar_obj.send(attribute) rescue nil
134
+ next unless result.present? && result.kind_of?(String)
135
+ IDENTIFICATIONS[ar_obj.class] = attribute
136
+ identi = result
137
+ break
138
+ end
139
+ end
140
+
141
+ identi = "#{ar_obj.class.to_s}(\##{ar_obj.id})" if identi.blank?
142
+ identi
143
+ end
144
+
145
+ # look for custom pfeed class, with or withour Pfeed:: prefix
146
+ CUSTOM_CLASSES = {}
147
+ def self.new_pfeed_item(pfeed_class_name, constructor_options, temp_references)
148
+ if (klass = CUSTOM_CLASSES[pfeed_class_name]).nil?
149
+ retried = false
150
+ begin
151
+ #puts "Attempting to create object of #{pfeed_class_name} "
152
+ klass = pfeed_class_name.constantize
153
+ (CUSTOM_CLASSES[pfeed_class_name] = klass).new(
154
+ constructor_options.merge(:temp_references => temp_references))
155
+ rescue NameError
156
+ unless retried
157
+ CUSTOM_CLASSES[pfeed_class_name] = false
158
+ retried = true
159
+ pfeed_class_name = "Pfeeds::"+pfeed_class_name
160
+ retry
161
+ end
162
+ PfeedItem.new(constructor_options)
163
+ end
164
+ else
165
+ if !klass
166
+ PfeedItem.new(constructor_options)
167
+ else
168
+ klass.new(constructor_options)
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,14 @@
1
+ class Pfeeds::UserUpdatedAttribute < PfeedItem
2
+
3
+ def pack_data(method_name,method_name_in_past_tense,returned_result,*args_supplied_to_method,&block_supplied_to_method)
4
+ super
5
+ self.data = {} if ! self.data
6
+ attribute_name = args_supplied_to_method[0].to_s.humanize
7
+ hash_to_be_merged = {:attribute_name => attribute_name}
8
+
9
+ self.data.merge! hash_to_be_merged
10
+ end
11
+
12
+ end
13
+
14
+
@@ -0,0 +1,14 @@
1
+ <ul class="pfeed_container">
2
+ <li>
3
+ <%=
4
+ begin
5
+ render(:partial => "pfeeds/#{pfeed.view_template_name}", :object => pfeed)
6
+ rescue
7
+ "<!-- error in #{pfeed.view_template_name}: #{$!.to_s.split("\n").join("-->\n<!--")} -->"
8
+ end
9
+ %>
10
+ </li>
11
+ </ul>
12
+
13
+
14
+
@@ -0,0 +1,3 @@
1
+ <%= pfeed_item.guess_identification(pfeed_item.originator) -%>
2
+ <%= pfeed_item.data[:action_string] -%>
3
+ about <%= time_ago_in_words(pfeed_item.created_at) -%> ago
@@ -0,0 +1,4 @@
1
+
2
+ <font color="blue"><%= user_updated_attribute.guess_identification(user_updated_attribute.originator) %></font>
3
+ <%= user_updated_attribute.data[:action_string] %> <%= user_updated_attribute.data[:attribute_name] %>
4
+ about <%= time_ago_in_words(user_updated_attribute.created_at) %> ago
@@ -0,0 +1,18 @@
1
+ class CreatePfeedItems < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :pfeed_items do |t|
4
+ t.string :type
5
+ t.integer :originator_id
6
+ t.string :originator_type
7
+ t.integer :participant_id
8
+ t.string :participant_type
9
+ t.text :data
10
+ t.datetime :expiry
11
+ t.timestamps
12
+ end
13
+ end
14
+
15
+ def self.down
16
+ drop_table :pfeed_items
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ class CreatePfeedDeliveries < ActiveRecord::Migration
2
+ def self.up
3
+
4
+ create_table :pfeed_deliveries do |t|
5
+ t.integer :pfeed_receiver_id
6
+ t.string :pfeed_receiver_type
7
+ t.integer :pfeed_item_id
8
+ t.timestamps
9
+ end
10
+ end
11
+
12
+ def self.down
13
+ drop_table :pfeed_deliveries
14
+ end
15
+ end
data/init.rb ADDED
@@ -0,0 +1,18 @@
1
+ # Author: Er Abhishek Parolkar
2
+
3
+ require File.dirname(__FILE__) + '/lib/pfeed'
4
+ require File.dirname(__FILE__) + '/lib/pfeed_utils'
5
+ ActiveRecord::Base.send(:include, ParolkarInnovationLab::SocialNet)
6
+
7
+ ActionController::Base.helper do
8
+ def pfeed_content(pfeed) #FIXME: interesting idea , but currently un-supported
9
+ controller.send('render_to_string',
10
+ :partial => "pfeeds/#{pfeed.view_template_name}.html.erb", :locals => {:object => pfeed})
11
+ end
12
+
13
+ def pfeed_item_url(pfeed_item)
14
+ # same as: polymorphic_url pfeed_item.originator
15
+ # but no need to query the database
16
+ send(pfeed_item.originator_type.underscore + '_url', pfeed_item.originator_id)
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,10 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate pfeed_customization user create
6
+
7
+ This will create:
8
+ app/model/pfeeds/user_created.rb
9
+ app/views/pfeeds/_user_created.html.erb
10
+
@@ -0,0 +1,29 @@
1
+ class PfeedCustomizationGenerator < Rails::Generators::Base
2
+ attr_reader :past_classname
3
+ attr_reader :past_varname
4
+
5
+ source_root File.expand_path('../templates', __FILE__)
6
+ argument :model, :type => :string, :required => true
7
+ argument :action_name, :type => :string,:required => true
8
+
9
+ def initialize_pfeed_customization
10
+ raise "#{model.to_s.classify} must define '#{action_name.underscore}' method" unless model.to_s.classify.constantize.methods.include? action_name.underscore
11
+
12
+ @model = model
13
+ @current_action = action_name.to_s.underscore
14
+ @past_action = ParolkarInnovationLab::SocialNet::PfeedUtils.attempt_pass_tense(@current_action)
15
+ @past = @model.downcase + '_' + @past_action
16
+ @past_classname = @model.capitalize + @past_action.capitalize
17
+ @past_varname = @model.downcase + '_' + @past_action.downcase
18
+ @model_filename = @past + '.rb'
19
+ @view_filename = '_' + @past + '.html.erb'
20
+ end
21
+
22
+ def manifest
23
+ template('pfeed_model.rb', "app/models/pfeeds/#{@model_filename}")
24
+ template('pfeed_view.html.erb', "app/views/pfeeds/#{@view_filename}")
25
+
26
+ end
27
+
28
+
29
+ end
@@ -0,0 +1,5 @@
1
+ class Pfeeds::<%= past_classname %> < PfeedItem
2
+ def pack_data(method_name,method_name_in_past_tense,returned_result,*args_supplied_to_method,&block_supplied_to_method)
3
+ super
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ <%%= <%= past_varname %>.guess_identification(<%= past_varname %>.originator)%>
2
+
3
+ <%%= <%= past_varname %>.data[:action_string] %>
4
+
5
+ about <%%= time_ago_in_words(<%= past_varname %>.created_at) %> ago
@@ -0,0 +1,29 @@
1
+ # Author: Er Abhishek Parolkar
2
+ if defined?(Rails)
3
+ module AuditTrail
4
+ require "rails"
5
+ require 'rubygems'
6
+ require 'rails'
7
+ require 'active_record'
8
+ require 'action_controller'
9
+ require 'active_support'
10
+ require 'pfeed/pfeed'
11
+ end
12
+ end
13
+
14
+
15
+
16
+ ActiveRecord::Base.send(:include, ParolkarInnovationLab::SocialNet)
17
+
18
+ ActionController::Base.helper do
19
+ def pfeed_content(pfeed) #FIXME: interesting idea , but currently un-supported
20
+ controller.send('render_to_string',
21
+ :partial => "pfeeds/#{pfeed.view_template_name}.html.erb", :locals => {:object => pfeed})
22
+ end
23
+
24
+ def pfeed_item_url(pfeed_item)
25
+ # same as: polymorphic_url pfeed_item.originator
26
+ # but no need to query the database
27
+ send(pfeed_item.originator_type.underscore + '_url', pfeed_item.originator_id)
28
+ end
29
+ end
@@ -0,0 +1,102 @@
1
+ #snippet: https://gist.github.com/89e92409ca9016d2d919
2
+
3
+ module ParolkarInnovationLab
4
+ module SocialNet
5
+ def self.included(base)
6
+ base.extend ParolkarInnovationLab::SocialNet::ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def emits_pfeeds arg_hash # {:on => [] , :for => [:itself , :all_in_its_class], :identified_by => :name, :if => :passes_test?}
12
+ arg_hash.assert_valid_keys(:on,:for,:if,:unless,:identified_by)
13
+ [:on, :for].each do |argument|
14
+ raise ArgumentError, "Expected an argument: #{argument}" if !arg_hash[argument]
15
+ end
16
+
17
+ include ParolkarInnovationLab::SocialNet::InstanceMethods
18
+
19
+ method_name_array = [*arg_hash[:on]]
20
+ class_inheritable_hash :pfeed_audience_hash
21
+
22
+ method_name_array.each{|method_name| register_pfeed_audience(method_name,[*arg_hash[:for]].compact) }
23
+
24
+ class_inheritable_hash :pfeed_options
25
+ write_inheritable_hash :pfeed_options, arg_hash.slice(:if,:unless,:identified_by)
26
+
27
+
28
+
29
+ method_name_array.each { |method_name|
30
+ method, symbol = method_name.to_s.split /(\!|\?)/
31
+ symbol = '' if symbol.nil?
32
+
33
+ method_to_define = method + '_with_pfeed' + symbol
34
+ method_to_be_called = method + '_without_pfeed' + symbol
35
+ eval %[
36
+
37
+ module ::ParolkarInnovationLab::SocialNet::PfeedTemp::#{self.to_s}
38
+ def #{method_to_define}(*a, &b)
39
+ returned_result = #{method_to_be_called}(*a , &b)
40
+ method_name_in_past_tense = "#{ParolkarInnovationLab::SocialNet::PfeedUtils.attempt_pass_tense(method)}"
41
+ PfeedItem.log(self,"#{method_name}",method_name_in_past_tense,returned_result,*a,&b)
42
+ returned_result
43
+ end
44
+ end
45
+ ]
46
+
47
+ }
48
+
49
+ #TODO : Pfeed.log(self,"#{method_name}",method_name_in_past_tense,returned_result,*a,*b) : this is to be done in a different thread in bg to boost performance & also needs exception handling such that parent call never breaks
50
+
51
+ include "::ParolkarInnovationLab::SocialNet::PfeedTemp::#{self.to_s}".constantize # why this? because "define_method((method + '_with_pfeed' + symbol).to_sym) do |*a , &b|" generates syntax error in ruby < 1.8.7
52
+
53
+ method_name_array.each { |method_name|
54
+ method, symbol = method_name.to_s.split /(\!|\?)/
55
+ symbol = '' if symbol.nil?
56
+ alias_method_chain (method + symbol), :pfeed
57
+ }
58
+
59
+ has_many :pfeed_items , :as => :originator , :dependent => :destroy #when originator is deleted the pfeed_items gets destroyed too
60
+
61
+
62
+ end
63
+
64
+ def receives_pfeed
65
+ has_many :pfeed_deliveries , :as => :pfeed_receiver
66
+ has_many :pfeed_inbox, :class_name => 'PfeedItem', :foreign_key => "pfeed_item_id" , :through => :pfeed_deliveries , :source => :pfeed_item
67
+
68
+ write_inheritable_attribute(:is_pfeed_receiver,true)
69
+ class_inheritable_reader :is_pfeed_receiver
70
+ end
71
+
72
+ def register_pfeed_audience(method_name,audience_arr)
73
+ write_inheritable_hash(:pfeed_audience_hash, { method_name.to_sym => audience_arr }) # this does a merge
74
+ end
75
+ end
76
+
77
+ module PfeedTemp
78
+ # Required for temporarily injecting new methods
79
+ end
80
+ module InstanceMethods
81
+
82
+ def itself
83
+ self
84
+ end
85
+ def all_in_its_class
86
+ self.class.find :all
87
+ end
88
+
89
+ def pfeed_recent_item_timestamp
90
+ self.pfeed_deliveries.last.created_at
91
+ rescue
92
+ nil
93
+ end
94
+
95
+ private
96
+ #let private methods come here
97
+ end
98
+ end
99
+ end
100
+
101
+
102
+