Module: Fragmentary::Fragment::ClassMethods
- Defined in:
- lib/fragmentary/fragment.rb
Overview
Defined Under Namespace
Modules: RecordClassMethods
Instance Method Summary
collapse
-
#acts_as_list_fragment(members:, list_record:, **options) ⇒ Object
This method defines the handler for the creation of new list items.
-
#attributes(options) ⇒ Object
Each fragment record is unique by type and parent_id (which is nil for a root_fragment) and for some types also by record_id (i.e. for root fragments for pages associated with particular AR records and for child fragments that appear in a list) user_type (e.g. "admin", "signed_in", "signed_out") and user_id (for fragments that include user-specific content).
-
#cache_store ⇒ Object
-
#child_search_key ⇒ Object
-
#existing(options) ⇒ Object
ToDo: combine this with Fragment.root.
-
#fragment_type ⇒ Object
-
#fragments_for_record(record_id) ⇒ Object
-
#inherited(subclass) ⇒ Object
Subclass-specific request_queues.
-
#key_name ⇒ Object
-
#list_record(association) ⇒ Object
Identifies the record_ids of list fragments associated with a specific membership association.
-
#needs?(attribute_name) ⇒ Boolean
-
#needs_key(options = {}) ⇒ Object
-
#needs_key? ⇒ Boolean
-
#needs_record_id(options = {}) ⇒ Object
If a class declares 'needs_record_id', a record_id value must be provided in the attributes hash in order to either create or retrieve a Fragment of that class.
-
#needs_record_id? ⇒ Boolean
-
#needs_user_id ⇒ Object
If a class declares 'needs_user_id', a user_id value must be provided in the attributes hash in order to either create or retrieve a Fragment of that class.
-
#needs_user_id? ⇒ Boolean
-
#needs_user_type(options = {}) ⇒ Object
If a class declares 'needs_user_type', a user_type value must be provided in the attributes hash in order to either create or retrieve a Fragment of that class.
-
#needs_user_type? ⇒ Boolean
-
#queue_request(request = nil) ⇒ Object
-
#record_type ⇒ Object
-
#remove_queued_request(host_url: nil, user:, request_path:) ⇒ Object
-
#request ⇒ Object
-
#request_method ⇒ Object
The instance method 'request_method' is defined in terms of this.
-
#request_options ⇒ Object
The instance method 'request_options' is defined in terms of this.
-
#request_parameters(*args) ⇒ Object
-
#request_queues ⇒ Object
There is one queue per user_type per application instance (the current app and any external instances).
-
#requestable? ⇒ Boolean
-
#root(options) ⇒ Object
-
#set_record_type(type) ⇒ Object
A subclass of a class declared with 'needs_record_id' will not have a record_type unless set explicitly, which can be done using the following method.
-
#subscribe_to(publisher, &block) ⇒ Object
-
#subscriber ⇒ Object
-
#touch_fragments_for_record(record_id) ⇒ Object
Note that fragments matching the specified attributes won't always exist, e.g.
-
#user_type(user) ⇒ Object
This default definition can be overridden by sub-classes as required (typically in root fragment classes by calling needs_user_type).
-
#user_types ⇒ Object
Instance Method Details
#acts_as_list_fragment(members:, list_record:, **options) ⇒ Object
This method defines the handler for the creation of new list items. The method takes:
- members: a symbol representing the association class whose records define membership
of the list,
- list_record: an association that when applied to a membership record identifies the record_id
associated with the list itself. This can be specified in the form of a symbol representing
a method to be applied to the membership association or a proc that takes the membership
association as an argument.
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
|
# File 'lib/fragmentary/fragment.rb', line 368
def acts_as_list_fragment(members:, list_record:, **options)
@members = members.to_s.singularize
@membership_class = @members.classify.constantize
@list_record = list_record
def list_record(association)
if @list_record.is_a? Symbol
association.send @list_record
elsif @list_record.is_a? Proc
@list_record.call(association)
end
end
if options.delete(:delay) == true
instance_eval <<-HEREDOC
class #{self.name}::Create#{@membership_class}Handler < Fragmentary::Handler
def call
association = @args
#{self.name}.touch_fragments_for_record(association[:#{@list_record.to_s}])
end
end
subscribe_to #{@membership_class} do
def create_#{@members}_successful(association)
#{self.name}::Create#{@membership_class}Handler.create(association.to_h)
end
end
HEREDOC
else
instance_eval <<-HEREDOC
subscribe_to #{@membership_class} do
def create_#{@members}_successful(association)
touch_fragments_for_record(list_record(association))
end
end
HEREDOC
end
instance_eval <<-HEREDOC
def self.child_search_key
:record_id
end
HEREDOC
end
|
#attributes(options) ⇒ Object
Each fragment record is unique by type and parent_id (which is nil for a root_fragment) and for some types also by
record_id (i.e. for root fragments for pages associated with particular AR records and for child fragments that
appear in a list) user_type (e.g. "admin", "signed_in", "signed_out") and user_id (for fragments that include
user-specific content).
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
# File 'lib/fragmentary/fragment.rb', line 66
def attributes(options)
klass = options.delete(:type).constantize
options.reverse_merge!(:user_type => klass.user_type(user = options.delete(:user)), :user_id => user.try(:id))
search_attributes = {}
if (parent_id = options.delete(:parent_id))
search_attributes.merge!(:parent_id => parent_id)
else
application_root_url_column = Fragmentary.config.application_root_url_column
if (application_root_url = options.delete(application_root_url_column)) && column_names.include?(application_root_url_column.to_s)
search_attributes.merge!(application_root_url_column => application_root_url)
end
end
[:record_id, :user_id, :user_type, :key].each do |attribute_name|
if klass.needs?(attribute_name)
option_name = (attribute_name == :key and klass.key_name) ? klass.key_name : attribute_name
attribute = options.delete(option_name) {puts caller(0); raise ArgumentError, "Fragment type #{klass} needs a #{option_name.to_s}"}
attribute = attribute.try :to_s if attribute_name == :key
search_attributes.merge!(attribute_name => attribute)
end
end
options.delete(:user_id); options.delete(:user_type)
return klass, search_attributes, options
end
|
#cache_store ⇒ Object
99
100
101
|
# File 'lib/fragmentary/fragment.rb', line 99
def cache_store
@@cache_store ||= Rails.application.config.action_controller.cache_store
end
|
#child_search_key ⇒ Object
331
332
333
|
# File 'lib/fragmentary/fragment.rb', line 331
def child_search_key
nil
end
|
#existing(options) ⇒ Object
ToDo: combine this with Fragment.root
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
# File 'lib/fragmentary/fragment.rb', line 104
def existing(options)
if fragment = options[:fragment]
raise ArgumentError, "You passed Fragment #{fragment.id} to Fragment.existing, but it's a child of Fragment #{fragment.parent_id}" if fragment.parent_id
else
options.merge!(:type => name) unless self == base_class
raise ArgumentError, "A 'type' attribute is needed in order to retrieve a fragment" unless options[:type]
klass, search_attributes, options = base_class.attributes(options)
fragment = klass.where(search_attributes.merge(options)).includes(:children).first
fragment.try :set_indexed_children if fragment.try :child_search_key
end
fragment
end
|
#fragment_type ⇒ Object
121
122
123
|
# File 'lib/fragmentary/fragment.rb', line 121
def fragment_type
self
end
|
#fragments_for_record(record_id) ⇒ Object
323
324
325
|
# File 'lib/fragmentary/fragment.rb', line 323
def fragments_for_record(record_id)
self.where(:record_id => record_id)
end
|
#inherited(subclass) ⇒ Object
Subclass-specific request_queues
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
# File 'lib/fragmentary/fragment.rb', line 144
def inherited(subclass)
subclass.instance_eval do
def request_queues
super @request_queues ||= begin
app_root_url = Fragmentary.application_root_url
remote_urls = Fragmentary.config.remote_urls
user_types.each_with_object( Hash.new {|hsh0, url| hsh0[url] = {}} ) do |user_type, hsh|
hsh[app_root_url][user_type] = @@request_queues[app_root_url][user_type]
if remote_urls.any?
unless Rails.application.routes.default_url_options[:host]
raise "Can't create external request queues without setting Rails.application.routes.default_url_options[:host]"
end
remote_urls.each {|remote_url| hsh[remote_url][user_type] = @@request_queues[remote_url][user_type]}
end
end
end
end
end
super
end
|
#key_name ⇒ Object
231
232
233
|
# File 'lib/fragmentary/fragment.rb', line 231
def key_name
@key_name ||= nil
end
|
#list_record(association) ⇒ Object
Identifies the record_ids of list fragments associated with a specific membership association.
This method will be called from the block passed to 'subscribe_to' below, which is executed
against the Subscriber, but sends missing methods back to its client, which is this class.
A ListFragment is not declared with 'needs_record_id'; by default it receives its record_id
from its parent fragment.
382
383
384
385
386
387
388
|
# File 'lib/fragmentary/fragment.rb', line 382
def list_record(association)
if @list_record.is_a? Symbol
association.send @list_record
elsif @list_record.is_a? Proc
@list_record.call(association)
end
end
|
#needs?(attribute_name) ⇒ Boolean
183
184
185
186
187
|
# File 'lib/fragmentary/fragment.rb', line 183
def needs?(attribute_name)
attribute_name = attribute_name.to_s if attribute_name.is_a? Symbol
raise ArgumentError unless attribute_name.is_a? String
send :"needs_#{attribute_name.to_s}?"
end
|
#needs_key(options = {}) ⇒ Object
223
224
225
226
227
228
229
|
# File 'lib/fragmentary/fragment.rb', line 223
def needs_key(options = {})
extend NeedsKey
if name = options.delete(:name) || options.delete(:key_name)
self.key_name = name.to_sym
define_method(key_name) {send(:key)}
end
end
|
#needs_key? ⇒ Boolean
312
313
314
|
# File 'lib/fragmentary/fragment.rb', line 312
def needs_key?
false
end
|
#needs_record_id(options = {}) ⇒ Object
If a class declares 'needs_record_id', a record_id value must be provided in the attributes hash in order to either
create or retrieve a Fragment of that class. Ordinarily a record_id is passed automatically from a parent fragment
to its child. However if the child fragment class is declared with 'needs_record_id' the parent's record_id is not
passed on and must be provided explicitly, typically for Fragment classes that represent items in a list that
each correspond to a particular record of some ActiveRecord class. In these cases the record_id should be provided
explicitly in the call to cache_fragment (for a root fragment) or cache_child (for a child fragment).
241
242
243
244
245
246
|
# File 'lib/fragmentary/fragment.rb', line 241
def needs_record_id(options = {})
self.extend NeedsRecordId
if record_type = options.delete(:record_type) || options.delete(:type)
set_record_type(record_type)
end
end
|
#needs_record_id? ⇒ Boolean
290
291
292
|
# File 'lib/fragmentary/fragment.rb', line 290
def needs_record_id?
false
end
|
#needs_user_id ⇒ Object
If a class declares 'needs_user_id', a user_id value must be provided in the attributes hash in order to either
create or retrieve a Fragment of that class. A user_id is needed for example when caching user-specific content
such as a user profile. When the fragment is instantiated using FragmentsHelper methods 'cache_fragment' or
'CacheBuilder.cache_child', a :user option is added to the options hash automatically from the value of 'current_user'.
The user_id is extracted from this option in Fragment.attributes.
194
195
196
|
# File 'lib/fragmentary/fragment.rb', line 194
def needs_user_id
self.extend NeedsUserId
end
|
#needs_user_id? ⇒ Boolean
294
295
296
|
# File 'lib/fragmentary/fragment.rb', line 294
def needs_user_id?
false
end
|
#needs_user_type(options = {}) ⇒ Object
If a class declares 'needs_user_type', a user_type value must be provided in the attributes hash in order to either
create or retrieve a Fragment of that class. A user_type is needed to distinguish between fragments that are rendered
differently depending on the type of user, e.g. to distinguish between content seen by signed in users and those not
signed in. When the fragment is instantiated using FragmentsHelper methods 'cache_fragment' or 'CacheBuilder.cache_child',
a :user option is added to the options hash automatically from the value of 'current_user'. The user_type is extracted
from this option in Fragment.attributes.
For each class that declares 'needs_user_type', a set of user_types is defined that determines the set of request_queues
that will be used to send requests to the application when a fragment is touched. By default these user_types are defined
globally using 'Fragmentary.setup' but they can alternatively be set on a class-specific basis by passing a :session_users
option to 'needs_user_type'. See 'Fragmentary.parse_session_users' for details.
209
210
211
212
213
214
215
216
217
218
219
220
221
|
# File 'lib/fragmentary/fragment.rb', line 209
def needs_user_type(options = {})
self.extend NeedsUserType
instance_eval do
@user_type_mapping = options[:user_type_mapping]
def self.user_type(user)
(@user_type_mapping || Fragmentary.config.default_user_type_mapping).try(:call, user)
end
@user_types = Fragmentary.parse_session_users(options[:session_users] || options[:types] || options[:user_types])
def self.user_types
@user_types || Fragmentary.config.user_types
end
end
end
|
#needs_user_type? ⇒ Boolean
308
309
310
|
# File 'lib/fragmentary/fragment.rb', line 308
def needs_user_type?
false
end
|
#queue_request(request = nil) ⇒ Object
335
336
337
|
# File 'lib/fragmentary/fragment.rb', line 335
def queue_request(request=nil)
request_queues.each{|key, hsh| hsh.each{|key2, queue| queue << request}} if request
end
|
#record_type ⇒ Object
248
249
250
251
|
# File 'lib/fragmentary/fragment.rb', line 248
def record_type
raise ArgumentError, "The #{self.name} class has no record_type" unless @record_type
@record_type
end
|
#remove_queued_request(host_url: nil, user:, request_path:) ⇒ Object
170
171
172
173
174
175
176
177
|
# File 'lib/fragmentary/fragment.rb', line 170
def remove_queued_request(host_url: nil, user:, request_path:)
u_type = user_type(user)
if host_url.is_a?(String) and (queue = request_queues[host_url][u_type])
queue.remove_path(request_path)
else
request_queues.each{|key, hsh| hsh[u_type].remove_path(request_path)}
end
end
|
#request ⇒ Object
357
358
359
|
# File 'lib/fragmentary/fragment.rb', line 357
def request
raise NotImplementedError
end
|
#request_method ⇒ Object
The instance method 'request_method' is defined in terms of this.
344
345
346
|
# File 'lib/fragmentary/fragment.rb', line 344
def request_method
:get
end
|
#request_options ⇒ Object
The instance method 'request_options' is defined in terms of this.
353
354
355
|
# File 'lib/fragmentary/fragment.rb', line 353
def request_options
{}
end
|
#request_parameters(*args) ⇒ Object
348
349
350
|
# File 'lib/fragmentary/fragment.rb', line 348
def request_parameters(*args)
nil
end
|
#request_queues ⇒ Object
There is one queue per user_type per application instance (the current app and any external instances). The queues
for all fragments are held in common by the Fragment base class here in @@request_queues but are also indexed on a
subclass basis by an individual subclass's user_types (see the inherited hook below). As well as being accessible
here as Fragment.request_queues, the queues are also available without indexation as RequestQueue.all.
129
130
131
132
133
134
135
136
137
138
139
140
141
|
# File 'lib/fragmentary/fragment.rb', line 129
def request_queues
@@request_queues ||= Hash.new do |hsh, host_url|
hsh[host_url] = Hash.new do |hsh2, user_type|
hsh2[user_type] = RequestQueue.new(user_type, host_url)
end
end
end
|
#requestable? ⇒ Boolean
339
340
341
|
# File 'lib/fragmentary/fragment.rb', line 339
def requestable?
respond_to?(:request_path)
end
|
#root(options) ⇒ Object
50
51
52
53
54
55
56
57
58
59
60
|
# File 'lib/fragmentary/fragment.rb', line 50
def root(options)
if fragment = options[:fragment]
raise ArgumentError, "You passed Fragment #{fragment.id} to Fragment.root, but it's a child of Fragment #{fragment.parent_id}" if fragment.parent_id
else
klass, search_attributes, options = base_class.attributes(options)
fragment = klass.where(search_attributes).includes(:children).first_or_initialize(options);
fragment.save if fragment.new_record?
fragment.set_indexed_children if fragment.child_search_key
end
fragment
end
|
#set_record_type(type) ⇒ Object
A subclass of a class declared with 'needs_record_id' will not have a record_type unless set explicitly, which can be done
using the following method.
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
|
# File 'lib/fragmentary/fragment.rb', line 255
def set_record_type(type)
if needs_record_id?
self.record_type = type
if record_type_subscription = subscriber.subscriptions[record_type]
class << record_type_subscription
set_callback :after_destroy, :after, ->{subscriber.client.remove_fragments_for_record(record.id)}
set_callback :after_create, :after, ->{subscriber.client.try_request_for_record(record.id)}
end
end
self.extend RecordClassMethods
define_method(:record){record_type.constantize.find(record_id)}
end
end
|
#subscribe_to(publisher, &block) ⇒ Object
327
328
329
|
# File 'lib/fragmentary/fragment.rb', line 327
def subscribe_to(publisher, &block)
subscriber.subscribe_to(publisher, block)
end
|
#subscriber ⇒ Object
179
180
181
|
# File 'lib/fragmentary/fragment.rb', line 179
def subscriber
@subscriber ||= Subscriber.new(self)
end
|
#touch_fragments_for_record(record_id) ⇒ Object
Note that fragments matching the specified attributes won't always exist, e.g. if the page they are to appear on
hasn't yet been requested, e.g. an assumption created on an article page won't necessarily have been rendered on the
opinion analysis page.
319
320
321
|
# File 'lib/fragmentary/fragment.rb', line 319
def touch_fragments_for_record(record_id)
fragments_for_record(record_id).includes({:parent => :parent}).each(&:touch)
end
|
#user_type(user) ⇒ Object
This default definition can be overridden by sub-classes as required
(typically in root fragment classes by calling needs_user_type).
304
305
306
|
# File 'lib/fragmentary/fragment.rb', line 304
def user_type(user)
user ? "signed_in" : "signed_out"
end
|
#user_types ⇒ Object
298
299
300
|
# File 'lib/fragmentary/fragment.rb', line 298
def user_types
['signed_in']
end
|