lib/jsonapi/resource.rb in jsonapi-resources-0.7.1.beta1 vs lib/jsonapi/resource.rb in jsonapi-resources-0.7.1.beta2
- old
+ new
@@ -150,10 +150,19 @@
# the _options hash will contain the serializer and the serialization_options
def meta(_options)
{}
end
+ # Override this to return custom links
+ # must return a hash, which will be merged with the default { self: 'self-url' } links hash
+ # links keys will be not be formatted with the key formatter for the serializer by default.
+ # They can however use the serializer's format_key and format_value methods if desired
+ # the _options hash will contain the serializer and the serialization_options
+ def custom_links(_options)
+ {}
+ end
+
private
def save
run_callbacks :save do
_save
@@ -171,12 +180,12 @@
# def _save
# super
# return :accepted
# end
# ```
- def _save
- unless @model.valid?
+ def _save(validation_context = nil)
+ unless @model.valid?(validation_context)
fail JSONAPI::Exceptions::ValidationErrors.new(self)
end
if defined? @model.save
saved = @model.save(validate: false)
@@ -199,10 +208,13 @@
def _remove
unless @model.destroy
fail JSONAPI::Exceptions::ValidationErrors.new(self)
end
:completed
+
+ rescue ActiveRecord::DeleteRestrictionError => e
+ fail JSONAPI::Exceptions::RecordLocked.new(e.message)
end
def _create_to_many_links(relationship_type, relationship_key_values)
relationship = self.class._relationships[relationship_type]
@@ -254,10 +266,15 @@
relation_name = self.class._relationships[relationship_type].relation_name(context: @context)
@model.public_send(relation_name).delete(key)
:completed
+
+ rescue ActiveRecord::DeleteRestrictionError => e
+ fail JSONAPI::Exceptions::RecordLocked.new(e.message)
+ rescue ActiveRecord::RecordNotFound
+ fail JSONAPI::Exceptions::RecordNotFound.new(key)
end
def _remove_to_one_link(relationship_type)
relationship = self.class._relationships[relationship_type]
@@ -388,15 +405,15 @@
end
@_attributes ||= {}
@_attributes[attr] = options
define_method attr do
- @model.public_send(attr)
+ @model.public_send(options[:delegate] ? options[:delegate].to_sym : attr)
end unless method_defined?(attr)
define_method "#{attr}=" do |value|
- @model.public_send "#{attr}=", value
+ @model.public_send("#{options[:delegate] ? options[:delegate].to_sym : attr}=", value)
end unless method_defined?("#{attr}=")
end
def default_attribute_options
{ format: :default }
@@ -519,21 +536,61 @@
records
end
def apply_sort(records, order_options, _context = {})
if order_options.any?
- records.order(order_options)
- else
- records
+ order_options.each_pair do |field, direction|
+ if field.to_s.include?(".")
+ *model_names, column_name = field.split(".")
+
+ associations = _lookup_association_chain([records.model.to_s, *model_names])
+ joins_query = _build_joins([records.model, *associations])
+
+ # _sorting is appended to avoid name clashes with manual joins eg. overriden filters
+ order_by_query = "#{associations.last.name}_sorting.#{column_name} #{direction}"
+ records = records.joins(joins_query).order(order_by_query)
+ else
+ records = records.order(field => direction)
+ end
+ end
end
+
+ records
end
+ def _lookup_association_chain(model_names)
+ associations = []
+ model_names.inject do |prev, current|
+ association = prev.classify.constantize.reflect_on_all_associations.detect do |assoc|
+ assoc.name.to_s.downcase == current.downcase
+ end
+ associations << association
+ association.class_name
+ end
+
+ associations
+ end
+
+ def _build_joins(associations)
+ joins = []
+
+ associations.inject do |prev, current|
+ joins << "LEFT JOIN #{current.table_name} AS #{current.name}_sorting ON #{current.name}_sorting.id = #{prev.table_name}.#{current.foreign_key}"
+ current
+ end
+ joins.join("\n")
+ end
+
def apply_filter(records, filter, value, options = {})
strategy = _allowed_filters.fetch(filter.to_sym, Hash.new)[:apply]
if strategy
- strategy.call(records, value, options)
+ if strategy.is_a?(Symbol) || strategy.is_a?(String)
+ send(strategy, records, value, options)
+ else
+ strategy.call(records, value, options)
+ end
else
records.where(filter => value)
end
end
@@ -569,12 +626,17 @@
def sort_records(records, order_options, context = {})
apply_sort(records, order_options, context)
end
+ # Assumes ActiveRecord's counting. Override if you need a different counting method
+ def count_records(records)
+ records.count(:all)
+ end
+
def find_count(filters, options = {})
- filter_records(filters, options).count(:all)
+ count_records(filter_records(filters, options))
end
# Override this method if you have more complex requirements than this basic find method provides
def find(filters, options = {})
context = options[:context]
@@ -585,13 +647,19 @@
order_options = construct_order_options(sort_criteria)
records = sort_records(records, order_options, context)
records = apply_pagination(records, options[:paginator], order_options)
+ resources_for(records, context)
+ end
+
+ def resources_for(records, context)
resources = []
+ resource_classes = {}
records.each do |model|
- resources.push self.resource_for_model(model).new(model, context)
+ resource_class = resource_classes[model.class] ||= self.resource_for_model(model)
+ resources.push resource_class.new(model, context)
end
resources
end
@@ -623,16 +691,23 @@
filter == _type || _relationships.include?(filter)
end
def verify_filter(filter, raw, context = nil)
filter_values = []
- filter_values += CSV.parse_line(raw) unless raw.nil? || raw.empty?
+ if raw.present?
+ filter_values += raw.is_a?(String) ? CSV.parse_line(raw) : [raw]
+ end
strategy = _allowed_filters.fetch(filter, Hash.new)[:verify]
if strategy
- [filter, strategy.call(filter_values, context)]
+ if strategy.is_a?(Symbol) || strategy.is_a?(String)
+ values = send(strategy, filter_values, context)
+ else
+ values = strategy.call(filter_values, context)
+ end
+ [filter, values]
else
if is_filter_relationship?(filter)
verify_relationship_filter(filter, filter_values, context)
else
verify_custom_filter(filter, filter_values, context)
@@ -643,10 +718,10 @@
def key_type(key_type)
@_resource_key_type = key_type
end
def resource_key_type
- @_resource_key_type || JSONAPI.configuration.resource_key_type
+ @_resource_key_type ||= JSONAPI.configuration.resource_key_type
end
def verify_key(key, context = nil)
key_type = resource_key_type