graphiti 1.0.alpha.1 → 1.0.alpha.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -8
  3. data/README.md +2 -72
  4. data/bin/console +1 -1
  5. data/exe/graphiti +5 -0
  6. data/graphiti.gemspec +2 -2
  7. data/lib/generators/jsonapi/resource_generator.rb +1 -1
  8. data/lib/generators/jsonapi/templates/application_resource.rb.erb +0 -2
  9. data/lib/graphiti.rb +17 -1
  10. data/lib/graphiti/adapters/abstract.rb +32 -0
  11. data/lib/graphiti/adapters/active_record/base.rb +8 -0
  12. data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +2 -9
  13. data/lib/graphiti/cli.rb +45 -0
  14. data/lib/graphiti/configuration.rb +12 -0
  15. data/lib/graphiti/context.rb +1 -0
  16. data/lib/graphiti/errors.rb +105 -3
  17. data/lib/graphiti/filter_operators.rb +13 -3
  18. data/lib/graphiti/query.rb +8 -0
  19. data/lib/graphiti/rails.rb +1 -1
  20. data/lib/graphiti/railtie.rb +21 -2
  21. data/lib/graphiti/renderer.rb +2 -1
  22. data/lib/graphiti/resource.rb +11 -2
  23. data/lib/graphiti/resource/configuration.rb +1 -0
  24. data/lib/graphiti/resource/dsl.rb +4 -3
  25. data/lib/graphiti/resource/interface.rb +15 -0
  26. data/lib/graphiti/resource/links.rb +92 -0
  27. data/lib/graphiti/resource/polymorphism.rb +1 -0
  28. data/lib/graphiti/resource/sideloading.rb +13 -5
  29. data/lib/graphiti/resource_proxy.rb +1 -1
  30. data/lib/graphiti/schema.rb +169 -0
  31. data/lib/graphiti/schema_diff.rb +174 -0
  32. data/lib/graphiti/scoping/filter.rb +1 -1
  33. data/lib/graphiti/sideload.rb +47 -18
  34. data/lib/graphiti/sideload/belongs_to.rb +5 -1
  35. data/lib/graphiti/sideload/has_many.rb +5 -1
  36. data/lib/graphiti/sideload/many_to_many.rb +6 -2
  37. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +3 -1
  38. data/lib/graphiti/types.rb +39 -17
  39. data/lib/graphiti/util/class.rb +22 -0
  40. data/lib/graphiti/util/hash.rb +16 -0
  41. data/lib/graphiti/util/hooks.rb +2 -2
  42. data/lib/graphiti/util/link.rb +48 -0
  43. data/lib/graphiti/util/serializer_relationships.rb +94 -0
  44. data/lib/graphiti/version.rb +1 -1
  45. metadata +16 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ca5c32df5a0a64e436b786d26cbe51074d18dc4
4
- data.tar.gz: c9ddad6eb9c3586ec3c7f9568696c7776c508834
3
+ metadata.gz: 736b8601bf09408a0fa6ed8f2a0c148a79f7c285
4
+ data.tar.gz: 9b0adbc770aea3e609f15804db1b33ddba1dee13
5
5
  SHA512:
6
- metadata.gz: 29bbb74fac76a93090f94e08cf38c0021bbd531d31979dbb4a1ba720ebd366df02b7f3b4fa405c1a5a743db8fe85c71cfb608b24610f1d5378d31a769441b24c
7
- data.tar.gz: 022b06dc74a994896b1c6478a716a540f04218071f7cd5baeab44fcb1d2c67a38aef385842ae8aeeb65226db02c70941f33fe8676a6c2d96185a89a2e262d21c
6
+ metadata.gz: 467e32b7154d37819d6bc4598872e66f3a138120ea2781253238ce4b6fc426b416184c79e5ce58a6d9b596b5754cc87bf7ffb2fc02ab2867bdc6eb00edbe8939
7
+ data.tar.gz: c8ec00841469671ef261283f3dd6f556c095742be04d31f1bfa9d633ee5066cd009a790560bc65a61845b6d2db366cff5aa2da7b0226e0a4a2ff7146677c2b78
@@ -10,11 +10,3 @@ install: bundle install --retry=3 --jobs=3
10
10
  gemfile:
11
11
  - gemfiles/rails_4.gemfile
12
12
  - gemfiles/rails_5.gemfile
13
-
14
- deploy:
15
- provider: rubygems
16
- api_key: $RUBYGEMS_API_KEY
17
- gem: graphiti-rb
18
- on:
19
- tags: true
20
- repo: jsonapi-suite/jsonapi_compliable
data/README.md CHANGED
@@ -1,75 +1,5 @@
1
1
  ### Graphiti
2
2
 
3
- [![Build Status](https://travis-ci.org/jsonapi-suite/jsonapi_compliable.svg?branch=master)](https://travis-ci.org/jsonapi-suite/jsonapi_compliable)
3
+ ![Build Status](https://travis-ci.org/graphiti-api/graphiti.svg?branch=master)
4
4
 
5
- [JSONAPI Suite Website](https://jsonapi-suite.github.io/jsonapi_suite)
6
-
7
- [Documentation](https://jsonapi-suite.github.io/jsonapi_compliable)
8
-
9
- Supported Rails versions: >= 4.1
10
-
11
- ### Upgrading to 0.11.x
12
-
13
- Due to a backwards-incompatibility introduced in the underlying
14
- [jsonapi-rb](http://jsonapi-rb.org) gem, specifying custom serializers
15
- now works slightly differently.
16
-
17
- Before:
18
-
19
- ```ruby
20
- # app/serializers/serializable_post.rb
21
-
22
- has_many :comments, class: SerializableSpecialComment
23
- ```
24
-
25
- and/or
26
-
27
- ```ruby
28
- render_jsonapi(post, class: SerializableSpecialPost)
29
- ```
30
-
31
- This is now all handled at the controller level:
32
-
33
- ```ruby
34
- render_jsonapi(post, class: {
35
- Post: SerializableSpecialPost,
36
- Comment: SerializableSpecialComment
37
- })
38
- ```
39
-
40
- ### Upgrading to 0.10
41
-
42
- `sideload_whitelist` has been moved from the resource to the controller:
43
-
44
- ```diff
45
- class PostsController < ApplicationController
46
- jsonapi resource: PostResource do
47
- - sideload_whitelist({ index: [:foo] })
48
- - end
49
- + sideload_whitelist({ index: [:foo] })
50
- end
51
-
52
- # NEW
53
- ```
54
-
55
- ### Running tests
56
-
57
- We support Rails >= 4.1. To do so, we use the [appraisal](https://github.com/thoughtbot/appraisal) gem. So, run:
58
-
59
- ```bash
60
- $ bin/appraisal rails-4 bin/rspec
61
- $ bin/appraisal rails-5 bin/rspec
62
- ```
63
-
64
- Or run tests for all versions:
65
-
66
- ```bash
67
- $ bin/appraisal bin/rspec
68
- ```
69
-
70
- ### Generating the Documentation
71
-
72
- ```bash
73
- $ yard doc
74
- $ yard server
75
- ```
5
+ A stylish alternative to GraphQL.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "graphiti-rb"
4
+ require "graphiti"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'graphiti/cli'
4
+
5
+ Graphiti::CLI.start(ARGV)
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  # Pinning this version until backwards-incompatibility is addressed
21
21
  spec.add_dependency 'jsonapi-serializable', '~> 0.3.0'
22
22
  spec.add_dependency 'dry-types', '~> 0.13'
23
- spec.add_dependency 'jsonapi_errorable', '~> 0.9'
23
+ spec.add_dependency 'graphiti_errors', '~> 1.0.alpha.2'
24
24
  spec.add_dependency 'concurrent-ruby', '~> 1.0'
25
25
 
26
26
  spec.add_development_dependency "activerecord", ['>= 4.1', '< 6']
@@ -30,5 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "sqlite3"
31
31
  spec.add_development_dependency "database_cleaner"
32
32
  spec.add_development_dependency "activemodel", ['>= 4.1', '< 6']
33
- spec.add_development_dependency "jsonapi_spec_helpers", '>= 1.0.alpha.1'
33
+ spec.add_development_dependency "graphiti_spec_helpers", '>= 1.0.alpha.1'
34
34
  end
@@ -1,4 +1,4 @@
1
- module Graphiti
1
+ module Jsonapi
2
2
  class ResourceGenerator < ::Rails::Generators::NamedBase
3
3
  source_root File.expand_path('../templates', __FILE__)
4
4
 
@@ -2,13 +2,11 @@
2
2
  # ApplicationResource is similar to ApplicationRecord - a base class that
3
3
  # holds configuration/methods for subclasses.
4
4
  # All Resources should inherit from ApplicationResource.
5
- # Resource documentation: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html
6
5
  <%- end -%>
7
6
  class ApplicationResource < Graphiti::Resource
8
7
  <%- unless omit_comments? -%>
9
8
  # Use the ActiveRecord Adapter for all subclasses.
10
9
  # Subclasses can still override this default.
11
- # More on adapters: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html
12
10
  <%- end -%>
13
11
  self.abstract_class = true
14
12
  self.adapter = Graphiti::Adapters::ActiveRecord::Base.new
@@ -7,7 +7,7 @@ require 'active_support/concern'
7
7
  require 'active_support/time'
8
8
 
9
9
  require 'dry-types'
10
- require 'jsonapi_errorable'
10
+ require 'graphiti_errors'
11
11
 
12
12
  require 'jsonapi/serializable'
13
13
 
@@ -17,8 +17,11 @@ require "graphiti/configuration"
17
17
  require "graphiti/context"
18
18
  require "graphiti/errors"
19
19
  require "graphiti/types"
20
+ require "graphiti/schema"
21
+ require "graphiti/schema_diff"
20
22
  require "graphiti/adapters/abstract"
21
23
  require "graphiti/resource/sideloading"
24
+ require "graphiti/resource/links"
22
25
  require "graphiti/resource/configuration"
23
26
  require "graphiti/resource/dsl"
24
27
  require "graphiti/resource/interface"
@@ -56,6 +59,9 @@ require "graphiti/util/sideload"
56
59
  require "graphiti/util/hooks"
57
60
  require "graphiti/util/attribute_check"
58
61
  require "graphiti/util/serializer_attributes"
62
+ require "graphiti/util/serializer_relationships"
63
+ require "graphiti/util/class"
64
+ require "graphiti/util/link"
59
65
 
60
66
  require 'graphiti/adapters/null'
61
67
 
@@ -116,6 +122,16 @@ module Graphiti
116
122
  def self.configure
117
123
  yield config
118
124
  end
125
+
126
+ def self.resources
127
+ @resources ||= []
128
+ end
129
+
130
+ def self.check!
131
+ resources.each do |resource|
132
+ resource.sideloads.values.each(&:check!)
133
+ end
134
+ end
119
135
  end
120
136
 
121
137
  require "graphiti/runner"
@@ -76,6 +76,30 @@ module Graphiti
76
76
  # @see Adapters::ActiveRecordSideloading
77
77
  # @see Adapters::Null
78
78
  class Abstract
79
+ def default_operators
80
+ {
81
+ string: [
82
+ :eq,
83
+ :not_eq,
84
+ :eql,
85
+ :not_eql,
86
+ :prefix,
87
+ :not_prefix,
88
+ :suffix,
89
+ :not_suffix,
90
+ :match,
91
+ :not_match
92
+ ],
93
+ integer_id: numerical_operators,
94
+ integer: numerical_operators,
95
+ big_decimal: numerical_operators,
96
+ float: numerical_operators,
97
+ boolean: [:eq],
98
+ date: numerical_operators,
99
+ datetime: numerical_operators
100
+ }
101
+ end
102
+
79
103
  def filter_string_eq(scope, attribute, value)
80
104
  raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_eq)
81
105
  end
@@ -378,6 +402,10 @@ module Graphiti
378
402
  scope
379
403
  end
380
404
 
405
+ def belongs_to_many_filter(sideload, scope, value)
406
+ raise 'You must implement #belongs_to_many_filter in an adapter subclass'
407
+ end
408
+
381
409
  def associate_all(parent, children, association_name, association_type)
382
410
  if activerecord_associate?(parent, children[0], association_name)
383
411
  activerecord_adapter.associate_all parent,
@@ -500,6 +528,10 @@ module Graphiti
500
528
 
501
529
  private
502
530
 
531
+ def numerical_operators
532
+ [:eq, :not_eq, :gt, :gte, :lt, :lte]
533
+ end
534
+
503
535
  def activerecord_adapter
504
536
  @activerecord_adapter ||=
505
537
  ::Graphiti::Adapters::ActiveRecord::Base.new
@@ -189,6 +189,14 @@ module Graphiti
189
189
  }
190
190
  end
191
191
 
192
+ def belongs_to_many_filter(sideload, scope, value)
193
+ scope
194
+ .includes(sideload.through_relationship_name)
195
+ .where(sideload.through_table_name => {
196
+ sideload.true_foreign_key => value
197
+ })
198
+ end
199
+
192
200
  def associate_all(parent, children, association_name, association_type)
193
201
  association = parent.association(association_name)
194
202
  association.loaded!
@@ -2,20 +2,13 @@ module Graphiti
2
2
  module Adapters
3
3
  module ActiveRecord
4
4
  class ManyToManySideload < Sideload::ManyToMany
5
- def default_base_scope
6
- resource_class.model.all
7
- end
8
-
9
5
  def through_table_name
10
6
  @through_table_name ||= parent_resource_class.model
11
7
  .reflections[through.to_s].klass.table_name
12
8
  end
13
9
 
14
- def scope(parent_ids)
15
- base_scope
16
- .includes(through)
17
- .where(through_table_name => { true_foreign_key => parent_ids })
18
- .distinct
10
+ def through_relationship_name
11
+ foreign_key.keys.first
19
12
  end
20
13
 
21
14
  def infer_foreign_key
@@ -0,0 +1,45 @@
1
+ require 'thor'
2
+ require 'net/http'
3
+ require 'graphiti'
4
+
5
+ Thor::Base.shell = Thor::Shell::Color
6
+
7
+ module Graphiti
8
+ class CLI < Thor
9
+ desc 'schema_check OLD_SCHEMA NEW_SCHEMA', 'Diff 2 schemas for backwards incompatibilities. Pass file path or URL. If your app relies on JSON Web Tokens, you can set GRAPHITI_TOKEN for authentication'
10
+ def schema_check(old, new)
11
+ old = schema_for(old)
12
+ new = schema_for(new)
13
+
14
+ errors = Graphiti::SchemaDiff.new(old, new).compare
15
+ if errors.any?
16
+ say(set_color("Backwards incompatibilties found!\n", :red, :bold))
17
+ errors.each { |e| say(set_color(e, :yellow)) }
18
+ exit(1)
19
+ else
20
+ say(set_color("No incompatibilities found!", :green))
21
+ exit(0)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def schema_for(input)
28
+ if input.starts_with?('http')
29
+ JSON.parse(fetch_remote_schema(input))
30
+ else
31
+ JSON.parse(File.read(input))
32
+ end
33
+ end
34
+
35
+ def fetch_remote_schema(path)
36
+ uri = URI(path)
37
+ http = Net::HTTP.new(uri.host, uri.port)
38
+ http.use_ssl = true if uri.scheme == 'https'
39
+ req = Net::HTTP::Get.new(uri)
40
+ req['Authorization'] = "Token token=\"#{ENV['GRAPHITI_TOKEN']}\""
41
+ res = http.request(req)
42
+ res.body
43
+ end
44
+ end
45
+ end
@@ -9,6 +9,9 @@ module Graphiti
9
9
  attr_accessor :concurrency
10
10
 
11
11
  attr_accessor :respond_to
12
+ attr_accessor :context_for_endpoint
13
+ attr_accessor :schema_path
14
+ attr_accessor :links_on_demand
12
15
 
13
16
  # Set defaults
14
17
  # @api private
@@ -16,6 +19,15 @@ module Graphiti
16
19
  @raise_on_missing_sideload = true
17
20
  @concurrency = false
18
21
  @respond_to = [:json, :jsonapi, :xml]
22
+ @links_on_demand = false
23
+
24
+ if defined?(::Rails)
25
+ @schema_path = "#{::Rails.root}/public/schema.json"
26
+ end
27
+ end
28
+
29
+ def schema_path
30
+ @schema_path ||= raise('No schema_path defined! Set Graphiti.config.schema_path to save your schema.')
19
31
  end
20
32
  end
21
33
  end
@@ -10,6 +10,7 @@ module Graphiti
10
10
 
11
11
  included do
12
12
  class_attribute :sideload_whitelist
13
+ self.sideload_whitelist = {}
13
14
  class << self;prepend Overrides;end
14
15
  end
15
16
  end
@@ -16,6 +16,40 @@ The adapter #{@adapter.class} does not implement method '#{@method}', which was
16
16
  end
17
17
  end
18
18
 
19
+ class InvalidLink < Base
20
+ def initialize(resource_class, sideload)
21
+ @resource_class = resource_class
22
+ @sideload = sideload
23
+ end
24
+
25
+ def message
26
+ <<-MSG
27
+ #{@resource_class.name}: Cannot link to sideload #{@sideload.name.inspect}!
28
+
29
+ Make sure the endpoint "#{@sideload.resource.endpoint[:full_path]}" exists, or customize the endpoint for #{@sideload.resource.class.name}.
30
+
31
+ If you do not wish to generate a link, pass link: false or set self.relationship_links_by_default = false.
32
+ MSG
33
+ end
34
+ end
35
+
36
+ class Unlinkable < Base
37
+ def initialize(resource_class, sideload)
38
+ @resource_class = resource_class
39
+ @sideload = sideload
40
+ end
41
+
42
+ def message
43
+ <<-MSG
44
+ #{@resource_class.name}: Tried to link sideload #{@sideload.name.inspect}, but cannot generate links!
45
+
46
+ Graphiti.config.context_for_endpoint must be set to enable link generation:
47
+
48
+ Graphiti.config.context_for_endpoint = ->(path, action) { ... }
49
+ MSG
50
+ end
51
+ end
52
+
19
53
  class AttributeError < Base
20
54
  attr_reader :resource,
21
55
  :name,
@@ -75,6 +109,58 @@ The adapter #{@adapter.class} does not implement method '#{@method}', which was
75
109
  end
76
110
  end
77
111
 
112
+ class InvalidEndpoint < Base
113
+ def initialize(resource_class, path, action)
114
+ @resource_class = resource_class
115
+ @path = path
116
+ @action = action
117
+ end
118
+
119
+ def message
120
+ <<-MSG
121
+ #{@resource_class.name} cannot be called directly from endpoint #{@path}##{@action}!
122
+
123
+ Either set a primary endpoint for this resource:
124
+
125
+ endpoint '/my/url', [:index, :show, :create]
126
+
127
+ Or whitelist a secondary endpoint:
128
+
129
+ secondary_endoint '/my_url', [:index, :update]
130
+
131
+ The current endpoints allowed for this resource are: #{@resource_class.endpoints.inspect}
132
+ MSG
133
+ end
134
+ end
135
+
136
+ class InvalidType < Base
137
+ def initialize(key, value)
138
+ @key = key
139
+ @value = value
140
+ end
141
+
142
+ def message
143
+ "Type must be a Hash with keys #{Types::REQUIRED_KEYS.inspect}"
144
+ end
145
+ end
146
+
147
+ class ResourceEndpointConflict < Base
148
+ def initialize(path, action, resource_a, resource_b)
149
+ @path = path
150
+ @action = action
151
+ @resource_a = resource_a
152
+ @resource_b = resource_b
153
+ end
154
+
155
+ def message
156
+ <<-MSG
157
+ Both '#{@resource_a}' and '#{@resource_b}' are associated to endpoint #{@path}##{@action}!
158
+
159
+ Only one resource can be associated to a given url/verb combination.
160
+ MSG
161
+ end
162
+ end
163
+
78
164
  class PolymorphicChildNotFound < Base
79
165
  def initialize(resource_class, model)
80
166
  @resource_class = resource_class
@@ -209,15 +295,31 @@ end
209
295
  end
210
296
  end
211
297
 
298
+ class MissingSideloadFilter < Base
299
+ def initialize(resource_class, sideload, filter)
300
+ @resource_class = resource_class
301
+ @sideload = sideload
302
+ @filter = filter
303
+ end
304
+
305
+ def message
306
+ <<-MSG
307
+ #{@resource_class.name}: sideload #{@sideload.name.inspect} is associated with resource #{@sideload.resource.class.name}, but it does not have corresponding filter.
308
+
309
+ Expecting filter #{@filter.inspect} on #{@sideload.resource.class.name}.
310
+ MSG
311
+ end
312
+ end
313
+
212
314
  class ResourceNotFound < Base
213
- def initialize(resource, sideload_name)
214
- @resource = resource
315
+ def initialize(resource_class, sideload_name)
316
+ @resource_class = resource_class
215
317
  @sideload_name = sideload_name
216
318
  end
217
319
 
218
320
  def message
219
321
  <<-MSG
220
- Could not find resource class for sideload '#{@sideload_name}' on Resource '#{@resource.class.name}'!
322
+ Could not find resource class for sideload '#{@sideload_name}' on Resource '#{@resource_class.name}'!
221
323
 
222
324
  If this follows a non-standard naming convention, use the :resource option to pass it directly:
223
325