diff --git a/Gemfile b/Gemfile index c9538bb5..85db93f5 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' gem 'rails', '~> 4.2.0.rc1' gem 'pg' -gem 'jbuilder', '~> 2.2' +gem 'restpack_serializer', '~> 0.5.6' gem 'sdoc', '~> 0.4', group: :doc gem 'spring', group: :development diff --git a/Gemfile.lock b/Gemfile.lock index 8dd5fb6d..421a22d7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,10 +64,10 @@ GEM hike (1.2.3) hitimes (1.2.2) i18n (0.7.0.beta1) - jbuilder (2.2.5) - activesupport (>= 3.0.0, < 5) - multi_json (~> 1.2) json (1.8.1) + kaminari (0.16.1) + actionpack (>= 3.0.0) + activesupport (>= 3.0.0) listen (2.8.3) celluloid (>= 0.15.2) rb-fsevent (>= 0.9.3) @@ -122,6 +122,10 @@ GEM ffi (>= 0.5.0) rdoc (4.1.2) json (~> 1.4) + restpack_serializer (0.5.6) + activerecord (>= 4.0.3, < 5.0) + activesupport (>= 4.0.3, < 5.0) + kaminari (~> 0.16.1) rspec (3.1.0) rspec-core (~> 3.1.0) rspec-expectations (~> 3.1.0) @@ -172,11 +176,11 @@ PLATFORMS DEPENDENCIES factory_girl_rails guard-rspec - jbuilder (~> 2.2) pg pry rails (~> 4.2.0.rc1) rb-fsevent + restpack_serializer (~> 0.5.6) rspec-rails sdoc (~> 0.4) spring diff --git a/app/serializers/board_serializer.rb b/app/serializers/board_serializer.rb new file mode 100644 index 00000000..467a5105 --- /dev/null +++ b/app/serializers/board_serializer.rb @@ -0,0 +1,5 @@ +class BoardSerializer + include TalkSerializer + all_attributes + can_include :discussions +end diff --git a/app/serializers/comment_serializer.rb b/app/serializers/comment_serializer.rb new file mode 100644 index 00000000..f69e033a --- /dev/null +++ b/app/serializers/comment_serializer.rb @@ -0,0 +1,11 @@ +class CommentSerializer + include TalkSerializer + all_attributes + can_include :focus + + def links + { + focus_type: model.focus_type + } if model.focus_id + end +end diff --git a/app/serializers/concerns/talk_serializer.rb b/app/serializers/concerns/talk_serializer.rb new file mode 100644 index 00000000..87d868e4 --- /dev/null +++ b/app/serializers/concerns/talk_serializer.rb @@ -0,0 +1,20 @@ +module TalkSerializer + extend ActiveSupport::Concern + + included do + include RestPack::Serializer + attr_reader :model + attributes :href, :links + can_filter_by(:section) if model_class.columns_hash.has_key? 'section' + end + + module ClassMethods + def all_attributes + attributes *model_class.attribute_names + end + end + + def links + { } + end +end diff --git a/app/serializers/conversation_serializer.rb b/app/serializers/conversation_serializer.rb new file mode 100644 index 00000000..532ab7cd --- /dev/null +++ b/app/serializers/conversation_serializer.rb @@ -0,0 +1,5 @@ +class ConversationSerializer + include TalkSerializer + all_attributes + can_include :messages +end diff --git a/app/serializers/discussion_serializer.rb b/app/serializers/discussion_serializer.rb new file mode 100644 index 00000000..199d9de1 --- /dev/null +++ b/app/serializers/discussion_serializer.rb @@ -0,0 +1,5 @@ +class DiscussionSerializer + include TalkSerializer + all_attributes + can_include :comments, :board, :user +end diff --git a/app/serializers/focus_serializer.rb b/app/serializers/focus_serializer.rb new file mode 100644 index 00000000..6e59e2b9 --- /dev/null +++ b/app/serializers/focus_serializer.rb @@ -0,0 +1,4 @@ +class FocusSerializer + include TalkSerializer + all_attributes +end diff --git a/app/serializers/message_serializer.rb b/app/serializers/message_serializer.rb new file mode 100644 index 00000000..a91c9dce --- /dev/null +++ b/app/serializers/message_serializer.rb @@ -0,0 +1,14 @@ +class MessageSerializer + include TalkSerializer + all_attributes + attributes :sender, :recipient + can_include :conversation + + def sender + UserSerializer.as_json model.sender + end + + def recipient + UserSerializer.as_json model.recipient + end +end diff --git a/app/serializers/moderation_serializer.rb b/app/serializers/moderation_serializer.rb new file mode 100644 index 00000000..8d1335e7 --- /dev/null +++ b/app/serializers/moderation_serializer.rb @@ -0,0 +1,4 @@ +class ModerationSerializer + include TalkSerializer + all_attributes +end diff --git a/app/serializers/tag_serializer.rb b/app/serializers/tag_serializer.rb new file mode 100644 index 00000000..9c4f17d5 --- /dev/null +++ b/app/serializers/tag_serializer.rb @@ -0,0 +1,4 @@ +class TagSerializer + include TalkSerializer + all_attributes +end diff --git a/app/serializers/user_conversation_serializer.rb b/app/serializers/user_conversation_serializer.rb new file mode 100644 index 00000000..ea8d4e6e --- /dev/null +++ b/app/serializers/user_conversation_serializer.rb @@ -0,0 +1,5 @@ +class UserConversationSerializer + include TalkSerializer + all_attributes + can_include :user, :conversation +end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb new file mode 100644 index 00000000..2163b841 --- /dev/null +++ b/app/serializers/user_serializer.rb @@ -0,0 +1,4 @@ +class UserSerializer + include TalkSerializer + all_attributes +end diff --git a/config/application.rb b/config/application.rb index 350e60e7..dabf2941 100644 --- a/config/application.rb +++ b/config/application.rb @@ -11,7 +11,9 @@ module Talk class Application < Rails::Application config.autoload_paths += [ - 'app/services' + 'app/services', + 'app/serializers/concerns', + 'app/serializers' ].collect{ |path| Rails.root.join path } end end diff --git a/config/initializers/restpack_serializer.rb b/config/initializers/restpack_serializer.rb new file mode 100644 index 00000000..a4f7c1fe --- /dev/null +++ b/config/initializers/restpack_serializer.rb @@ -0,0 +1,19 @@ +require 'restpack_serializer' + +module RestPack::Serializer + class SideLoadDataBuilder + # Bug fix. Prevent side loaded association from try to find non-existant records + def side_load_belongs_to + foreign_keys = @models.map { |model| model.send(@association.foreign_key) }.uniq.compact + side_load = foreign_keys.any? ? @association.klass.find(foreign_keys) : [] + json_model_data = side_load.map { |model| @serializer.as_json(model) } + { @association.plural_name.to_sym => json_model_data, meta: { } } + end + end +end + +# preload autoloaded serializers +Dir[Rails.root.join('app/serializers/**/*.rb')].sort.each do |path| + name = path.match(/serializers\/(.+)\.rb$/)[1] + name.classify.constantize unless path =~ /serializers\/concerns/ +end diff --git a/spec/factories/tags.rb b/spec/factories/tags.rb index 29680408..94d1f1cc 100644 --- a/spec/factories/tags.rb +++ b/spec/factories/tags.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :tag do - + name{ "tag#{ id }" } end end diff --git a/spec/serializers/board_serializer_spec.rb b/spec/serializers/board_serializer_spec.rb new file mode 100644 index 00000000..e6d490b5 --- /dev/null +++ b/spec/serializers/board_serializer_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe BoardSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all, including: [:discussions] +end diff --git a/spec/serializers/comment_serializer_spec.rb b/spec/serializers/comment_serializer_spec.rb new file mode 100644 index 00000000..9f343688 --- /dev/null +++ b/spec/serializers/comment_serializer_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +RSpec.describe CommentSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all, including: [:focus] + + it 'should specify the focus type in links' do + subject = create :subject + comment = create :comment_for_focus, focus: subject + json = CommentSerializer.resource id: comment.id + comment_json = json[:comments].first + expect(comment_json[:links]).to eql focus: subject.id.to_s, focus_type: 'Subject' + end +end diff --git a/spec/serializers/conversation_serializer_spec.rb b/spec/serializers/conversation_serializer_spec.rb new file mode 100644 index 00000000..2f057ba1 --- /dev/null +++ b/spec/serializers/conversation_serializer_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe ConversationSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all, including: [:messages] +end diff --git a/spec/serializers/discussion_serializer_spec.rb b/spec/serializers/discussion_serializer_spec.rb new file mode 100644 index 00000000..d0c1db18 --- /dev/null +++ b/spec/serializers/discussion_serializer_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe DiscussionSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all, including: [:comments, :board, :user] +end diff --git a/spec/serializers/focus_serializer_spec.rb b/spec/serializers/focus_serializer_spec.rb new file mode 100644 index 00000000..64cbde0a --- /dev/null +++ b/spec/serializers/focus_serializer_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe FocusSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all +end diff --git a/spec/serializers/message_serializer_spec.rb b/spec/serializers/message_serializer_spec.rb new file mode 100644 index 00000000..8d5ed17e --- /dev/null +++ b/spec/serializers/message_serializer_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +RSpec.describe MessageSerializer, type: :serializer do + let(:conversation){ create :conversation_with_messages } + let(:object){ conversation.messages.first } + it_behaves_like 'a talk serializer', exposing: :all, including: [:conversation] + + it 'should sideload sender' do + json = MessageSerializer.resource id: object.id + message_json = json[:messages].first + expect(message_json[:sender][:id]).to eql object.sender.id + expect(message_json[:recipient][:id]).to eql object.recipient.id + end +end diff --git a/spec/serializers/moderation_serializer_spec.rb b/spec/serializers/moderation_serializer_spec.rb new file mode 100644 index 00000000..e1109e92 --- /dev/null +++ b/spec/serializers/moderation_serializer_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe ModerationSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all +end diff --git a/spec/serializers/tag_serializer_spec.rb b/spec/serializers/tag_serializer_spec.rb new file mode 100644 index 00000000..d6e016dc --- /dev/null +++ b/spec/serializers/tag_serializer_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe TagSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all +end diff --git a/spec/serializers/user_conversation_serializer_spec.rb b/spec/serializers/user_conversation_serializer_spec.rb new file mode 100644 index 00000000..db98bb8e --- /dev/null +++ b/spec/serializers/user_conversation_serializer_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe UserConversationSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all, including: [:user, :conversation] +end diff --git a/spec/serializers/user_serializer_spec.rb b/spec/serializers/user_serializer_spec.rb new file mode 100644 index 00000000..b62e5694 --- /dev/null +++ b/spec/serializers/user_serializer_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe UserSerializer, type: :serializer do + it_behaves_like 'a talk serializer', exposing: :all +end diff --git a/spec/support/shared_examples_for_talk_serializer.rb b/spec/support/shared_examples_for_talk_serializer.rb new file mode 100644 index 00000000..094d41a7 --- /dev/null +++ b/spec/support/shared_examples_for_talk_serializer.rb @@ -0,0 +1,46 @@ +RSpec.shared_context 'a serializer' do + let(:serializer){ described_class } + let(:model){ serializer.model_class } + let(:instance){ FactoryGirl.create serializer.singular_key } + let(:model_instance){ instance } + let(:json){ serializer.as_json model_instance } +end + +RSpec.shared_examples 'a talk serializer' do |exposing: nil, including: nil| + include_context 'a serializer' + let(:model_instance){ defined?(object) ? object : instance } + + describe 'attributes' do + subject{ json } + + if exposing == :all + described_class.model_class.attribute_names.each do |name| + it{ is_expected.to include name.to_sym } + end + elsif exposing + exposing.each do |name| + it{ is_expected.to include name.to_sym } + end + end + + it{ is_expected.to include :href } + end + + describe 'associations' do + if including + including.each do |association| + it "should allow inclusion of #{ association }" do + association = association.to_s + json = serializer.resource id: model_instance.id, include: association + association_key = association.to_s.pluralize.to_sym + expect(json[:linked]).to include association_key + + macro = model.reflect_on_association(association).macro + unless macro.in? [:belongs_to, :has_one] + expect(json[:meta]).to include association_key + end + end + end + end + end +end