lib/jsonapi/resource.rb in jsonapi-resources-0.9.0.beta1 vs lib/jsonapi/resource.rb in jsonapi-resources-0.9.0.beta2
- old
+ new
@@ -414,32 +414,43 @@
def inherited(subclass)
subclass.abstract(false)
subclass.immutable(false)
subclass.caching(false)
subclass._attributes = (_attributes || {}).dup
+
subclass._model_hints = (_model_hints || {}).dup
- subclass._relationships = {}
- # Add the relationships from the base class to the subclass using the original options
- if _relationships.is_a?(Hash)
- _relationships.each_value do |relationship|
- options = relationship.options.dup
- options[:parent_resource] = subclass
- subclass._add_relationship(relationship.class, relationship.name, options)
- end
+ unless _model_name.empty?
+ subclass.model_name(_model_name, add_model_hint: (_model_hints && !_model_hints[_model_name].nil?) == true)
end
+ subclass.rebuild_relationships(_relationships || {})
+
subclass._allowed_filters = (_allowed_filters || Set.new).dup
type = subclass.name.demodulize.sub(/Resource$/, '').underscore
subclass._type = type.pluralize.to_sym
subclass.attribute :id, format: :id
check_reserved_resource_name(subclass._type, subclass.name)
end
+ def rebuild_relationships(relationships)
+ original_relationships = relationships.deep_dup
+
+ @_relationships = {}
+
+ if original_relationships.is_a?(Hash)
+ original_relationships.each_value do |relationship|
+ options = relationship.options.dup
+ options[:parent_resource] = self
+ _add_relationship(relationship.class, relationship.name, options)
+ end
+ end
+ end
+
def resource_for(type)
type = type.underscore
type_with_module = type.include?('/') ? type : module_path + type
resource_name = _resource_name_from_type(type_with_module)
@@ -552,10 +563,12 @@
def model_name(model, options = {})
@_model_name = model.to_sym
model_hint(model: @_model_name, resource: self) unless options[:add_model_hint] == false
+
+ rebuild_relationships(_relationships)
end
def model_hint(model: _model_name, resource: _type)
resource_type = ((resource.is_a?(Class)) && (resource < JSONAPI::Resource)) ? resource._type : resource.to_s
@@ -805,11 +818,15 @@
end
def verify_filter(filter, raw, context = nil)
filter_values = []
if raw.present?
- filter_values += raw.is_a?(String) ? CSV.parse_line(raw) : [raw]
+ begin
+ filter_values += raw.is_a?(String) ? CSV.parse_line(raw) : [raw]
+ rescue CSV::MalformedCSVError
+ filter_values << raw
+ end
end
strategy = _allowed_filters.fetch(filter, Hash.new)[:verify]
if strategy
@@ -898,14 +915,15 @@
def _model_name
if _abstract
return ''
else
- return @_model_name if defined?(@_model_name)
+ return @_model_name.to_s if defined?(@_model_name)
class_name = self.name
return '' if class_name.nil?
- return @_model_name = class_name.demodulize.sub(/Resource$/, '')
+ @_model_name = class_name.demodulize.sub(/Resource$/, '')
+ return @_model_name.to_s
end
end
def _primary_key
@_primary_key ||= _model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id
@@ -972,15 +990,21 @@
end
def _model_class
return nil if _abstract
- return @model if defined?(@model)
- return nil if self.name.to_s.blank? && _model_name.to_s.blank?
- @model = _model_name.to_s.safe_constantize
- warn "[MODEL NOT FOUND] Model could not be found for #{self.name}. If this a base Resource declare it as abstract." if @model.nil?
- @model
+ return @model_class if @model_class
+
+ model_name = _model_name
+ return nil if model_name.to_s.blank?
+
+ @model_class = model_name.to_s.safe_constantize
+ if @model_class.nil?
+ warn "[MODEL NOT FOUND] Model could not be found for #{self.name}. If this a base Resource declare it as abstract."
+ end
+
+ @model_class
end
def _allowed_filter?(filter)
!_allowed_filters[filter].nil?
end
@@ -1038,11 +1062,11 @@
elsif self.caching?
t = _model_class.arel_table
cache_ids = pluck_arel_attributes(records, t[_primary_key], t[_cache_field])
resources = CachedResourceFragment.fetch_fragments(self, serializer, options[:context], cache_ids)
else
- resources = resources_for(records, options).map{|r| [r.id, r] }.to_h
+ resources = resources_for(records, options[:context]).map{|r| [r.id, r] }.to_h
end
preload_included_fragments(resources, records, serializer, options)
resources.values
@@ -1100,11 +1124,10 @@
res_ids = resources.keys
include_directives = options[:include_directives]
return unless include_directives
- relevant_options = options.except(:include_directives, :order, :paginator)
context = options[:context]
# For each association, including indirect associations, find the target record ids.
# Even if a target class doesn't have caching enabled, we still have to look up
# and match the target ids here, because we can't use ActiveRecord#includes.
@@ -1131,12 +1154,19 @@
assocs_path = [] # [ :posts, :approved_comments, :author ]
ar_hash = nil # { :posts => { :approved_comments => :author } }
# For each step on the path, figure out what the actual table name/alias in the join
# will be, and include the primary key of that table in our list of fields to select
+ non_polymorphic = true
path.each do |elem|
relationship = klass._relationships[elem]
+ if relationship.polymorphic
+ # Can't preload through a polymorphic belongs_to association, ResourceSerializer
+ # will just have to bypass the cache and load the real Resource.
+ non_polymorphic = false
+ break
+ end
assocs_path << relationship.relation_name(options).to_sym
# Converts [:a, :b, :c] to Rails-style { :a => { :b => :c }}
ar_hash = assocs_path.reverse.reduce{|memo, step| { step => memo } }
# We can't just look up the table name from the resource class, because Arel could
# have used a table alias if the relation includes a self-reference.
@@ -1146,10 +1176,11 @@
table = join_source.left
parent_klass = klass
klass = relationship.resource_klass
pluck_attrs << table[klass._primary_key]
end
+ next unless non_polymorphic
# Pre-fill empty hashes for each resource up to the end of the path.
# This allows us to later distinguish between a preload that returned nothing
# vs. a preload that never ran.
prefilling_resources = resources.values
@@ -1186,10 +1217,10 @@
else
sub_res_ids = id_rows
.map(&:last)
.reject{|id| target_resources[klass.name].has_key?(id) }
.uniq
- found = klass.find({klass._primary_key => sub_res_ids}, relevant_options)
+ found = klass.find({klass._primary_key => sub_res_ids}, context: options[:context])
target_resources[klass.name].merge! found.map{|r| [r.id, r] }.to_h
end
id_rows.each do |row|
res = resources[row.first]