pfeed 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+