Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions lib/restpack_serializer/factory.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
class RestPack::Serializer::Factory
def self.create(*identifiers)
serializers = identifiers.map { |identifier| self.classify(identifier) }
serializers.count == 1 ? serializers.first : serializers
end
module RestPack::Serializer

class UnknownSerializer < StandardError; end

private
class Factory

def self.classify(identifier)
normalised_identifier = identifier.to_s.underscore
[normalised_identifier, normalised_identifier.singularize].each do |format|
klass = RestPack::Serializer.class_map[format]
return klass.new if klass
def self.create(*identifiers)
serializers = identifiers.map { |identifier| self.classify(identifier) }
serializers.count == 1 ? serializers.first : serializers
end

raise "Invalid RestPack::Serializer : #{identifier}"
private

def self.classify(identifier)
normalised_identifier = identifier.to_s.underscore
[normalised_identifier, normalised_identifier.singularize].each do |format|
klass = RestPack::Serializer.class_map[format]
return klass.new if klass
end

raise UnknownSerializer.new("Unknown serializer class: #{identifier}")
end
end
end
26 changes: 25 additions & 1 deletion lib/restpack_serializer/serializable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def as_json(model, context = {})
def custom_attributes
{}
end

def url
self.class.url
end

private

Expand All @@ -69,7 +73,22 @@ def add_links(model, data)
data[:links] ||= {}
links_value = case
when association.macro == :belongs_to
model.send(association.foreign_key).try(:to_s)
if association.polymorphic?
linked_id = model.send(association.foreign_key)
.try(:to_s)
linked_type = model.send(association.foreign_type)
.try(:to_s)
.demodulize
.underscore
.pluralize
{
href: "/#{linked_type}/#{linked_id}",
id: linked_id,
type: linked_type
}
else
model.send(association.foreign_key).try(:to_s)
end
when association.macro.to_s.match(/has_/)
if model.send(association.name).loaded?
model.send(association.name).collect { |associated| associated.id.to_s }
Expand Down Expand Up @@ -126,6 +145,11 @@ def singular_key
def plural_key
self.key
end

def url(path=nil)
return @url || plural_key unless path
@url = path
end
end
end
end
6 changes: 5 additions & 1 deletion lib/restpack_serializer/serializable/paging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ def page_with_options(options)

if options.include_links
result.links = self.links
Array(RestPack::Serializer::Factory.create(*options.include)).each do |serializer|
linkable_types = result.links.values.map { |link| link[:type].to_s }
includes = options.include.select do |include|
linkable_types.include?(include)
end
Array(RestPack::Serializer::Factory.create(*includes)).each do |serializer|
result.links.merge! serializer.class.links
end
end
Expand Down
18 changes: 14 additions & 4 deletions lib/restpack_serializer/serializable/side_load_data_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ module RestPack
module Serializer
class SideLoadDataBuilder

def initialize(association, models, serializer)
def initialize(association, models)
@association = association
@models = models
@serializer = serializer
end

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) }
json_model_data = side_load.map do |model|
model_serializer(model).as_json(model)
end
{ @association.plural_name.to_sym => json_model_data, meta: { } }
end

Expand Down Expand Up @@ -43,9 +44,18 @@ def model_ids
@models.map(&:id)
end

def model_serializer(model)
serializer_type = if @association.polymorphic?
model.class.name
else
@association.class_name
end
RestPack::Serializer::Factory.create(serializer_type)
end

def has_association_relation
return {} if @models.empty?
serializer_class = @serializer.class
serializer_class = RestPack::Serializer::Factory.create(@association.class_name).class
options = RestPack::Serializer::Options.new(serializer_class)
yield options
options.include_links = false
Expand Down
55 changes: 42 additions & 13 deletions lib/restpack_serializer/serializable/side_loading.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,25 @@ def can_includes
end

def can_include(*includes)
@can_include_options = {}
@can_include_options = includes.last if includes.last.is_a?(Hash)
@can_includes ||= []
@can_includes += includes
@can_includes += includes.flat_map do
|include| include.try(:keys)|| include
end
end

def links
{}.tap do |links|
associations.each do |association|
if association.macro == :belongs_to
link_key = "#{self.key}.#{association.name}"
href = "/#{association.plural_name}/{#{link_key}}"
non_polymorphic_associations.each do |association|
link_key = if association.macro == :belongs_to
"#{key}.#{association.name}"
elsif association.macro.to_s.match(/has_/)
singular_key = self.key.to_s.singularize
link_key = "#{self.key}.#{association.plural_name}"
href = "/#{association.plural_name}?#{singular_key}_id={#{key}.id}"
"#{key}.#{association.plural_name}"
end

links.merge!(link_key => {
:href => href_prefix + href,
:href => href_prefix + url_for_association(association),
:type => association.plural_name.to_sym
}
)
Expand All @@ -53,13 +54,16 @@ def associations

private

def non_polymorphic_associations
associations.select do |association|
!association.polymorphic?
end
end

def side_load(include, models, options)
association = association_from_include(include)
return {} unless supported_association?(association.macro)
serializer = RestPack::Serializer::Factory.create(association.class_name)
builder = RestPack::Serializer::SideLoadDataBuilder.new(association,
models,
serializer)
builder = RestPack::Serializer::SideLoadDataBuilder.new(association,models)
builder.send("side_load_#{association.macro}")
end

Expand Down Expand Up @@ -92,5 +96,30 @@ def raise_invalid_include(include)
raise RestPack::Serializer::InvalidInclude.new,
":#{include} is not a valid include for #{self.model_class}"
end

def url_from_association(association)
serializer_from_association_class(association).url
end

def url_for_association(association)
identifier = if association.macro == :belongs_to
"/{#{key}.#{association.name}}"
else association.macro.to_s.match(/has_/)
param = can_include_options(association)[:param] || "#{singular_key}_id"
value = can_include_options(association)[:value] || "id"

"?#{param}={#{key}.#{value}}"
end

"/#{url_from_association(association)}#{identifier}"
end

def can_include_options(association)
@can_include_options.fetch(association.name.to_sym, {})
end

def serializer_from_association_class(association)
RestPack::Serializer::Factory.create(association.class_name)
end
end
end
12 changes: 12 additions & 0 deletions spec/factory/factory_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
it "creates multi-word string" do
factory.create("AlbumReview").should be_an_instance_of(MyApp::AlbumReviewSerializer)
end

it "creates multi-word lowercase string" do
factory.create("album_review").should be_an_instance_of(MyApp::AlbumReviewSerializer)
end
Expand All @@ -46,4 +47,15 @@
end
end

describe "unknown serializer word" do

it "raises a custom exception" do
unknown_serializer_class = "UnknownModelType"
message = "Unknown serializer class: #{unknown_serializer_class}"
expect do
factory.create(unknown_serializer_class)
end.to raise_error(RestPack::Serializer::UnknownSerializer, message)
end
end

end
28 changes: 28 additions & 0 deletions spec/fixtures/db.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
t.string "title"
t.integer "year"
t.integer "artist_id"
t.string "producer"
t.datetime "created_at"
t.datetime "updated_at"
end
Expand Down Expand Up @@ -62,6 +63,17 @@
t.integer :artist_id
t.integer :stalker_id
end

create_table "producers", force: true do |t|
t.string :name
t.string :album
end

create_table "generic_metadata", force: true do |t|
t.integer :linked_id
t.string :linked_type
t.string :some_stuff_about_the_link
end
end

module MyApp
Expand All @@ -72,6 +84,8 @@ class Artist < ActiveRecord::Base
has_many :songs
has_many :payments
has_many :fans, :through => :payments
has_many :generic_metadata, as: :linked

has_and_belongs_to_many :stalkers
end

Expand All @@ -82,6 +96,8 @@ class Album < ActiveRecord::Base
belongs_to :artist
has_many :songs
has_many :album_reviews
has_many :producers, foreign_key: :album
has_many :generic_metadata, as: :linked
end

class AlbumReview < ActiveRecord::Base
Expand All @@ -94,6 +110,7 @@ class Song < ActiveRecord::Base

attr_accessible :title, :artist, :album

has_many :generic_metadata, as: :linked
belongs_to :artist
belongs_to :album
end
Expand All @@ -108,11 +125,22 @@ class Payment < ActiveRecord::Base
class Fan < ActiveRecord::Base
attr_accessible :name
has_many :payments
has_many :generic_metadata, as: :linked
has_many :artists, :through => :albums
end

class Stalker < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :artists
end

class Producer < ActiveRecord::Base
attr_accessible :name
belongs_to :album, foreign_key: :album
end

class GenericMetadatum < ActiveRecord::Base
attr_accessible :some_stuff_about_the_link
belongs_to :linked, polymorphic: true
end
end
15 changes: 13 additions & 2 deletions spec/fixtures/serializers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module MyApp
class SongSerializer
include RestPack::Serializer
attributes :id, :title, :album_id
can_include :albums, :artists
can_include :artists, :albums
can_filter_by :title
can_sort_by :id, :title

Expand All @@ -14,7 +14,7 @@ def title
class AlbumSerializer
include RestPack::Serializer
attributes :id, :title, :year, :artist_id
can_include :artists, :songs
can_include :artists, :songs, producers: { param: "album", value: "title" }
can_filter_by :year
end

Expand All @@ -39,4 +39,15 @@ class StalkerSerializer
include RestPack::Serializer
attributes :id, :name
end

class ProducerSerializer
include RestPack::Serializer
attributes :id, :name
end

class GenericMetadatumSerializer
include RestPack::Serializer
attributes :id, :some_stuff_about_the_link
can_include :linked
end
end
22 changes: 22 additions & 0 deletions spec/serializable/paging_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,17 @@
page[:links]['artists.albums'].should_not == nil
end
end

context "with an unknown include link" do
let(:params) { { include: "unknown_link" } }

it "raises an exception" do
message = ":unknown_link is not a valid include for MyApp::Song"
expect do
page[:links]
end.to raise_error(RestPack::Serializer::InvalidInclude, message)
end
end
end

context "when filtering" do
Expand Down Expand Up @@ -256,6 +267,17 @@
page[:meta][:songs][:page_count].should == 6
end
end

context "with an unknown include link" do
let(:params) { { include: "unknown_link" } }

it "raises an exception" do
message = ":unknown_link is not a valid include for MyApp::Song"
expect do
page[:links]
end.to raise_error(RestPack::Serializer::InvalidInclude, message)
end
end
end

context "paging with paged side-load" do
Expand Down
Loading