-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from avvo/associations
Associations
- Loading branch information
Showing
28 changed files
with
1,168 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,3 @@ DEPENDENCIES | |
rake | ||
simplecov | ||
webmock (> 0) | ||
|
||
BUNDLED WITH | ||
1.11.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
module JsonApiResource | ||
module Associatable | ||
extend ActiveSupport::Concern | ||
|
||
included do | ||
|
||
class_attribute :_associations | ||
self._associations = {} | ||
|
||
attr_accessor :_cached_associations | ||
|
||
class << self | ||
def belongs_to( name, opts = {} ) | ||
process Associations::BelongsTo.new( self, name, opts ) | ||
end | ||
|
||
def has_one( name, opts = {} ) | ||
process Associations::HasOne.new( self, name, opts ) | ||
end | ||
|
||
def has_many( name, opts = {} ) | ||
if opts[:prefetched_ids] | ||
process Associations::HasManyPrefetched.new( self, name, opts ) | ||
else | ||
process Associations::HasMany.new( self, name, opts ) | ||
end | ||
end | ||
|
||
private | ||
|
||
def process(association) | ||
add_association association | ||
methodize association | ||
end | ||
|
||
def add_association(association) | ||
self._associations = _associations.merge association.name => association | ||
end | ||
|
||
def methodize( association ) | ||
define_method association.name do | ||
self._cached_associations ||= {} | ||
unless self._cached_associations.has_key? association.name | ||
if association.callable?(self) | ||
result = association.klass.send( association.action, association.query(self) ) | ||
result = association.post_process result | ||
else | ||
result = association.nil_default | ||
end | ||
|
||
self._cached_associations[association.name] = result | ||
end | ||
self._cached_associations[association.name] | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module JsonApiResource | ||
module Associations | ||
autoload :Base, 'json_api_resource/associations/base' | ||
autoload :BelongsTo, 'json_api_resource/associations/belongs_to' | ||
autoload :HasManyPrefetched, 'json_api_resource/associations/has_many_prefetched' | ||
autoload :HasOne, 'json_api_resource/associations/has_one' | ||
autoload :HasMany, 'json_api_resource/associations/has_many' | ||
autoload :Preloader, 'json_api_resource/associations/preloader' | ||
autoload :Preloaders, 'json_api_resource/associations/preloaders' | ||
|
||
|
||
BELONGS_TO = :belongs_to | ||
HAS_ONE = :has_one | ||
HAS_MANY = :has_many | ||
HAS_MANY_PREFETCHED = :has_many_prefetched | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
module JsonApiResource | ||
module Associations | ||
class Base | ||
include ::JsonApiResource::Errors | ||
attr_accessor :name, :action, :key, :root | ||
|
||
def initialize(associated_class, name, opts = {}) | ||
self.name = name.to_sym | ||
self.root = associated_class | ||
@opts = opts.merge( skip_pagination: true ) | ||
|
||
self.action = @opts.delete :action do default_action end | ||
self.key = @opts.delete :foreign_key do server_key end | ||
|
||
self.key = self.key.try :to_sym | ||
validate_options | ||
end | ||
|
||
def type | ||
raise NotImplementedError | ||
end | ||
|
||
def query( root_insatnce ) | ||
raise NotImplementedError | ||
end | ||
|
||
def callable?( root_insatnce ) | ||
raise NotImplementedError | ||
end | ||
|
||
def default_nil | ||
raise NotImplementedError | ||
end | ||
|
||
# klass has to be lazy initted for circular dependencies | ||
def klass | ||
@klass ||= @opts.delete :class do derived_class end | ||
end | ||
|
||
def post_process( value ) | ||
value | ||
end | ||
|
||
def opts | ||
@opts.except *ASSOCIATION_OPTS | ||
end | ||
|
||
protected | ||
|
||
def server_key | ||
raise NotImplementedError | ||
end | ||
|
||
def default_action | ||
raise NotImplementedError | ||
end | ||
|
||
def derived_class | ||
module_string = self.root.to_s.split("::")[0 ... -1].join("::") | ||
class_string = name.to_s.singularize.camelize | ||
|
||
# we don't necessarily want to add :: to classes, in case they have a relative path or something | ||
class_string = [module_string, class_string].select{|s| s.present? }.join "::" | ||
class_string.constantize | ||
end | ||
|
||
ASSOCIATION_OPTS = [:class, :action, :foreign_key, :prefetched_ids] | ||
|
||
RESERVED_KEYWORDS = [:attributes, :_associations, :_cached_associations, :schema, :client] | ||
|
||
def validate_options | ||
raise_unless action.present?, "Invalid action: #{self.root}.#{name}" | ||
raise_unless key.present?, "Invalid foreign_key for #{self.root}.#{name}" | ||
|
||
raise_if RESERVED_KEYWORDS.include?(name), "'#{name}' is a reserved keyword for #{self.root}" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
module JsonApiResource | ||
module Associations | ||
class BelongsTo < Base | ||
def default_action | ||
:find | ||
end | ||
|
||
def server_key | ||
"#{name}_id" | ||
end | ||
|
||
def query( root_instance ) | ||
root_instance.send key | ||
end | ||
|
||
def callable?( root_instance ) | ||
root_instance.send(key).present? | ||
end | ||
|
||
def nil_default | ||
nil | ||
end | ||
|
||
def type | ||
JsonApiResource::Associations::BELONGS_TO | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module JsonApiResource | ||
module Associations | ||
class HasMany < Base | ||
def default_action | ||
:where | ||
end | ||
|
||
def server_key | ||
class_name = self.root.to_s.demodulize.underscore | ||
"#{class_name}_id" | ||
end | ||
|
||
def callable?( root_instance ) | ||
true | ||
end | ||
|
||
def query( root_instance ) | ||
{ key => root_instance.id }.merge(opts) | ||
end | ||
|
||
def type | ||
JsonApiResource::Associations::HAS_MANY | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
module JsonApiResource | ||
module Associations | ||
class HasManyPrefetched < Base | ||
def default_action | ||
:where | ||
end | ||
|
||
def server_key | ||
@opts[:prefetched_ids] | ||
end | ||
|
||
def query( root_instance ) | ||
{ id: root_instance.send(key) }.merge(opts) | ||
end | ||
|
||
def callable?( root_instance ) | ||
root_instance.send(key).present? | ||
end | ||
|
||
def nil_default | ||
[] | ||
end | ||
|
||
def type | ||
JsonApiResource::Associations::HAS_MANY_PREFETCHED | ||
end | ||
|
||
def validate_options | ||
raise_unless key == server_key, "#{root}.#{name} cannot specify both prefetched_ids and a foreign key" | ||
super | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
module JsonApiResource | ||
module Associations | ||
class HasOne < Base | ||
def post_process( value ) | ||
Array(value).first | ||
end | ||
|
||
def default_action | ||
:where | ||
end | ||
|
||
def server_key | ||
class_name = self.root.to_s.demodulize.underscore | ||
"#{class_name}_id" | ||
end | ||
|
||
def query( root_instance ) | ||
{ key => root_instance.id }.merge(opts) | ||
end | ||
|
||
def callable?( root_instance ) | ||
true | ||
end | ||
|
||
def type | ||
JsonApiResource::Associations::HAS_ONE | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
module JsonApiResource | ||
module Associations | ||
class Preloader | ||
include JsonApiResource::Errors | ||
|
||
class << self | ||
def preload ( objects, preloads ) | ||
objects = Array(objects) | ||
preloads = Array(preloads) | ||
|
||
preloads.each do |preload| | ||
|
||
association = association_for objects, preload | ||
|
||
preloader = preloader_for association | ||
|
||
preloader.preload( objects ) | ||
|
||
end | ||
end | ||
|
||
private | ||
|
||
def association_for( objects, preload ) | ||
# let's assert the objects are of a single class | ||
verify_object_homogenity!(objects) | ||
|
||
obj_class = objects.first.class | ||
association = obj_class._associations[preload] | ||
|
||
raise_if association.nil?, "'#{preload}' is not a valid association on #{obj_class}" | ||
|
||
association | ||
end | ||
|
||
def preloader_for( association ) | ||
preloader_class = PREOLOADERS_FOR_ASSOCIATIONS[association.type] | ||
preloader_class.new association | ||
end | ||
|
||
def verify_object_homogenity!( objects ) | ||
obj_class = objects.first.class | ||
|
||
objects.each do |obj| | ||
raise_unless obj.is_a?(obj_class), "JsonApiResource::Associations::Preloader.preload called with a heterogenious array of objects." | ||
end | ||
end | ||
|
||
PREOLOADERS_FOR_ASSOCIATIONS = { | ||
JsonApiResource::Associations::BELONGS_TO => JsonApiResource::Associations::Preloaders::BelongsToPreloader, | ||
JsonApiResource::Associations::HAS_ONE => JsonApiResource::Associations::Preloaders::HasOnePreloader, | ||
JsonApiResource::Associations::HAS_MANY => JsonApiResource::Associations::Preloaders::HasManyPreloader, | ||
JsonApiResource::Associations::HAS_MANY_PREFETCHED => JsonApiResource::Associations::Preloaders::HasManyPrefetchedPreloader, | ||
} | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module JsonApiResource | ||
module Associations | ||
module Preloaders | ||
autoload :Base, 'json_api_resource/associations/preloaders/base' | ||
autoload :BelongsToPreloader, 'json_api_resource/associations/preloaders/belongs_to_preloader' | ||
autoload :Distributors, 'json_api_resource/associations/preloaders/distributors' | ||
autoload :HasManyPrefetchedPreloader, 'json_api_resource/associations/preloaders/has_many_prefetched_preloader' | ||
autoload :HasManyPreloader, 'json_api_resource/associations/preloaders/has_many_preloader' | ||
autoload :HasOnePreloader, 'json_api_resource/associations/preloaders/has_one_preloader' | ||
end | ||
end | ||
end |
Oops, something went wrong.