fragmentary 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.
@@ -0,0 +1,62 @@
1
+ module Fragmentary
2
+
3
+ module FragmentsHelper
4
+
5
+ def cache_fragment(options)
6
+ no_cache = options.delete(:no_cache)
7
+ options.reverse_merge!(:user => current_user) if respond_to?(:current_user)
8
+ fragment = options.delete(:fragment) || Fragmentary::Fragment.base_class.root(options)
9
+ builder = CacheBuilder.new(fragment, template = self)
10
+ unless no_cache
11
+ cache fragment, :skip_digest => true do
12
+ yield(builder)
13
+ end
14
+ else
15
+ yield(builder)
16
+ end
17
+ self.output_buffer = WidgetParser.new(self).parse_buffer
18
+ end
19
+
20
+ def fragment_builder(options)
21
+ template = options.delete(:template)
22
+ options.reverse_merge!(:user => current_user) if respond_to?(:current_user)
23
+ CacheBuilder.new(Fragmentary::Fragment.base_class.existing(options), template)
24
+ end
25
+
26
+
27
+ class CacheBuilder
28
+ include ::ActionView::Helpers::CacheHelper
29
+ include ::ActionView::Helpers::TextHelper
30
+
31
+ attr_accessor :fragment, :template
32
+
33
+ def initialize(fragment, template)
34
+ @fragment = fragment
35
+ @template = template
36
+ end
37
+
38
+ def cache_child(options)
39
+ no_cache = options.delete(:no_cache)
40
+ insert_widgets = options.delete(:insert_widgets)
41
+ options.reverse_merge!(:user => template.current_user) if template.respond_to?(:current_user)
42
+ child = options.delete(:child) || fragment.child(options)
43
+ builder = CacheBuilder.new(child, template)
44
+ unless no_cache
45
+ template.cache child, :skip_digest => true do
46
+ yield(builder)
47
+ end
48
+ else
49
+ yield(builder)
50
+ end
51
+ template.output_buffer = WidgetParser.new(template).parse_buffer if insert_widgets
52
+ end
53
+
54
+ def method_missing(method, *args)
55
+ fragment.send(method, *args)
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,31 @@
1
+ module Fragmentary
2
+ class Handler
3
+ def self.all
4
+ @@all
5
+ end
6
+
7
+ def self.clear
8
+ @@all = []
9
+ end
10
+ self.clear
11
+
12
+ def self.create(**args)
13
+ @@all << (handler = self.new(args))
14
+ handler
15
+ end
16
+
17
+ def initialize(**args)
18
+ @args = args
19
+ end
20
+
21
+ def call
22
+ raise "Method 'call' not defined."
23
+ end
24
+ end
25
+ end
26
+
27
+ class ActiveRecord::Base
28
+ def to_h
29
+ attributes.symbolize_keys
30
+ end
31
+ end
@@ -0,0 +1,82 @@
1
+ require 'wisper/active_record'
2
+
3
+ module Fragmentary
4
+
5
+ module Publisher
6
+
7
+ def self.included(base)
8
+ base.instance_eval do
9
+ @class_registrations ||= Set.new
10
+ include Wisper.model
11
+ # ensures we override Wisper's definitions
12
+ include InstanceMethods
13
+ extend ClassMethods
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ private
20
+
21
+ def registrations
22
+ local_registrations + class_registrations + global_registrations + temporary_registrations
23
+ end
24
+
25
+ def class_registrations
26
+ self.class.registrations
27
+ end
28
+
29
+ def after_create_broadcast
30
+ Rails.logger.info "\n***** #{start = Time.now} broadcasting :after_create from #{self.class.name} #{self.id}\n"
31
+ broadcast(:after_create, self)
32
+ Rails.logger.info "\n***** #{Time.now} broadcast :after_create from #{self.class.name} #{self.id} took #{(Time.now - start) * 1000} ms\n"
33
+ end
34
+
35
+ def after_update_broadcast
36
+ broadcast(:after_update, self)
37
+ end
38
+
39
+ def after_destroy_broadcast
40
+ broadcast(:after_destroy, self)
41
+ end
42
+
43
+ def after_commit_broadcast
44
+ end
45
+ end
46
+
47
+ module ClassMethods
48
+ def subscribe(listener, options = {})
49
+ @class_registrations << ::Wisper::ObjectRegistration.new(listener, options.merge(:scope => self))
50
+ end
51
+
52
+ def registrations
53
+ @class_registrations + (superclass.try(:registrations) || [])
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+
60
+ module Wisper
61
+ class ObjectRegistration
62
+
63
+ # For the registration to broadcast a specified event we require:
64
+ # - 'should_broadcast?' - If the listener susbcribed with an ':on' option, ensure that the event
65
+ # is included in the 'on' list.
66
+ # - 'listener.respond_to?' - The listener contains a handler for the event
67
+ # - 'publisher_in_scope' - If the listener subscribed with a ':scope' option, ensure that the
68
+ # publisher's class is included in the 'scope' list.
69
+ def broadcast(event, publisher, *args)
70
+ method_to_call = map_event_to_method(event)
71
+ if should_broadcast?(event) && listener.respond_to?(method_to_call) && publisher_in_scope?(publisher)
72
+ broadcaster.broadcast(listener, publisher, method_to_call, args)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def publisher_in_scope?(publisher)
79
+ allowed_classes.empty? || (allowed_classes.include? publisher.class.name)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,30 @@
1
+ module Fragmentary
2
+
3
+ class Request
4
+ attr_reader :method, :path, :options, :parameters
5
+
6
+ def initialize(method, path, parameters=nil, options=nil)
7
+ @method, @path, @parameters, @options = method, path, parameters, options
8
+ end
9
+
10
+ def ==(other)
11
+ method == other.method and path == other.path and parameters == other.parameters and options == other.options
12
+ end
13
+
14
+ def to_proc
15
+ method = @method; path = @path; parameters = @parameters; options = @options.try :dup
16
+ if @options.try(:[], :xhr)
17
+ Proc.new do
18
+ puts " * Sending xhr request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
19
+ send(:xhr, method, path, parameters, options)
20
+ end
21
+ else
22
+ Proc.new do
23
+ puts " * Sending request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
24
+ send(method, path, parameters, options)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,139 @@
1
+ require 'fragmentary/user_session'
2
+
3
+ module Fragmentary
4
+
5
+ class RequestQueue
6
+
7
+ @@all = []
8
+
9
+ def self.all
10
+ @@all
11
+ end
12
+
13
+ attr_reader :requests, :user_type, :sender
14
+
15
+ def initialize(user_type)
16
+ @user_type = user_type
17
+ @requests = []
18
+ @sender = Sender.new(self)
19
+ @@all << self
20
+ end
21
+
22
+ def <<(request)
23
+ unless @requests.find{|r| r == request}
24
+ @requests << request
25
+ end
26
+ self
27
+ end
28
+
29
+ def size
30
+ @requests.size
31
+ end
32
+
33
+ def session
34
+ @session ||= new_session
35
+ end
36
+
37
+ def new_session
38
+ case user_type
39
+ when 'signed_in'
40
+ UserSession.new('Bob')
41
+ when 'admin'
42
+ UserSession.new('Alice', :admin => true)
43
+ else
44
+ UserSession.new
45
+ end
46
+ end
47
+
48
+ def next_request
49
+ @requests.shift
50
+ end
51
+
52
+ def clear
53
+ @requests = []
54
+ end
55
+
56
+ def clear_session
57
+ @session = nil
58
+ end
59
+
60
+ def remove_path(path)
61
+ requests.delete_if{|r| r.path == path}
62
+ end
63
+
64
+ def send(**args)
65
+ sender.start(args)
66
+ end
67
+
68
+ def method_missing(method, *args)
69
+ sender.send(method, *args)
70
+ end
71
+
72
+ class Sender
73
+ class << self
74
+ def jobs
75
+ ::Delayed::Job.where("(handler LIKE ?) OR (handler LIKE ?)", "--- !ruby/object:#{name} %", "--- !ruby/object:#{name}\n%")
76
+ end
77
+ end
78
+
79
+ attr_reader :queue
80
+
81
+ def initialize(queue)
82
+ @queue = queue
83
+ end
84
+
85
+ def next_request
86
+ queue.next_request.to_proc
87
+ end
88
+
89
+ def send_next_request
90
+ if queue.size > 0
91
+ queue.session.instance_exec(&(next_request))
92
+ end
93
+ end
94
+
95
+ def send_all_requests
96
+ while queue.size > 0
97
+ send_next_request
98
+ end
99
+ end
100
+
101
+ # Send all requests, either directly or by schedule
102
+ def start(delay: nil, between: nil)
103
+ Rails.logger.info "\n***** Processing request queue for user_type '#{queue.user_type}'\n"
104
+ @delay = delay; @between = between
105
+ if @delay or @between
106
+ schedule_requests(@delay)
107
+ # sending requests by schedule makes a copy of the sender and queue objects for
108
+ # asynchronous execution, so we have to manually clear out the original queue.
109
+ queue.clear
110
+ else
111
+ send_all_requests
112
+ end
113
+ end
114
+
115
+ def perform
116
+ Rails.logger.info "\n***** Processing request queue for user_type '#{queue.user_type}'\n"
117
+ @between ? send_next_request : send_all_requests
118
+ end
119
+
120
+ def success
121
+ schedule_requests(@between) if queue.size > 0
122
+ end
123
+
124
+ private
125
+
126
+ def schedule_requests(delay=0.seconds)
127
+ if queue.size > 0
128
+ queue.clear_session
129
+ Delayed::Job.transaction do
130
+ self.class.jobs.destroy_all
131
+ Delayed::Job.enqueue self, :run_at => delay.from_now
132
+ end
133
+ end
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,36 @@
1
+ require 'fragmentary/subscription'
2
+
3
+ module Fragmentary
4
+
5
+ # Each fragment subclass has a unique Subscriber instance reponsible for handling subscriptions
6
+ # to publishers. Each subscriber maintains a hash of Subscriptions, one for each publisher it
7
+ # subscribes to. The 'subscribe_to' method instantiates each new Subscription in turn and executes
8
+ # its block against against the Subscriber in order to define handlers for each publisher event
9
+ # of interest. Any other method invoked within a handler is delegated to the client, i.e. the
10
+ # fragment subclass that the subscriber is reponsible for.
11
+ class Subscriber
12
+ attr_reader :client, :subscriptions
13
+
14
+ def initialize(client)
15
+ @client = client
16
+ @subscriptions = Hash.new do |h, key|
17
+ if Object.const_defined?(key) and (publisher = key.constantize) < ActiveRecord::Base
18
+ h[key] = Subscription.new(publisher, self)
19
+ else
20
+ nil
21
+ end
22
+ end
23
+ end
24
+
25
+ def subscribe_to(publisher, block)
26
+ if subscriptions[publisher.name]
27
+ instance_exec(&block)
28
+ end
29
+ end
30
+
31
+ def method_missing(method, *args)
32
+ @client.send(method, *args)
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,85 @@
1
+ module Fragmentary
2
+
3
+ class Subscription
4
+
5
+ class Proxy
6
+
7
+ # Allow only one proxy per publisher; the proxy is responsible for subscribing
8
+ # to the publisher on behalf of individual subscriptions and calling handlers
9
+ # on each of them whenever the publisher broadcasts.
10
+ @@all = Hash.new do |h, key|
11
+ h[key] = Proxy.new(:publisher => key.constantize)
12
+ end
13
+
14
+ attr_reader :publisher
15
+
16
+ def self.fetch(key)
17
+ @@all[key]
18
+ end
19
+
20
+ def register(subscription)
21
+ subscriptions << subscription if subscription.is_a? Subscription
22
+ end
23
+
24
+ ['create', 'update', 'destroy'].each do |event|
25
+ class_eval <<-HEREDOC
26
+ def after_#{event}(record)
27
+ subscriptions.each do |subscription|
28
+ subscription.after_#{event}(record)
29
+ end
30
+ end
31
+ HEREDOC
32
+ end
33
+
34
+ private
35
+ def initialize(publisher:)
36
+ @publisher = publisher
37
+ @publisher.subscribe(self)
38
+ end
39
+
40
+ def subscriptions
41
+ @subscriptions ||= Set.new
42
+ end
43
+
44
+ end
45
+
46
+ include ActiveSupport::Callbacks
47
+ define_callbacks :after_destroy
48
+
49
+ attr_reader :subscriber
50
+ attr_accessor :record
51
+
52
+ def initialize(publisher, subscriber)
53
+ @subscriber = subscriber
54
+ Proxy.fetch(publisher.name).register(self)
55
+ end
56
+
57
+ def after_create(record)
58
+ call_method(:"create_#{record.class.model_name.param_key}_successful", record)
59
+ end
60
+
61
+ def after_update(record)
62
+ call_method(:"update_#{record.class.model_name.param_key}_successful", record)
63
+ end
64
+
65
+ def after_destroy(record)
66
+ # An ActiveSupport::Callbacks :after_destroy callback is set on the eigenclass of each individual
67
+ # subscription in Fragment.set_record_type in order to clean up fragments whose AR records are destroyed.
68
+ run_callbacks :after_destroy do
69
+ @record = record
70
+ call_method(:"destroy_#{record.class.model_name.param_key}_successful", record)
71
+ end
72
+ end
73
+
74
+ private
75
+ def call_method(method, record)
76
+ Rails.logger.info "***** Calling #{method.inspect} on #{subscriber.client.name} with record #{record.class.name} #{record.id}"
77
+ start = Time.now
78
+ subscriber.public_send(method, record) if subscriber.respond_to? method
79
+ finish = Time.now
80
+ Rails.logger.info "***** #{method.inspect} duration: #{(finish - start) * 1000}ms\n\n"
81
+ end
82
+
83
+ end
84
+
85
+ end