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]