Class: JsonapiCompliable::Sideload
- Inherits:
-
Object
- Object
- JsonapiCompliable::Sideload
- Defined in:
- lib/jsonapi_compliable/sideload.rb
Constant Summary collapse
- HOOK_ACTIONS =
[:save, :create, :update, :destroy, :disassociate]
Instance Attribute Summary collapse
-
#assign_proc ⇒ Proc
readonly
The configured 'assign' block.
-
#foreign_key ⇒ Symbol
readonly
The attribute used to match objects - need not be a true database foreign key.
-
#grouping_field ⇒ Symbol
readonly
The configured 'group_by' symbol.
-
#name ⇒ Symbol
readonly
The name of the sideload.
-
#parent ⇒ Object
readonly
Returns the value of attribute parent.
-
#polymorphic ⇒ Boolean
readonly
Is this a polymorphic sideload?.
-
#polymorphic_groups ⇒ Hash
readonly
The subgroups, when polymorphic.
-
#primary_key ⇒ Symbol
readonly
The attribute used to match objects - need not be a true database primary key.
-
#resource_class ⇒ Class
readonly
The corresponding Resource class.
-
#scope_proc ⇒ Proc
readonly
The configured 'scope' block.
-
#sideloads ⇒ Hash
readonly
The associated sibling sideloads.
-
#type ⇒ Symbol
readonly
One of :has_many, :belongs_to, etc.
Class Method Summary collapse
- .max_recursion ⇒ Object private
-
.max_recursion=(val) ⇒ Object
Set maximum levels of sideload recursion /authors?comments.authors would be one level /authors?comments.authors.comments.authors would be two levels etc.
Instance Method Summary collapse
-
#after_save(only: [], except: [], &blk) ⇒ Object
Configure post-processing hooks.
- #all_sideloads ⇒ Object private
-
#allow_sideload(name, opts = {}, &blk) ⇒ Object
Configure a relationship between Resource objects.
-
#assign {|parents, children| ... } ⇒ Object
The proc used to assign the resolved parents and children.
-
#associate(parent, child) ⇒ Object
private
Configure how to associate parent and child records.
-
#disassociate(parent, child) ⇒ Object
private
Configure how to disassociate parent and child records.
- #fire_hooks!(parent, objects, method) ⇒ Object
-
#group_by(grouping_field) ⇒ Object
Define an attribute that groups the parent records.
-
#hooks ⇒ Object
Get the hooks the user has configured.
-
#initialize(name, type: nil, resource: nil, polymorphic: false, primary_key: :id, foreign_key: nil, parent: nil) ⇒ Sideload
constructor
NB - the adapter's
#sideloading_moduleis mixed in on instantiation. -
#polymorphic? ⇒ Boolean
Is this sideload polymorphic?.
- #polymorphic_child_for_type(type) ⇒ Object private
-
#resolve(parents, query, namespace = nil) ⇒ void
private
Resolve the sideload.
-
#resource ⇒ Resource
An instance of
#resource_class. -
#scope {|parents| ... } ⇒ Object
Build a scope that will be used to fetch the related records This scope will be further chained with filtering/sorting/etc.
-
#sideload(name) ⇒ Object
Fetch a Sideload object by its name.
-
#to_hash(recursion_chain = [], parent = nil) ⇒ Hash
private
Looks at all nested sideload, and all nested sideloads for the corresponding Resources, and returns an Include Directive hash.
Constructor Details
#initialize(name, type: nil, resource: nil, polymorphic: false, primary_key: :id, foreign_key: nil, parent: nil) ⇒ Sideload
NB - the adapter's #sideloading_module is mixed in on instantiation
An anonymous Resource will be assigned when none provided.
32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/jsonapi_compliable/sideload.rb', line 32 def initialize(name, type: nil, resource: nil, polymorphic: false, primary_key: :id, foreign_key: nil, parent: nil) @name = name @resource_class = (resource || Class.new(Resource)) @sideloads = {} @polymorphic = !!polymorphic @polymorphic_groups = {} if polymorphic? @parent = parent @primary_key = primary_key @foreign_key = foreign_key @type = type extend @resource_class.config[:adapter].sideloading_module end |
Instance Attribute Details
#assign_proc ⇒ Proc (readonly)
The configured 'assign' block
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def assign_proc @assign_proc end |
#foreign_key ⇒ Symbol (readonly)
The attribute used to match objects - need not be a true database foreign key.
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def foreign_key @foreign_key end |
#grouping_field ⇒ Symbol (readonly)
The configured 'group_by' symbol
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def grouping_field @grouping_field end |
#name ⇒ Symbol (readonly)
The name of the sideload
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def name @name end |
#parent ⇒ Object (readonly)
Returns the value of attribute parent.
14 15 16 |
# File 'lib/jsonapi_compliable/sideload.rb', line 14 def parent @parent end |
#polymorphic ⇒ Boolean (readonly)
Is this a polymorphic sideload?
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def polymorphic @polymorphic end |
#polymorphic_groups ⇒ Hash (readonly)
The subgroups, when polymorphic
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def polymorphic_groups @polymorphic_groups end |
#primary_key ⇒ Symbol (readonly)
The attribute used to match objects - need not be a true database primary key.
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def primary_key @primary_key end |
#resource_class ⇒ Class (readonly)
The corresponding Resource class
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def resource_class @resource_class end |
#scope_proc ⇒ Proc (readonly)
The configured 'scope' block
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def scope_proc @scope_proc end |
#sideloads ⇒ Hash (readonly)
The associated sibling sideloads
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def sideloads @sideloads end |
#type ⇒ Symbol (readonly)
One of :has_many, :belongs_to, etc
13 14 15 |
# File 'lib/jsonapi_compliable/sideload.rb', line 13 def type @type end |
Class Method Details
.max_recursion ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
47 48 49 |
# File 'lib/jsonapi_compliable/sideload.rb', line 47 def self.max_recursion @max_recursion || 2 end |
.max_recursion=(val) ⇒ Object
Set maximum levels of sideload recursion /authors?comments.authors would be one level /authors?comments.authors.comments.authors would be two levels etc
Default max recursion is 2
57 58 59 |
# File 'lib/jsonapi_compliable/sideload.rb', line 57 def self.max_recursion=(val) @max_recursion = val end |
Instance Method Details
#after_save(only: [], except: [], &blk) ⇒ Object
Configure post-processing hooks
In particular, helpful for bulk operations. "after_save" will fire
for any persistence method - :create, :update, :destroy, :disassociate.
Use "only" and "except" keyword arguments to fire only for a
specific persistence method.
232 233 234 235 236 237 238 239 |
# File 'lib/jsonapi_compliable/sideload.rb', line 232 def after_save(only: [], except: [], &blk) actions = HOOK_ACTIONS - except actions = only & actions actions = [:save] if only.empty? && except.empty? actions.each do |a| hooks[:"after_#{a}"] << blk end end |
#all_sideloads ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/jsonapi_compliable/sideload.rb', line 350 def all_sideloads {}.tap do |all| if polymorphic? polymorphic_groups.each_pair do |type, sl| all.merge!(sl.resource.sideloading.all_sideloads) end else all.merge!(@sideloads.merge(resource.sideloading.sideloads)) end end end |
#allow_sideload(name, opts = {}, &blk) ⇒ Object
Configure a relationship between Resource objects
You probably want to extract this logic into an adapter rather than using directly
330 331 332 333 334 335 336 337 338 339 |
# File 'lib/jsonapi_compliable/sideload.rb', line 330 def allow_sideload(name, opts = {}, &blk) sideload = Sideload.new(name, opts) sideload.instance_eval(&blk) if blk if polymorphic? @polymorphic_groups[name] = sideload else @sideloads[name] = sideload end end |
#assign {|parents, children| ... } ⇒ Object
The proc used to assign the resolved parents and children.
You probably want to wrap this logic in an Adapter, instead of specifying in your resource directly.
183 184 185 |
# File 'lib/jsonapi_compliable/sideload.rb', line 183 def assign(&blk) @assign_proc = blk end |
#associate(parent, child) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Configure how to associate parent and child records. Delegates to #resource
193 194 195 196 |
# File 'lib/jsonapi_compliable/sideload.rb', line 193 def associate(parent, child) association_name = @parent ? @parent.name : name resource.associate(parent, child, association_name, type) end |
#disassociate(parent, child) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Configure how to disassociate parent and child records. Delegates to #resource
204 205 206 207 |
# File 'lib/jsonapi_compliable/sideload.rb', line 204 def disassociate(parent, child) association_name = @parent ? @parent.name : name resource.disassociate(parent, child, association_name, type) end |
#fire_hooks!(parent, objects, method) ⇒ Object
409 410 411 412 413 414 415 416 |
# File 'lib/jsonapi_compliable/sideload.rb', line 409 def fire_hooks!(parent, objects, method) return unless self.hooks hooks = self.hooks[:"after_#{method}"] + self.hooks[:after_save] hooks.compact.each do |hook| resource.instance_exec(parent, objects, &hook) end end |
#group_by(grouping_field) ⇒ Object
Define an attribute that groups the parent records. For instance, with
an ActiveRecord polymorphic belongs_to there will be a parent_id
and parent_type. We would want to group on parent_type:
allow_sideload :organization, polymorphic: true do
group parent_type, parent here is 'organization'
group_by :organization_type end
263 264 265 |
# File 'lib/jsonapi_compliable/sideload.rb', line 263 def group_by(grouping_field) @grouping_field = grouping_field end |
#hooks ⇒ Object
Get the hooks the user has configured
244 245 246 247 248 249 250 251 |
# File 'lib/jsonapi_compliable/sideload.rb', line 244 def hooks @hooks ||= {}.tap do |h| HOOK_ACTIONS.each do |a| h[:"after_#{a}"] = [] h[:"before_#{a}"] = [] end end end |
#polymorphic? ⇒ Boolean
Is this sideload polymorphic?
Polymorphic sideloads group the parent objects in some fashion,
so different 'types' can be resolved differently. Let's say an
Office has a polymorphic Organization, which can be either a
Business or Government:
allow_sideload :organization, :polymorphic: true do
group_by :organization_type
allow_sideload 'Business', resource: BusinessResource do
# ... code ...
end
allow_sideload 'Governemnt', resource: GovernmentResource do
# ... code ...
end
end
You probably want to extract this code into an Adapter. For instance, with ActiveRecord:
polymorphic_belongs_to :organization,
group_by: :organization_type,
groups: {
'Business' => {
scope: -> { Business.all },
resource: BusinessResource,
foreign_key: :organization_id
},
'Government' => {
scope: -> { Government.all },
resource: GovernmentResource,
foreign_key: :organization_id
}
}
106 107 108 |
# File 'lib/jsonapi_compliable/sideload.rb', line 106 def polymorphic? @polymorphic == true end |
#polymorphic_child_for_type(type) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
403 404 405 406 407 |
# File 'lib/jsonapi_compliable/sideload.rb', line 403 def polymorphic_child_for_type(type) polymorphic_groups.values.find do |v| v.resource_class.config[:type] == type.to_sym end end |
#resolve(parents, query, namespace = nil) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Resolve the sideload.
- Uses the 'scope' proc to build a 'base scope'
- Chains additional criteria onto that 'base scope'
- Resolves that scope (see Scope#resolve)
- Assigns the resulting child objects to their corresponding parents
282 283 284 285 286 287 288 289 290 |
# File 'lib/jsonapi_compliable/sideload.rb', line 282 def resolve(parents, query, namespace = nil) namespace ||= name if polymorphic? resolve_polymorphic(parents, query) else resolve_basic(parents, query, namespace) end end |
#resource ⇒ Resource
Returns an instance of #resource_class.
63 64 65 |
# File 'lib/jsonapi_compliable/sideload.rb', line 63 def resource @resource ||= resource_class.new end |
#scope {|parents| ... } ⇒ Object
Build a scope that will be used to fetch the related records This scope will be further chained with filtering/sorting/etc
You probably want to wrap this logic in an Adapter, instead of specifying in your resource directly.
146 147 148 |
# File 'lib/jsonapi_compliable/sideload.rb', line 146 def scope(&blk) @scope_proc = blk end |
#sideload(name) ⇒ Object
Fetch a Sideload object by its name
345 346 347 |
# File 'lib/jsonapi_compliable/sideload.rb', line 345 def sideload(name) @sideloads[name] end |
#to_hash(recursion_chain = [], parent = nil) ⇒ Hash
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Looks at all nested sideload, and all nested sideloads for the corresponding Resources, and returns an Include Directive hash
For instance, this configuration:
class BarResource < ApplicationResource
allow_sideload :baz do
end
end
class PostResource < ApplicationResource
allow_sideload :foo do
allow_sideload :bar, resource: BarResource do
end
end
end
post_resource.sideloading.to_hash would return
{ base: { foo: { bar: { baz: {} } } } }
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/jsonapi_compliable/sideload.rb', line 385 def to_hash(recursion_chain = [], parent = nil) recursing = ->(arr) { arr == [parent.object_id, self.object_id] } recursions = recursion_chain.select(&recursing).length return {} if recursions >= self.class.max_recursion unless (parent && parent.name == :base) || name == :base recursion_chain += [[parent.object_id, self.object_id]] end { name => {} }.tap do |hash| all_sideloads.each_pair do |key, sl| sideload_hash = sl.to_hash(recursion_chain, self) hash[name].merge!(sideload_hash) end end end |