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(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.
362
363
364
365
366
367
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
|
# File 'lib/fragmentary/fragment.rb', line 362
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).
65
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
|
# File 'lib/fragmentary/fragment.rb', line 65
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
98
99
100
|
# File 'lib/fragmentary/fragment.rb', line 98
def cache_store
@@cache_store ||= Rails.application.config.action_controller.cache_store
end
|
#child_search_key ⇒ Object
325
326
327
|
# File 'lib/fragmentary/fragment.rb', line 325
def child_search_key
nil
end
|
#existing(options) ⇒ Object
ToDo: combine this with Fragment.root
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
# File 'lib/fragmentary/fragment.rb', line 103
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
120
121
122
|
# File 'lib/fragmentary/fragment.rb', line 120
def fragment_type
self
end
|
#fragments_for_record(record_id) ⇒ Object
317
318
319
|
# File 'lib/fragmentary/fragment.rb', line 317
def fragments_for_record(record_id)
self.where(:record_id => record_id)
end
|
#inherited(subclass) ⇒ Object
Subclass-specific request_queues
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
# File 'lib/fragmentary/fragment.rb', line 143
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
225
226
227
|
# File 'lib/fragmentary/fragment.rb', line 225
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.
376
377
378
379
380
381
382
|
# File 'lib/fragmentary/fragment.rb', line 376
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
177
178
179
180
181
|
# File 'lib/fragmentary/fragment.rb', line 177
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
217
218
219
220
221
222
223
|
# File 'lib/fragmentary/fragment.rb', line 217
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
306
307
308
|
# File 'lib/fragmentary/fragment.rb', line 306
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).
235
236
237
238
239
240
|
# File 'lib/fragmentary/fragment.rb', line 235
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
284
285
286
|
# File 'lib/fragmentary/fragment.rb', line 284
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.
188
189
190
|
# File 'lib/fragmentary/fragment.rb', line 188
def needs_user_id
self.extend NeedsUserId
end
|
#needs_user_id? ⇒ Boolean
288
289
290
|
# File 'lib/fragmentary/fragment.rb', line 288
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.
203
204
205
206
207
208
209
210
211
212
213
214
215
|
# File 'lib/fragmentary/fragment.rb', line 203
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.session_users.keys
end
end
end
|
#needs_user_type? ⇒ Boolean
302
303
304
|
# File 'lib/fragmentary/fragment.rb', line 302
def needs_user_type?
false
end
|
#queue_request(request = nil) ⇒ Object
329
330
331
|
# File 'lib/fragmentary/fragment.rb', line 329
def queue_request(request=nil)
request_queues.each{|key, hsh| hsh.each{|key2, queue| queue << request}} if request
end
|
#record_type ⇒ Object
242
243
244
245
|
# File 'lib/fragmentary/fragment.rb', line 242
def record_type
raise ArgumentError, "The #{self.name} class has no record_type" unless @record_type
@record_type
end
|
#remove_queued_request(user:, request_path:) ⇒ Object
169
170
171
|
# File 'lib/fragmentary/fragment.rb', line 169
def remove_queued_request(user:, request_path:)
request_queues.each{|key, hsh| hsh[user_type(user)].remove_path(request_path)}
end
|
#request ⇒ Object
351
352
353
|
# File 'lib/fragmentary/fragment.rb', line 351
def request
raise NotImplementedError
end
|
#request_method ⇒ Object
The instance method ‘request_method’ is defined in terms of this.
338
339
340
|
# File 'lib/fragmentary/fragment.rb', line 338
def request_method
:get
end
|
#request_options ⇒ Object
The instance method ‘request_options’ is defined in terms of this.
347
348
349
|
# File 'lib/fragmentary/fragment.rb', line 347
def request_options
{}
end
|
#request_parameters(*args) ⇒ Object
342
343
344
|
# File 'lib/fragmentary/fragment.rb', line 342
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.
128
129
130
131
132
133
134
135
136
137
138
139
140
|
# File 'lib/fragmentary/fragment.rb', line 128
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
333
334
335
|
# File 'lib/fragmentary/fragment.rb', line 333
def requestable?
respond_to?(:request_path)
end
|
#root(options) ⇒ Object
50
51
52
53
54
55
56
57
58
59
|
# 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.
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
|
# File 'lib/fragmentary/fragment.rb', line 249
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
321
322
323
|
# File 'lib/fragmentary/fragment.rb', line 321
def subscribe_to(publisher, &block)
subscriber.subscribe_to(publisher, block)
end
|
#subscriber ⇒ Object
173
174
175
|
# File 'lib/fragmentary/fragment.rb', line 173
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.
313
314
315
|
# File 'lib/fragmentary/fragment.rb', line 313
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).
298
299
300
|
# File 'lib/fragmentary/fragment.rb', line 298
def user_type(user)
user ? "signed_in" : "signed_out"
end
|
#user_types ⇒ Object
292
293
294
|
# File 'lib/fragmentary/fragment.rb', line 292
def user_types
['signed_in']
end
|