From a7ce076ec7e7769b72637aca6f593cc8f0b11f04 Mon Sep 17 00:00:00 2001 From: Zander Hill Date: Sun, 9 Feb 2014 22:12:00 +0000 Subject: [PATCH 1/3] [Issue #13] Add render syntactic sugar As in issue #13 an example solution provided by @jrhe an implementation of said feature has been created. As per the discussion in the thread this is only a helper to be able to reach the available options provided in the active_model_serializer gem. usage is as follows: ```ruby get '/some_path' do collection = Collection.all render collection, { meta: { current_page: 5 }, meta_key: :pagination_info } end ``` The return value would be: `{ pagination_info: { current_page: 5 }, collection: [item, item] }` If given without a `meta_key` it would return as: `{ meta: { current_page: 5 }, collection: [item, item] }` Any feedback appreciated. @zph, @olleolleolle and @bjoska --- README.md | 10 +++ .../endpoint_extension.rb | 23 ++++++- .../formatter.rb | 37 ++++++++++- .../render_spec.rb | 65 +++++++++++++++++++ .../endpoint_extension_spec.rb | 39 +++++++++++ .../formatter_spec.rb | 28 ++++++++ 6 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 spec/features/grape-active_model_serializers/render_spec.rb create mode 100644 spec/grape-active_model_serializers/endpoint_extension_spec.rb diff --git a/README.md b/README.md index a5b8026..c11dbad 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,16 @@ namespace 'foo', :serializer => :bar do end ``` +### Custom metadata along with the resources + +```ruby +# Control any additional metadata using meta and meta_key +get "/homes" + collection = Home.all + render collection, { meta: { page: 5, current_page: 3 }, meta_key: :pagination_info } +end +``` + ### current_user One of the nice features of ActiveModel::Serializers is that it diff --git a/lib/grape-active_model_serializers/endpoint_extension.rb b/lib/grape-active_model_serializers/endpoint_extension.rb index 58bad83..9573e45 100644 --- a/lib/grape-active_model_serializers/endpoint_extension.rb +++ b/lib/grape-active_model_serializers/endpoint_extension.rb @@ -27,9 +27,26 @@ module Grape end end - def default_serializer_options; end - def url_options; end - end + def render(resources, meta={}) + set_meta_and_meta_key(meta) + resources + end + def default_serializer_options; end + + def url_options; end + + private + + def set_meta_and_meta_key(meta) + if meta.has_key?(:meta) + Formatter::ActiveModelSerializers.meta = meta[:meta] + if meta.has_key?(:meta_key) + Formatter::ActiveModelSerializers.meta_key = meta[:meta_key] + end + end + end + + end Endpoint.send(:include, EndpointExtension) end diff --git a/lib/grape-active_model_serializers/formatter.rb b/lib/grape-active_model_serializers/formatter.rb index a6247cc..6f605dc 100644 --- a/lib/grape-active_model_serializers/formatter.rb +++ b/lib/grape-active_model_serializers/formatter.rb @@ -20,7 +20,42 @@ module Grape # ensure we have an root to fallback on endpoint.controller_name = default_root(endpoint) end - ::ActiveModel::Serializer.build_json(endpoint, resource, options) + + ::ActiveModel::Serializer.build_json(endpoint, + resource, + options.merge(other_options) + ) + end + + def other_options + options = {} + if @meta_content_items + meta_option = @meta_content_items[:meta] + @meta_content_items.delete(:meta) + options[:meta] = meta_option if meta_option + if @meta_key + key_option = @meta_key[:meta_key] + @meta_key.delete(:meta_key) + options[:meta_key] = key_option if key_option + end + end + options + end + + def meta + @meta_content_items || {} + end + + def meta=(meta_content) + @meta_content_items = { meta: meta_content } if meta_content + end + + def meta_key + @meta_key || {} + end + + def meta_key=(key) + @meta_key = { meta_key: key } if key end def build_options_from_endpoint(endpoint) diff --git a/spec/features/grape-active_model_serializers/render_spec.rb b/spec/features/grape-active_model_serializers/render_spec.rb new file mode 100644 index 0000000..b4154e1 --- /dev/null +++ b/spec/features/grape-active_model_serializers/render_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' +require 'support/models/user' +require 'support/serializers/user_serializer' +require 'grape-active_model_serializers' +require 'securerandom' + +describe '#render' do + let(:app) { Class.new(Grape::API) } + + before do + app.format :json + app.formatter :json, Grape::Formatter::ActiveModelSerializers + end + + def get_resource_with(meta) + url = "/#{SecureRandom.hex}" + app.get(url) do + render User.new(first_name: 'Jeff'), meta + end + get url + JSON.parse last_response.body + end + + context 'with meta key' do + it 'includes meta key and content' do + result = get_resource_with({ meta: { total: 2 }}) + expect(result).to have_key('meta') + expect(result.fetch('meta')).to eq({ 'total' => 2 }) + end + end + + context 'with a custom meta_key' do + + it 'includes the custom meta key name' do + result = get_resource_with({ meta: { total: 2 }, meta_key: :custom_key_name }) + expect(result).to have_key('custom_key_name') + expect(result.fetch('custom_key_name')).to eq({ 'total' => 2 }) + end + + it 'ignores a lonely meta_key' do + result = get_resource_with({ meta_key: :custom_key_name }) + expect(result).not_to have_key('meta') + expect(result).not_to have_key('custom_key_name') + end + end + + context 'junk keys' do + + it 'ignores junk keys' do + result = get_resource_with({ junk_key: { total: 2 } }) + expect(result).not_to have_key('junk_key') + end + + it 'ignores empty meta_key' do + result = get_resource_with({ meta: { total: 2 }, meta_key: nil }) + expect(result).to have_key('meta') + end + + it 'ignores empty meta' do + result = get_resource_with({ meta: nil }) + expect(result).not_to have_key('meta') + end + + end +end diff --git a/spec/grape-active_model_serializers/endpoint_extension_spec.rb b/spec/grape-active_model_serializers/endpoint_extension_spec.rb new file mode 100644 index 0000000..04ddf17 --- /dev/null +++ b/spec/grape-active_model_serializers/endpoint_extension_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe 'Grape::EndpointExtension' do + + subject { Grape::Endpoint.new(nil, {path: '/', method: 'foo'}) } + + let(:serializer) { Grape::Formatter::ActiveModelSerializers } + + let(:user) do + Object.new do + def name + 'sven' + end + end + end + + let(:users) { [user, user] } + + describe "#render" do + it { should respond_to(:render) } + let (:meta_content) { { total: 2 } } + let (:meta_full) { { meta: meta_content } } + context 'supplying meta' do + it 'passes through the Resource and uses given meta settings' do + expect(serializer).to receive(:meta=).with(meta_content) + expect(subject.render(users, meta_full)).to eq(users) + end + end + context 'supplying meta and key' do + let (:meta_key) { { meta_key: :custom_key_name } } + it 'passes through the Resource and uses given meta settings' do + expect(serializer).to receive(:meta=).with(meta_content) + expect(serializer).to receive(:meta_key=).with(meta_key[:meta_key]) + expect(subject.render(users, meta_full.merge(meta_key))).to eq(users) + end + end + end + +end diff --git a/spec/grape-active_model_serializers/formatter_spec.rb b/spec/grape-active_model_serializers/formatter_spec.rb index a9f807c..e5ac396 100644 --- a/spec/grape-active_model_serializers/formatter_spec.rb +++ b/spec/grape-active_model_serializers/formatter_spec.rb @@ -2,5 +2,33 @@ require 'spec_helper' require 'grape-active_model_serializers/formatter' describe Grape::Formatter::ActiveModelSerializers do + subject { Grape::Formatter::ActiveModelSerializers } + it { should respond_to(:meta) } + it { should respond_to(:meta=) } + it { should respond_to(:meta_key) } + it { should respond_to(:meta_key=) } + context '#meta' do + it 'will silently accept falsy input but return empty Hash' do + subject.meta = nil + expect(subject.meta).to eq({}) + end + + it 'will wrap valid input in the meta: {} wrapper' do + subject.meta = { total: 2 } + expect(subject.meta).to eq({ meta: { total: 2 } }) + end + end + + context '#meta_key' do + it 'will silently accept falsy input but return empty Hash' do + subject.meta_key = nil + expect(subject.meta_key).to eq({}) + end + + it 'will wrap valid input in the meta_key: {} wrapper' do + subject.meta_key = :custom_key_name + expect(subject.meta_key).to eq({ meta_key: :custom_key_name }) + end + end end From e30af569b283c03d3493d9b73b0355e55f331b36 Mon Sep 17 00:00:00 2001 From: Stephane Bounmy Date: Sun, 29 Jun 2014 22:18:08 +0200 Subject: [PATCH 2/3] support active model serializer 0.9.x --- grape-active_model_serializers.gemspec | 2 +- .../formatter.rb | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/grape-active_model_serializers.gemspec b/grape-active_model_serializers.gemspec index 2858c92..fc68ea4 100644 --- a/grape-active_model_serializers.gemspec +++ b/grape-active_model_serializers.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |gem| gem.licenses = ['MIT'] gem.add_dependency "grape", "~> 0.3" - gem.add_dependency "active_model_serializers", ">= 0.8.1" + gem.add_dependency "active_model_serializers", ">= 0.9.0.alpha1" gem.add_development_dependency "rspec" gem.add_development_dependency "rack-test" diff --git a/lib/grape-active_model_serializers/formatter.rb b/lib/grape-active_model_serializers/formatter.rb index 6f605dc..49b3c19 100644 --- a/lib/grape-active_model_serializers/formatter.rb +++ b/lib/grape-active_model_serializers/formatter.rb @@ -16,28 +16,27 @@ module Grape endpoint = env['api.endpoint'] options = build_options_from_endpoint(endpoint) - if resource.respond_to?(:to_ary) && !resource.empty? - # ensure we have an root to fallback on - endpoint.controller_name = default_root(endpoint) + if serializer = options.fetch(:serializer, ActiveModel::Serializer.serializer_for(resource)) + options[:scope] = serialization_scope unless options.has_key?(:scope) + if resource.respond_to?(:to_ary) && !resource.empty? + # ensure we have an root to fallback on + options[:resource_name] = default_root(endpoint) + end + serializer.new(resource, options.merge(other_options)) end - - ::ActiveModel::Serializer.build_json(endpoint, - resource, - options.merge(other_options) - ) end def other_options options = {} if @meta_content_items - meta_option = @meta_content_items[:meta] - @meta_content_items.delete(:meta) - options[:meta] = meta_option if meta_option if @meta_key key_option = @meta_key[:meta_key] @meta_key.delete(:meta_key) options[:meta_key] = key_option if key_option end + meta_option = @meta_content_items[:meta] + @meta_content_items.delete(:meta) + options[key_option || :meta] = meta_option if meta_option end options end @@ -73,6 +72,10 @@ module Grape endpoint.options[:path][0].to_s.split('/')[-1] end end + + def serialization_scope + :current_user + end end end end From 94a6122c9e3ddd3066e7bb652cd420e90be27a45 Mon Sep 17 00:00:00 2001 From: Stephane Bounmy Date: Sun, 6 Jul 2014 00:12:41 +0200 Subject: [PATCH 3/3] fix: when resource is empty, keep json resource root --- lib/grape-active_model_serializers/formatter.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/grape-active_model_serializers/formatter.rb b/lib/grape-active_model_serializers/formatter.rb index 49b3c19..4dc28d3 100644 --- a/lib/grape-active_model_serializers/formatter.rb +++ b/lib/grape-active_model_serializers/formatter.rb @@ -18,10 +18,8 @@ module Grape if serializer = options.fetch(:serializer, ActiveModel::Serializer.serializer_for(resource)) options[:scope] = serialization_scope unless options.has_key?(:scope) - if resource.respond_to?(:to_ary) && !resource.empty? - # ensure we have an root to fallback on - options[:resource_name] = default_root(endpoint) - end + # ensure we have an root to fallback on + options[:resource_name] = default_root(endpoint) if resource.respond_to?(:to_ary) serializer.new(resource, options.merge(other_options)) end end