simple_json_api 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -2
  3. data/README.md +1 -1
  4. data/lib/generators/simple_json_api/resource/USAGE +8 -0
  5. data/lib/generators/simple_json_api/resource/resource_generator.rb +73 -0
  6. data/lib/generators/simple_json_api/resource/templates/controller_template.rb.erb +34 -0
  7. data/lib/generators/simple_json_api/resource/templates/serializer_template.rb.erb +14 -0
  8. data/lib/simple_json_api/api_node.rb +68 -0
  9. data/lib/simple_json_api/array_serializer.rb +15 -1
  10. data/lib/simple_json_api/association.rb +16 -0
  11. data/lib/simple_json_api/attribute.rb +9 -0
  12. data/lib/simple_json_api/builder.rb +35 -0
  13. data/lib/simple_json_api/dsl.rb +35 -5
  14. data/lib/simple_json_api/field_list.rb +2 -7
  15. data/lib/simple_json_api/helper.rb +5 -0
  16. data/lib/simple_json_api/include_list.rb +12 -7
  17. data/lib/simple_json_api/json_api_wrapper.rb +46 -0
  18. data/lib/simple_json_api/member/data.rb +14 -0
  19. data/lib/simple_json_api/member/included.rb +38 -0
  20. data/lib/simple_json_api/member/links.rb +9 -0
  21. data/lib/simple_json_api/member/meta.rb +9 -0
  22. data/lib/simple_json_api/refinements/active_record.rb +23 -0
  23. data/lib/simple_json_api/refinements/array.rb +13 -0
  24. data/lib/simple_json_api/refinements/symbol.rb +17 -0
  25. data/lib/simple_json_api/resource.rb +23 -0
  26. data/lib/simple_json_api/resource_serializer.rb +25 -24
  27. data/lib/simple_json_api/serializer.rb +39 -2
  28. data/lib/simple_json_api/serializer_factory.rb +22 -0
  29. data/lib/simple_json_api/version.rb +1 -1
  30. data/lib/simple_json_api.rb +7 -29
  31. data/simple_json_api.gemspec +2 -7
  32. data/test/generators/simple_json_api/resource_generator_test.rb +31 -0
  33. data/test/integration/render_basic_test.rb +10 -7
  34. data/test/integration/render_fields_test.rb +35 -16
  35. data/test/integration/render_include_test.rb +66 -63
  36. data/test/integration/render_nested_include_test.rb +38 -83
  37. data/test/integration/serializers_test.rb +1 -1
  38. data/test/setup/data.rb +7 -3
  39. data/test/setup/serializers.rb +2 -0
  40. data/test/test_helper.rb +3 -9
  41. data/test/unit/field_list_test.rb +0 -10
  42. data/test/unit/include_list_test.rb +35 -5
  43. data/test/unit/symbol_refinement_test.rb +16 -0
  44. metadata +25 -89
  45. data/lib/simple_json_api/active_record_refinements.rb +0 -21
  46. data/lib/simple_json_api/array_refinements.rb +0 -14
  47. data/lib/simple_json_api/json_api_builder.rb +0 -84
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a442243658287849495015d9e7d70033be758c9
4
- data.tar.gz: 47fa80783b4c0689f3e26051a012aeda9e077caf
3
+ metadata.gz: d342c421da25b887edeade3d14b1a145bbe3a352
4
+ data.tar.gz: 2720735c97db2a23d45ac6154460f2c92f8dfa67
5
5
  SHA512:
6
- metadata.gz: 39b59ac72db54031090ca63f62bdc8387cef3ce49039a49841ca7cb0df7cd0ea3972dbf88af350ed81b3c6b34e2d77e6baf2996eb3714525c35811f2942787e4
7
- data.tar.gz: 5d3a296127944821174cc21c352ad079989899501268d34517499b4700e44b88642d639b83770a35f7c210d5ad86a4bcf3939a14687c9789457ac280bcb3aff0
6
+ metadata.gz: 87165f1029afae88c79cae12d06d502ad77fe55c6d2a68cdb02b6446ef757b73cc2ea3e0906418083ba1f358f5a79a88a7348acdb8754227e6d24c65f9182038
7
+ data.tar.gz: 7f0428e1ab60247971c0ef04ad4a09a156ab62c5c784473024c90da6809ec6656fd77b006025593aa3b021852219830f585cbd004e401544a07a6c905fd2e825
data/Gemfile CHANGED
@@ -8,7 +8,6 @@ version = ENV['RAILS_VERSION'] || '4.1'
8
8
  case version
9
9
  when 'master'
10
10
  gem 'rails', github: 'rails/rails'
11
-
12
11
  # Learned from AMS
13
12
  # ugh https://github.com/rails/rails/issues/16063#issuecomment-48090125
14
13
  gem 'arel', github: 'rails/arel'
@@ -18,4 +17,17 @@ else
18
17
  fail GemfileError, "Unsupported Rails version - #{version}"
19
18
  end
20
19
 
21
- gem 'codeclimate-test-reporter', group: :test, require: nil
20
+ group :test do
21
+ gem 'codeclimate-test-reporter', require: nil
22
+ end
23
+
24
+ group :development do
25
+ gem 'rdoc'
26
+
27
+ gem 'awesome_print'
28
+ gem 'minitest-match_json'
29
+ gem 'diffy'
30
+ gem 'minitest'
31
+ gem 'minitest-reporters'
32
+ gem 'sqlite3'
33
+ end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SimpleJsonApi
2
2
 
3
- [![Build Status](https://travis-ci.org/ggordon/simple_json_api.svg?branch=master)](https://travis-ci.org/ggordon/simple_json_api) [![Code Climate](https://codeclimate.com/github/ggordon/simple_json_api/badges/gpa.svg)](https://codeclimate.com/github/ggordon/simple_json_api) [![Test Coverage](https://codeclimate.com/github/ggordon/simple_json_api/badges/coverage.svg)](https://codeclimate.com/github/ggordon/simple_json_api)
3
+ [![Build Status](https://travis-ci.org/ggordon/simple_json_api.svg?branch=master)](https://travis-ci.org/ggordon/simple_json_api) [![Code Climate](https://codeclimate.com/github/ggordon/simple_json_api/badges/gpa.svg)](https://codeclimate.com/github/ggordon/simple_json_api) [![Test Coverage](https://codeclimate.com/github/ggordon/simple_json_api/badges/coverage.svg)](https://codeclimate.com/github/ggordon/simple_json_api) [![Gem Version](https://badge.fury.io/rb/simple_json_api.svg)](http://badge.fury.io/rb/simple_json_api)
4
4
 
5
5
  A gem to render json following the [jsonapi](http://jsonapi.org) spec.
6
6
 
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate simple_json_api:resource resource
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,73 @@
1
+ module SimpleJsonApi
2
+ module Generators
3
+ # Generates the resource template files
4
+ class ResourceGenerator < Rails::Generators::NamedBase
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ argument :name, type: :string, required: true, banner: 'ResourceName'
8
+
9
+ class_option :model,
10
+ desc: 'Model class if different than Resource',
11
+ type: :string
12
+ class_option :namespace,
13
+ desc: 'Namespace for the generated files',
14
+ type: :string
15
+ class_option :controller,
16
+ desc: 'Base controller for resources',
17
+ type: :string
18
+ class_option :skip_serializer,
19
+ desc: "Don't generate a serializer file.",
20
+ type: :boolean
21
+ class_option :skip_controller,
22
+ desc: "Don't generate a controller file.",
23
+ type: :boolean
24
+ class_option :skip_service,
25
+ desc: "Don't generate a service file.",
26
+ type: :boolean
27
+ class_option :root_dir,
28
+ desc: "Root dir for generated code, default: '.'.",
29
+ type: :string
30
+
31
+ def create_resource
32
+ @namespace = options[:namespace]
33
+ @model = options[:model] || class_name
34
+ namespaced_name = [@namespace, class_name].compact.join('::')
35
+ @serializer_name = "#{namespaced_name}Serializer"
36
+ @controller_name = "#{namespaced_name.pluralize}Controller"
37
+ @base_controller = options[:controller] || 'ApplicationController'
38
+ @root_dir = options[:root_dir] || '.'
39
+ file_path = "#{@namespace.underscore}/#{class_name.underscore}"
40
+
41
+ check_model
42
+
43
+ unless options[:skip_serializer]
44
+ template 'serializer_template.rb.erb',
45
+ "#{@root_dir}/app/serializers/#{file_path}_serializer.rb"
46
+ # TODO: create serializer test
47
+ end
48
+
49
+ unless options[:skip_controller]
50
+ template 'controller_template.rb.erb',
51
+ "#{@root_dir}/app/controllers/#{file_path.pluralize}_controller.rb"
52
+ # TODO: create controller test
53
+ end
54
+
55
+ unless options[:skip_service]
56
+ # TODO: create service
57
+ # TODO: create service test
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def check_model
64
+ unless @model.constantize.ancestors.include?(ActiveRecord::Base)
65
+ fail NameError
66
+ end
67
+ rescue NameError
68
+ puts "Error: '#{@model}' is not an AR model"
69
+ exit
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,34 @@
1
+ class <%= @controller_name -%> < <%= @base_controller %>
2
+ include SimpleJsonApi::Helper
3
+
4
+ def index
5
+ @<%= class_name.underscore.gsub('/', '__').pluralize %> = <%= @model -%>.paginate(page: params[:page], per_page: params[:per_page]).to_a
6
+ render json: SimpleJsonApi.render(
7
+ model: @<%= class_name.underscore.gsub('/', '__').pluralize %>,
8
+ serializer: <%= @serializer_name %>,
9
+ fields: options_hash.fetch(:fields, nil),
10
+ include: options_hash.fetch(:include, nil)
11
+ ),
12
+ status: 200
13
+ end
14
+
15
+ def show
16
+ @<%= class_name.underscore.gsub('/', '__') %> = <%= @model -%>.find(params[:id])
17
+ render json: SimpleJsonApi.render(
18
+ model: @<%= class_name.underscore.gsub('/', '__') %>,
19
+ serializer: <%= @serializer_name %>,
20
+ fields: options_hash.fetch(:fields, nil),
21
+ include: options_hash.fetch(:include, nil)
22
+ ),
23
+ status: 200
24
+ end
25
+
26
+ private
27
+
28
+ def options_hash
29
+ {
30
+ fields: params[:fields],
31
+ include: params[:include]
32
+ }
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ class <%= @serializer_name -%> < SimpleJsonApi::ResourceSerializer
2
+ serializes :<%= class_name.underscore.gsub('/', '__').pluralize %>, model: <%= "::#{@model}" -%>, primary_key: :<%= @model.constantize.primary_key %>
3
+
4
+ <%- @model.constantize.column_names.each do |col| -%>
5
+ attribute :<%= col %>
6
+ <%- end -%>
7
+
8
+ <%- @model.constantize.reflect_on_all_associations.each do |assoc| -%>
9
+ <%- klass_name = assoc.options[:class_name] -%>
10
+ <%- serializer = [@namespace, klass_name].compact.join('::') + 'Serializer' if klass_name -%>
11
+ <%= assoc.macro %> :<%= assoc.name %><%= ', polymorphic: true' if assoc.options[:polymorphic] %><%= ', serializer: ' + serializer if serializer %>
12
+ <%- end -%>
13
+ end
14
+
@@ -0,0 +1,68 @@
1
+ require 'simple_json_api/refinements/symbol'
2
+
3
+ module SimpleJsonApi
4
+ # Node in the directed graph of associations (eventually)
5
+ # Will start as a tree with duplication
6
+ class ApiNode
7
+ extend Forwardable
8
+ def_delegators :serializer, :serialize
9
+
10
+ attr_reader :name
11
+ attr_reader :serializer
12
+ attr_reader :associations
13
+ attr_reader :assoc_list
14
+
15
+ def initialize(name, serializer, assoc_list, each_serializer = nil)
16
+ @name = name
17
+ @serializer = serializer
18
+ @each_serializer = each_serializer
19
+ @assoc_list = assoc_list
20
+ @associations = [] # list of api_nodes
21
+ end
22
+
23
+ def load
24
+ return self unless serializer_actual._associations
25
+ serializer_actual._associations.each do |association|
26
+ add_association(association)
27
+ end
28
+ self
29
+ end
30
+
31
+ def serializer_actual
32
+ @serializer_actual ||= @each_serializer || self.serializer
33
+ end
34
+
35
+ def add_association(association)
36
+ return unless @assoc_list.key? association.plural_name
37
+ resource = serializer.associated_object(association.name)
38
+ Array(resource).each do |object|
39
+ each_serializer = Serializer.for(object, association)
40
+ serializer = SerializerFactory.create(
41
+ object, each_serializer, self.serializer._builder
42
+ )
43
+ self <<
44
+ ApiNode.new(
45
+ association.plural_name,
46
+ serializer,
47
+ @assoc_list[association.plural_name],
48
+ serializer._each_serializer
49
+ ).load
50
+ end
51
+ end
52
+
53
+ def <<(node)
54
+ add_assoc(node)
55
+ end
56
+
57
+ def add_assoc(node)
58
+ @associations << node
59
+ end
60
+
61
+ # def display(offset = '')
62
+ # ap "DISPLAY: #{offset}#{@name}, #{@assoc_list}, #{Array(@object).first.class}, #{Array(@object).map(&:id)}"
63
+ # @associations.each do |assoc|
64
+ # assoc.display(offset + ' ')
65
+ # end
66
+ # end
67
+ end
68
+ end
@@ -5,12 +5,26 @@ module SimpleJsonApi
5
5
  def serialize
6
6
  _object.map do |object|
7
7
  serializer = _each_serializer.new(object, _builder)
8
- serializer.serialize
8
+ Resource.new(serializer.serialize)
9
9
  end
10
10
  end
11
11
 
12
+ def associated_object(association_name)
13
+ serializers.map { |serializer| serializer.send(association_name) }
14
+ end
15
+
12
16
  def _root_name
13
17
  _each_serializer._root_name
14
18
  end
19
+
20
+ def _associations
21
+ _each_serializer._associations if _each_serializer
22
+ end
23
+
24
+ def serializers
25
+ _object.map do |object|
26
+ _each_serializer.new(object, _builder)
27
+ end
28
+ end
15
29
  end
16
30
  end
@@ -0,0 +1,16 @@
1
+ # SimpleJsonApi
2
+ module SimpleJsonApi
3
+ # Wrapper for an included association
4
+ Association = Struct.new(:name, :type, :serializer, :polymorphic, :key) do
5
+ using Refinements::Symbol
6
+
7
+ def key
8
+ name_s = name.to_s
9
+ (type == :has_many) ? name_s.pluralize.to_sym : name_s.singularize.to_sym
10
+ end
11
+
12
+ def plural_name
13
+ name.pluralize
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ # SimpleJsonApi
2
+ module SimpleJsonApi
3
+ # Wrapper for an Attribute
4
+ Attribute = Struct.new(:name, :key) do
5
+ def key
6
+ self[:key] || self[:name]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ require 'simple_json_api/api_node'
2
+ require 'simple_json_api/field_list'
3
+ require 'simple_json_api/include_list'
4
+ require 'simple_json_api/refinements/active_record'
5
+ require 'simple_json_api/serializer_factory'
6
+
7
+ # SimpleJsonApi
8
+ module SimpleJsonApi
9
+ # The Builder to walk the hierarchy and construct the JSON
10
+ class Builder
11
+ extend Forwardable
12
+
13
+ attr_reader :wrapper
14
+ attr_reader :field_list
15
+ attr_reader :include
16
+ attr_reader :object
17
+ attr_reader :serializer
18
+
19
+ def_delegators :@field_list, :fields_for
20
+
21
+ # TODO: sort: nil
22
+ def initialize(object, wrapper, serializer, fields, include)
23
+ @object = object
24
+ @wrapper = wrapper
25
+ @field_list = FieldList.new(fields, serializer)
26
+ @include = IncludeList.new(include).parse
27
+ @serializer = SerializerFactory.create(object, serializer, self)
28
+ end
29
+
30
+ def as_json(options = nil)
31
+ root_node = ApiNode.new(serializer._root_name, serializer, include.include_hash).load
32
+ wrapper.new(root_node).as_json(options)
33
+ end
34
+ end
35
+ end
@@ -1,9 +1,17 @@
1
+ require 'simple_json_api/association'
2
+ require 'simple_json_api/attribute'
3
+ require 'simple_json_api/refinements/array'
4
+
1
5
  # SimpleJsonApi
2
6
  module SimpleJsonApi
3
7
  # Define the public API for creating serializers
4
8
  module DSL
9
+ using Refinements::Array
10
+
5
11
  attr_accessor :_associations
6
12
  attr_accessor :_attributes
13
+ attr_accessor :_default_fields
14
+ attr_accessor :_required_associations
7
15
  attr_reader :_root_name
8
16
 
9
17
  def inherited(base)
@@ -12,7 +20,7 @@ module SimpleJsonApi
12
20
  end
13
21
 
14
22
  def serializes(name, options = {})
15
- ResourceSerializer.register_serializer(
23
+ Serializer.register_serializer(
16
24
  { serializer: self, resource: name },
17
25
  options
18
26
  )
@@ -23,6 +31,16 @@ module SimpleJsonApi
23
31
  register_attribute(name, options)
24
32
  end
25
33
 
34
+ # Attributes presented if no fields specified
35
+ def default_fields(attrs)
36
+ @_default_fields = attrs
37
+ end
38
+
39
+ # Associations that are always included
40
+ def required_associations(assocs)
41
+ @_required_associations = assocs
42
+ end
43
+
26
44
  def belongs_to(name, options = {})
27
45
  register_association(name, :belongs_to, options)
28
46
  end
@@ -35,7 +53,7 @@ module SimpleJsonApi
35
53
  register_association(name, :has_one, options)
36
54
  end
37
55
 
38
- def default_fields
56
+ def default_attributes
39
57
  @_attributes.map(&:first).join(',')
40
58
  end
41
59
 
@@ -44,16 +62,28 @@ module SimpleJsonApi
44
62
  def register_association(name, type, options)
45
63
  serializer = options.fetch(:serializer, nil)
46
64
  polymorphic = options.fetch(:polymorphic, false)
47
- association = SimpleJsonApi::Association.new(
65
+ association = Association.new(
48
66
  name, type, serializer, polymorphic
49
67
  )
50
68
  _associations << association
51
- def_delegator :_object, association.name
69
+ define_assoc_method(association) # unless defined? association.name
70
+ end
71
+
72
+ def define_assoc_method(association)
73
+ if association.type == :has_many
74
+ define_method association.name do |includes = nil|
75
+ _object.send(association.name).includes(includes).to_a if _object.respond_to?(association.name)
76
+ end
77
+ else
78
+ define_method association.name do |includes = nil|
79
+ _object.send(association.name) if _object.respond_to?(association.name)
80
+ end
81
+ end
52
82
  end
53
83
 
54
84
  def register_attribute(name, options)
55
85
  key = options.fetch(:key, nil)
56
- attribute = SimpleJsonApi::Attribute.new(name, key)
86
+ attribute = Attribute.new(name, key)
57
87
  _attributes << attribute
58
88
  def_delegator :_object, attribute.name
59
89
  end
@@ -13,13 +13,10 @@ module SimpleJsonApi
13
13
 
14
14
  def parse
15
15
  result = {}
16
- case @fields
17
- when Hash
16
+ if @fields.is_a? Hash
18
17
  @fields.each do |resource, fields|
19
- result[resource.to_sym] = FIELD_LIST_PARSER.call(fields)
18
+ result[resource.to_sym] = fields.split(',').map(&:to_s)
20
19
  end
21
- when String
22
- result[@root_serializer._root_name] = FIELD_LIST_PARSER.call(@fields)
23
20
  end
24
21
  result
25
22
  end
@@ -27,7 +24,5 @@ module SimpleJsonApi
27
24
  def fields_for(resource)
28
25
  field_list.fetch(resource, nil)
29
26
  end
30
-
31
- FIELD_LIST_PARSER = ->(list) { list.split(',').map(&:to_s) }
32
27
  end
33
28
  end