diff --git a/lib/hal_api/controller/filtering.rb b/lib/hal_api/controller/filtering.rb index 2773a8c..4fa5596 100644 --- a/lib/hal_api/controller/filtering.rb +++ b/lib/hal_api/controller/filtering.rb @@ -53,6 +53,24 @@ def filters @filters ||= parse_filters_param end + def filter_facets + end + + def index_collection + collection = defined?(super) ? super : HalApi::PagedCollection.new([]) + + # add facets if defined, removing filters/facets with counts of 0 + non_zero_facets = (filter_facets || {}).with_indifferent_access.tap do |hash| + hash.each do |filter_key, facets| + hash[filter_key] = facets.try(:select) { |f| f.try(:[], :count) > 0 } + hash.delete(filter_key) if hash[filter_key].blank? + end + end + collection.facets = non_zero_facets unless non_zero_facets.blank? + + collection + end + private def parse_filters_param diff --git a/lib/hal_api/controller/resources.rb b/lib/hal_api/controller/resources.rb index 6ddae14..38a2863 100644 --- a/lib/hal_api/controller/resources.rb +++ b/lib/hal_api/controller/resources.rb @@ -75,6 +75,10 @@ def resources_base self.class.resource_class.where(nil) end + def resources_query + filtered(scoped(resources_base)) + end + def find_base filtered(scoped(included(resources_base))) end diff --git a/lib/hal_api/paged_collection.rb b/lib/hal_api/paged_collection.rb index 6297c68..49c3148 100644 --- a/lib/hal_api/paged_collection.rb +++ b/lib/hal_api/paged_collection.rb @@ -6,7 +6,7 @@ class HalApi::PagedCollection extend ActiveModel::Naming extend Forwardable - attr_accessor :items, :request, :options + attr_accessor :items, :request, :options, :facets def_delegators :items, :total_count, :prev_page, :next_page, :total_pages, :first_page?, :last_page? alias_method :total, :total_count diff --git a/lib/hal_api/rails/version.rb b/lib/hal_api/rails/version.rb index 396de02..185549f 100644 --- a/lib/hal_api/rails/version.rb +++ b/lib/hal_api/rails/version.rb @@ -1,5 +1,5 @@ module HalApi module Rails - VERSION = "0.6.0" + VERSION = "0.7.0" end end diff --git a/lib/hal_api/representer.rb b/lib/hal_api/representer.rb index a9d205c..84ea29a 100644 --- a/lib/hal_api/representer.rb +++ b/lib/hal_api/representer.rb @@ -21,5 +21,6 @@ class HalApi::Representer < Roar::Decorator include HalApi::Representer::Caches include HalApi::Representer::LinkSerialize self_link + vary_link profile_link end diff --git a/lib/hal_api/representer/collection_paging.rb b/lib/hal_api/representer/collection_paging.rb index 04e22c2..4daadc8 100644 --- a/lib/hal_api/representer/collection_paging.rb +++ b/lib/hal_api/representer/collection_paging.rb @@ -7,6 +7,7 @@ module HalApi::Representer::CollectionPaging class_eval do property :count property :total + property :facets embeds :items, decorator: lambda{|*| item_decorator }, class: lambda{|*| item_class }, zoom: :always @@ -36,6 +37,14 @@ def self_url(represented) href_url_helper(represented.params) end + def vary_url(represented) + href_url_helper(represented.params.except(*vary_params)) + end + + def vary_params + %w(page per zoom filters sorts) + end + def profile_url(represented) model_uri(:collection, represented.item_class) end @@ -46,6 +55,7 @@ def profile_url(represented) # if it is a lambda, execute in the context against the represented.parent (if there is one) or represented def href_url_helper(options={}) if represented_url.nil? + options = options.except(:format) result = url_for(options.merge(only_path: true)) rescue nil if represented.parent result ||= polymorphic_path([:api, represented.parent, represented.item_class], options) rescue nil diff --git a/lib/hal_api/representer/uri_methods.rb b/lib/hal_api/representer/uri_methods.rb index beb8f63..9be60d1 100644 --- a/lib/hal_api/representer/uri_methods.rb +++ b/lib/hal_api/representer/uri_methods.rb @@ -21,6 +21,15 @@ def self_link end end + def vary_link + link(:vary) do + { + href: vary_url(represented) + vary_query_params, + templated: true, + } if vary_url(represented).present? && vary_params.present? + end + end + def profile_link link(:profile) { profile_url(represented) } end @@ -46,6 +55,18 @@ def self_url(represented) polymorphic_path([:api, rep]) end + def vary_url(represented) + self_url(represented) + end + + def vary_params + [] + end + + def vary_query_params + "{?#{vary_params.join(',')}}" + end + def becomes_represented_class(rep) return rep unless rep.respond_to?(:becomes) klass = rep.try(:item_class) || rep.class.try(:base_class) diff --git a/test/hal_api/controller/filtering_test.rb b/test/hal_api/controller/filtering_test.rb index d7f70d8..d03cd2f 100644 --- a/test/hal_api/controller/filtering_test.rb +++ b/test/hal_api/controller/filtering_test.rb @@ -8,10 +8,25 @@ class FilteringTestController < ActionController::Base filter_params :one, :two, :three, :four, :five, six: :date, seven: :time attr_accessor :filter_string + attr_accessor :no_facets def params { filters: filter_string } end + + def filter_facets + { + one: [{count: 2}], + two: [], + three: [ + {id: 'a', count: 4}, + {id: 'b', count: 5}, + {id: 'c', count: 0} + ], + four: [{count: 0}], + five: [{id: 'a', count: 0}] + } unless no_facets + end end let(:controller) { FilteringTestController.new } @@ -84,4 +99,24 @@ def params err.must_be_instance_of(HalApi::Errors::BadFilterValueError) end + it 'sets facets on the collection' do + controller.index_collection.facets[:one].must_equal [{'count' => 2}] + controller.index_collection.facets[:three].count.must_equal 2 + controller.index_collection.facets[:three][0].must_equal({'id' => 'a', 'count' => 4}) + controller.index_collection.facets[:three][1].must_equal({'id' => 'b', 'count' => 5}) + end + + it 'removes empty facets' do + controller.index_collection.facets.keys.must_include 'one' + controller.index_collection.facets.keys.must_include 'three' + controller.index_collection.facets[:three].map { |f| f['id'] }.wont_include 'c' + controller.index_collection.facets.keys.wont_include 'four' + controller.index_collection.facets.keys.wont_include 'five' + controller.index_collection.facets.keys.wont_include 'six' + end + + it 'does not set facets if there are none' do + controller.no_facets = true + controller.index_collection.facets.must_be_nil + end end diff --git a/test/hal_api/paged_collection_representer_test.rb b/test/hal_api/paged_collection_representer_test.rb index 458f41c..e0b0936 100644 --- a/test/hal_api/paged_collection_representer_test.rb +++ b/test/hal_api/paged_collection_representer_test.rb @@ -30,6 +30,13 @@ representer.represented_url.must_equal "api_stories_path" end + it 'has a vary link' do + representer.represented.options[:url] = "api_stories_path" + json['_links']['vary'].wont_be_nil + json['_links']['vary']['href'].must_equal 'api_stories_path{?page,per,zoom,filters,sorts}' + json['_links']['vary']['templated'].must_equal true + end + it 'uses a lambda for a url method' do representer.represented.options[:url] = ->(options){ options.keys.sort.join('/') } representer.href_url_helper({foo: 1, bar: 2, camp: 3}).must_equal "bar/camp/foo"