graphql-activerecord 0.6.7 → 0.7.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea206475115f125b4737f85251ee149550462dc0
4
- data.tar.gz: e5c43b72e56875f407ec79d54e08678f765deadd
3
+ metadata.gz: ba393c0528ebe3d08f24276c21093661c300c273
4
+ data.tar.gz: 06f269998f7a64044385355e1903c35d1b47d6cf
5
5
  SHA512:
6
- metadata.gz: 710667348e3cbc1287f5b65b1d999d3f4b303e675bd851360dd5169e87be220e105b0ee6aa4bd0aaf1602d70712f76c64a87e21e00a68b9fc46248c5a1b88563
7
- data.tar.gz: dc098d499193ad53caa7fcd80d2042b5daf64ffa62a4b8dee3a04e9b9f4bd89ef7ec0f874dc93953b0e2b243db39f32a2cf1f578515023948b0e253fe9a068bd
6
+ metadata.gz: de38713b1668f9b52f2c6e8d4541ccb98a1b395bc15da96e2525da0c71b7d5385c0c434c6b3f2432592ebcf79410a6e758bbc246d21dcb2cdbdfe2e1e2e17b5b
7
+ data.tar.gz: 5d4efe47c646569527493e8c59f39cf1e064209d321b344ed974781a030eeb6a6de4f78705eb75cc54b4efafbc9b4ddec96ef3ed71f806236390ba01e5f640ea
data/.bundle/config ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_BIN: bin
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ graphql-activerecord-*.gem
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ /spec/examples.txt
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at theorygeek@gmail.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in graphql-activerecord.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 theorygeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # GraphQL::Models
2
+
3
+ TODO: Write this. I promise, one day, I really will write some stuff here to describe this gem.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'graphql-activerecord'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install graphql-activerecord
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Development
26
+
27
+ TODO: Write development instructions here
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/graphql-activerecord. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
32
+
33
+
34
+ ## License
35
+
36
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/_guard-core ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application '_guard-core' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("guard", "_guard-core")
data/bin/bundler ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'bundler' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("bundler", "bundler")
data/bin/coderay ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'coderay' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("coderay", "coderay")
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "graphql/activerecord"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/guard ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'guard' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("guard", "guard")
data/bin/htmldiff ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'htmldiff' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("diff-lcs", "htmldiff")
data/bin/ldiff ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'ldiff' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("diff-lcs", "ldiff")
data/bin/listen ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'listen' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("listen", "listen")
data/bin/pry ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'pry' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("pry", "pry")
data/bin/rake ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rake' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rake", "rake")
data/bin/rspec ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rspec' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/thor ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'thor' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("thor", "thor")
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'graphql/models/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "graphql-activerecord"
8
+ spec.version = GraphQL::Models::VERSION
9
+ spec.authors = ["Ryan Foster"]
10
+ spec.email = ["theorygeek@gmail.com"]
11
+
12
+ spec.summary = "ActiveRecord helpers for GraphQL + Relay"
13
+ spec.description = "Build Relay-compatible GraphQL schemas based on ActiveRecord models"
14
+ spec.homepage = "http://github.com/goco-inc/graphql-activerecord"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_runtime_dependency "activesupport", "~> 4.2"
23
+ spec.add_runtime_dependency "activerecord", "~> 4.2"
24
+ spec.add_runtime_dependency "graphql", "~> 0.13.0"
25
+ spec.add_runtime_dependency "graphql-batch", "~> 0.2.1"
26
+ spec.add_runtime_dependency 'graphql-relay', '~> 0.9.5'
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.11"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec", "~> 3.0"
31
+ spec.add_development_dependency "pry"
32
+ end
@@ -1,4 +1,8 @@
1
+ require 'active_support'
2
+ require 'active_record'
1
3
  require 'graphql'
4
+ require 'graphql/batch'
5
+ require 'graphql/relay'
2
6
 
3
7
  require 'graphql/models/monkey_patches/graphql_query_context'
4
8
  require 'graphql/models/monkey_patches/graphql_schema_middleware_chain'
@@ -6,9 +10,12 @@ require 'graphql/models/active_record_extension'
6
10
  require 'graphql/models/middleware'
7
11
 
8
12
  # Helpers
13
+ require 'graphql/models/helpers'
14
+ require 'graphql/models/hash_combiner'
9
15
  require 'graphql/models/definer'
10
16
  require 'graphql/models/association_load_request'
11
- require 'graphql/models/loader'
17
+ require 'graphql/models/attribute_loader'
18
+ require 'graphql/models/relation_loader'
12
19
 
13
20
  # Order matters...
14
21
  require 'graphql/models/promise_relation_connection'
@@ -20,7 +20,7 @@ module GraphQL
20
20
  def_delegators :@hash, :[]=, :include?, :keys
21
21
  end
22
22
 
23
- extend ActiveSupport::Concern
23
+ extend ::ActiveSupport::Concern
24
24
  class_methods do
25
25
  def graphql_enum_types
26
26
  @_graphql_enum_types ||= EnumTypeHash.new
@@ -64,4 +64,4 @@ module GraphQL
64
64
  end
65
65
  end
66
66
 
67
- ActiveRecord::Base.send(:include, GraphQL::Models::ActiveRecordExtension)
67
+ ::ActiveRecord::Base.send(:include, GraphQL::Models::ActiveRecordExtension)
@@ -7,68 +7,25 @@ module GraphQL
7
7
  @base_model = base_model
8
8
  @association = base_model.association(association_name)
9
9
  @context = context
10
- end
11
-
12
- ####################################################################
13
- # Public members that all load requests should implement
14
- ####################################################################
15
10
 
16
- def load_type
17
- case reflection.macro
18
- when :belongs_to
19
- :id
20
- else
21
- :relation
11
+ if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
12
+ fail ArgumentError, "You cannot batch-load a has_many :through association. Instead, load each association individually."
22
13
  end
23
14
  end
24
15
 
25
- def load_target
26
- case reflection.macro
27
- when :belongs_to
28
- base_model.send(reflection.foreign_key)
29
- when :has_many
30
- base_model.send(association.reflection.name)
31
- else
32
- # has_one, need to construct our own relation, because accessing the relation will load the model
33
- condition = { reflection.foreign_key => base_model.id }
34
-
35
- if reflection.options.include?(:as)
36
- condition[reflection.type] = base_model.class.name
37
- end
38
-
39
- target_class.where(condition)
40
- end
41
- end
42
-
43
- # If the value should be an array, make sure it's an array. If it should be a single value, make sure it's single.
44
- # Passed in result could be a single model or an array of models.
45
- def ensure_cardinality(result)
46
- case reflection.macro
47
- when :has_many
48
- Array.wrap(result)
49
- else
50
- result.is_a?(Array) ? result[0] : result
51
- end
52
- end
53
-
54
- # When the request is fulfilled, this method is called so that it can do whatever caching, etc. is needed
55
- def fulfilled(result)
56
- association.loaded!
57
-
58
- if reflection.macro == :has_many
59
- association.target.slice!(0..-1)
60
- association.target.concat(result)
61
- result.each do |m|
62
- association.set_inverse_instance(m)
63
- end
64
- else
65
- association.target = result
66
- association.set_inverse_instance(result) if result
67
- end
16
+ def request
17
+ AttributeLoader::Request.new(
18
+ association.scope.where_values_hash,
19
+ Helpers.orders_to_sql(association.scope.orders)
20
+ )
68
21
  end
69
22
 
70
23
  def load
71
- loader.load(self)
24
+ loader.load(request).then do |result|
25
+ result = result.first unless reflection.macro == :has_many
26
+ Helpers.load_association_with(association, result)
27
+ result
28
+ end
72
29
  end
73
30
 
74
31
  #################################################################
@@ -86,7 +43,7 @@ module GraphQL
86
43
  private
87
44
 
88
45
  def loader
89
- @loader ||= Loader.for(target_class)
46
+ @loader ||= AttributeLoader.for(target_class)
90
47
  end
91
48
 
92
49
  def reflection
@@ -0,0 +1,96 @@
1
+ module GraphQL::Models
2
+ # Simplified loader that can take a hash of attributes to match, combine them into a single query, and then fulfill
3
+ # then individually. It can also ask the database to order them correctly.
4
+ class AttributeLoader < GraphQL::Batch::Loader
5
+ attr_reader :model_class
6
+
7
+ def initialize(model_class)
8
+ @model_class = model_class
9
+ end
10
+
11
+ class Request
12
+ attr_accessor :attributes, :sorting
13
+ def initialize(attributes, sorting)
14
+ @attributes = attributes
15
+ @sorting = sorting
16
+ end
17
+ end
18
+
19
+ WHERE_STRIP = /\AWHERE /
20
+
21
+ # @param requests An AttributeLoader::Request (or a simple hash) that describes the models to be loaded
22
+ def perform(requests)
23
+ # Combine the conditions together, into the minimal set of conditions needed
24
+ conditions = HashCombiner.combine(requests.map { |r| r.is_a?(Request) ? r.attributes : r })
25
+
26
+ # Get the distinct list of sorting conditions that we need to ask for, also
27
+ sorters = requests.map { |r| r.is_a?(Request) ? r.sorting : nil }.compact.reject(&:blank?).uniq
28
+
29
+ # Start constructing the query that we'll execute to get the results
30
+ table = model_class.arel_table
31
+
32
+ # Start building the query, add in the where conditions
33
+ conditions.map! { |cond| hash_to_condition(table, cond) }
34
+ query = table.where(conditions.reduce { |memo, cond| memo.or(cond) })
35
+
36
+ # Convert the list of sorters into RANK() selections that we'll add to the selection
37
+ order_selections = sorters.each_with_index.map { |s, idx| order_selection(s, idx) }
38
+
39
+ # Add the projections to the query
40
+ query = order_selections.reduce(query.project(:*)) { |memo, selection| memo.project(selection) }
41
+
42
+ # Get the result set
43
+ results = model_class.find_by_sql(query.to_sql)
44
+
45
+ # De-multiplex the result set and fulfill the requests
46
+ requests.each do |request|
47
+ # Get the rows that match this request
48
+ response = match_results(results, request)
49
+
50
+ if response.size > 1 && request.is_a?(Request) && request.sorting
51
+ idx = sorters.index(request.sorting)
52
+ sort_by = "rank_#{idx}"
53
+ response = response.sort_by { |row| row[sort_by] }
54
+ end
55
+
56
+ fulfill(request, response)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def order_selection(sorter, idx)
63
+ arel = model_class.unscoped.order(sorter).arel
64
+ order_sql = Helpers.orders_to_sql(arel.orders)
65
+ %{ DENSE_RANK() OVER(ORDER BY #{order_sql}) AS rank_#{idx} }
66
+ end
67
+
68
+ # Converts a hash into arel conditions
69
+ def hash_to_condition(table, hash)
70
+ conditions = hash.map do |attr, value|
71
+ if value.is_a?(Array) && value.size > 1
72
+ table[attr].in(value)
73
+ elsif value.is_a?(Array)
74
+ table[attr].eq(value[0])
75
+ else
76
+ table[attr].eq(value)
77
+ end
78
+ end
79
+
80
+ conditions.reduce { |memo, cond| memo.and(cond) }
81
+ end
82
+
83
+ def match_results(results, request)
84
+ # Find all of the items in the results that match the request
85
+ attributes = request.is_a?(Request) ? request.attributes : request
86
+
87
+ results.select do |row|
88
+ attributes.all? { |key, value| is_match(row.send(key), value) }
89
+ end
90
+ end
91
+
92
+ def is_match(row_value, compare_value)
93
+ compare_value.is_a?(Array) ? compare_value.include?(row_value) : compare_value == row_value
94
+ end
95
+ end
96
+ end
@@ -24,8 +24,30 @@ module GraphQL
24
24
  association = current_model.association(path[0])
25
25
  end
26
26
 
27
- request = AssociationLoadRequest.new(current_model, path[0], context)
28
- request.load.then do |next_model|
27
+ # If this is a has_many :through, then we need to load the two associations in sequence
28
+ # (eg: Company has_many :health_lines, through: :open_enrollments => load open enrollments, then health lines)
29
+
30
+ promise = if association.reflection.options[:through]
31
+ # First step, load the :through association (ie, the :open_enrollments)
32
+ through = association.reflection.options[:through]
33
+ load_and_traverse(current_model, [through], context).then do |intermediate_models|
34
+
35
+ # Now, for each of the intermediate models (OpenEnrollment), load the source association (:health_line)
36
+ sources = intermediate_models.map do |im|
37
+ load_and_traverse(im, [association.reflection.source_reflection_name], context)
38
+ end
39
+
40
+ # Once all of the eventual models are loaded, flatten the results
41
+ Promise.all(sources).then do |result|
42
+ result = result.flatten
43
+ Helpers.load_association_with(association, result)
44
+ end
45
+ end
46
+ else
47
+ AssociationLoadRequest.new(current_model, path[0], context).load
48
+ end
49
+
50
+ promise.then do |next_model|
29
51
  next next_model if next_model.blank?
30
52
  cache_model(context, next_model)
31
53
 
@@ -0,0 +1,40 @@
1
+ module GraphQL::Models::HashCombiner
2
+ class << self
3
+ # Takes a set of hashes that represent conditions, and combines them into the smallest number of hashes
4
+ def combine(hashes)
5
+ # Group the hashes by keys. If they are querying different columns, they can't be combined
6
+ by_keys = hashes.group_by { |h| h.keys }
7
+ by_keys.map { |keys, values| combine_core(values, keys) }.flatten
8
+ end
9
+
10
+ private
11
+
12
+ def combine_core(hashes, keys)
13
+ return [] if keys.blank?
14
+
15
+ # If there's only one key in each of the hashes, then combine that into a single hash with an array
16
+ if keys.length == 1
17
+ values = hashes.map { |h| h[keys[0]] }
18
+ return [{ keys[0] => values.uniq }]
19
+ end
20
+
21
+ # Get the most commonly occuring value in the hash, and remove it from the keys.
22
+ # Return one hash for each unique value.
23
+ min_key = keys.min_by { |k| hashes.map { |h| h[k] }.uniq.count }
24
+ inner_keys = keys.dup
25
+ inner_keys.delete(min_key)
26
+
27
+ # Group the hashes based on the value that they have for that key
28
+ grouped = hashes.group_by { |h| h[min_key] }
29
+
30
+ grouped = grouped.map do |key_value, inner_hashes|
31
+ combined = combine_core(inner_hashes, inner_keys)
32
+ merge_with = { min_key => key_value }
33
+
34
+ combined.map { |reduced_hash| merge_with.merge(reduced_hash) }
35
+ end
36
+
37
+ grouped.flatten
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ module GraphQL::Models
2
+ module Helpers
3
+ def self.orders_to_sql(orders)
4
+ expressions = orders.map do |expr|
5
+ case expr
6
+ when Arel::Nodes::SqlLiteral
7
+ expr.to_s
8
+ else
9
+ expr.to_sql
10
+ end
11
+ end
12
+
13
+ expressions.join(', ')
14
+ end
15
+
16
+ def self.load_association_with(association, result)
17
+ reflection = association.reflection
18
+ association.loaded!
19
+
20
+ if reflection.macro == :has_many
21
+ association.target.slice!(0..-1)
22
+ association.target.concat(result)
23
+ result.each do |m|
24
+ association.set_inverse_instance(m)
25
+ end
26
+ else
27
+ association.target = result
28
+ association.set_inverse_instance(result) if result
29
+ end
30
+ end
31
+ end
32
+ end
@@ -42,9 +42,9 @@ module GraphQL
42
42
  end
43
43
 
44
44
  private
45
-
45
+
46
46
  def loader
47
- @loader ||= Loader.for(target_class)
47
+ @loader ||= RelationLoader.for(target_class)
48
48
  end
49
49
 
50
50
  end
@@ -1,6 +1,6 @@
1
1
  module GraphQL
2
2
  module Models
3
- class Loader < GraphQL::Batch::Loader
3
+ class RelationLoader < GraphQL::Batch::Loader
4
4
  attr_reader :model_class
5
5
 
6
6
  def initialize(model_class)
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Models
3
- VERSION = "0.6.7"
3
+ VERSION = "0.7.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,43 +1,141 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.7
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Foster
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2016-07-29 00:00:00.000000000 Z
11
+ date: 2016-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: graphql
15
43
  requirement: !ruby/object:Gem::Requirement
16
44
  requirements:
17
- - - ">="
45
+ - - "~>"
18
46
  - !ruby/object:Gem::Version
19
- version: 0.12.0
47
+ version: 0.13.0
20
48
  type: :runtime
21
49
  prerelease: false
22
50
  version_requirements: !ruby/object:Gem::Requirement
23
51
  requirements:
24
- - - ">="
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.13.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: graphql-batch
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
25
60
  - !ruby/object:Gem::Version
26
- version: 0.12.0
61
+ version: 0.2.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.2.1
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: graphql-relay
29
71
  requirement: !ruby/object:Gem::Requirement
30
72
  requirements:
31
- - - ">="
73
+ - - "~>"
32
74
  - !ruby/object:Gem::Version
33
- version: 0.8.0
75
+ version: 0.9.5
34
76
  type: :runtime
35
77
  prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.9.5
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.11'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.11'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '10.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '10.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
36
134
  version_requirements: !ruby/object:Gem::Requirement
37
135
  requirements:
38
136
  - - ">="
39
137
  - !ruby/object:Gem::Version
40
- version: 0.8.0
138
+ version: '0'
41
139
  description: Build Relay-compatible GraphQL schemas based on ActiveRecord models
42
140
  email:
43
141
  - theorygeek@gmail.com
@@ -45,15 +143,40 @@ executables: []
45
143
  extensions: []
46
144
  extra_rdoc_files: []
47
145
  files:
146
+ - ".bundle/config"
147
+ - ".gitignore"
148
+ - ".rspec"
149
+ - ".travis.yml"
150
+ - CODE_OF_CONDUCT.md
151
+ - Gemfile
152
+ - LICENSE.txt
153
+ - README.md
154
+ - Rakefile
155
+ - bin/_guard-core
156
+ - bin/bundler
157
+ - bin/coderay
158
+ - bin/console
159
+ - bin/guard
160
+ - bin/htmldiff
161
+ - bin/ldiff
162
+ - bin/listen
163
+ - bin/pry
164
+ - bin/rake
165
+ - bin/rspec
166
+ - bin/setup
167
+ - bin/thor
168
+ - graphql-activerecord.gemspec
48
169
  - lib/graphql/activerecord.rb
49
170
  - lib/graphql/models/active_record_extension.rb
50
171
  - lib/graphql/models/association_load_request.rb
172
+ - lib/graphql/models/attribute_loader.rb
51
173
  - lib/graphql/models/backed_by_model.rb
52
174
  - lib/graphql/models/definer.rb
53
175
  - lib/graphql/models/definition_helpers.rb
54
176
  - lib/graphql/models/definition_helpers/associations.rb
55
177
  - lib/graphql/models/definition_helpers/attributes.rb
56
- - lib/graphql/models/loader.rb
178
+ - lib/graphql/models/hash_combiner.rb
179
+ - lib/graphql/models/helpers.rb
57
180
  - lib/graphql/models/middleware.rb
58
181
  - lib/graphql/models/monkey_patches/graphql_query_context.rb
59
182
  - lib/graphql/models/monkey_patches/graphql_schema_middleware_chain.rb
@@ -68,6 +191,7 @@ files:
68
191
  - lib/graphql/models/promise_relation_connection.rb
69
192
  - lib/graphql/models/proxy_block.rb
70
193
  - lib/graphql/models/relation_load_request.rb
194
+ - lib/graphql/models/relation_loader.rb
71
195
  - lib/graphql/models/scalar_types.rb
72
196
  - lib/graphql/models/version.rb
73
197
  homepage: http://github.com/goco-inc/graphql-activerecord
@@ -82,7 +206,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
206
  requirements:
83
207
  - - ">="
84
208
  - !ruby/object:Gem::Version
85
- version: 2.1.0
209
+ version: '0'
86
210
  required_rubygems_version: !ruby/object:Gem::Requirement
87
211
  requirements:
88
212
  - - ">="