sequel-through 0.3.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f97ae5f870b6431b6a0d8186421ad8422ce24b58de83205b377e0c854d832578
4
+ data.tar.gz: 7eba0d88aa13b1908dbc2a39e48b2c2b43879a623ca51d7f933519a008bf12e4
5
+ SHA512:
6
+ metadata.gz: 52aa46a33e6162b18d0df2b5e3d401010fc32ac0984d776bb25db4ce3d7dc5672b94ec8a789c91dafa199018160e7a6a76a6804557a7b886fc88614b412f159f
7
+ data.tar.gz: cd274aa38361bda1aba76e5e779d647262efbeaefcfb9015b8fde65c6b27d846348ae9bd64647a27e09687657a2b1127e138db0c5149ada8744ce17fc4865241
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
10
+ /gemfiles/*.lock
11
+ *.gem
@@ -0,0 +1,9 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6
7
+ - 2.5
8
+ - 2.4
9
+ before_install: gem install bundler
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/kenaniah/sequel-through" }
4
+
5
+ # Specify your gem's dependencies in sequel-through.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Kenaniah Cerny
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.
@@ -0,0 +1,41 @@
1
+ # Sequel::Through
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/sequel-through.svg)](https://badge.fury.io/rb/sequel-through)
4
+ [![Build Status](https://secure.travis-ci.org/kenaniah/sequel-through.svg)](https://travis-ci.org/kenaniah/sequel-through)
5
+ [![Inline docs](https://inch-ci.org/github/kenaniah/sequel-through.svg?branch=master)](https://inch-ci.org/github/kenaniah/sequel-through)
6
+
7
+ This gem extends [sequel](https://github.com/jeremyevans/sequel)'s associations to provide the ability to create associations that flow through other associations, similar to how `:through` works in [ActiveRecord](https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association).
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'sequel-through'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install sequel-through
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kenaniah/sequel-through.
38
+
39
+ ## License
40
+
41
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "sequel/through"
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(__FILE__)
@@ -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
@@ -0,0 +1,65 @@
1
+ module Sequel
2
+ module Plugins
3
+ module CyclicalAssociationSolver
4
+
5
+ # Ensure through associations are loaded
6
+ def self.apply mod
7
+ mod.plugin(:through_associations)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ @@_resolving = false
13
+ @@_resolver_stack = []
14
+
15
+ # Solves any remaining cyclical associations
16
+ def solve_cyclical_associations!
17
+
18
+ # Keep trying to solve as long as the stack length is reduced each time
19
+ length = nil
20
+ while length != @@_resolver_stack.count do
21
+
22
+ length = @@_resolver_stack.count
23
+ stack = @@_resolver_stack
24
+
25
+ # Attempt to solve remaining cyclical associations
26
+ @@_resolver_stack = []
27
+ stack.each do |klass, assoc_type, name, opts, block|
28
+ klass.send(assoc_type, name, **opts, &block)
29
+ end
30
+
31
+ end
32
+
33
+ # Output errors for any unsolved associations
34
+ @@_resolving = true
35
+ @@_resolver_stack.each do |klass, assoc_type, name, opts, block|
36
+ klass.send(assoc_type, name, **opts, &block)
37
+ end
38
+ @@_resolving = false
39
+
40
+ end
41
+
42
+ def associate_through(type, name, opts, &block)
43
+ begin
44
+ result = super
45
+ rescue \
46
+ Sequel::Plugins::ThroughAssociations::MissingAssociation, \
47
+ Sequel::Plugins::ThroughAssociations::NoAssociationPath \
48
+ => e
49
+
50
+ # Re-raise if we were resolving
51
+ raise e if @@_resolving
52
+
53
+ # Otherwise, attempt to resolve later
54
+ unless result
55
+ @@_resolver_stack.push([self, type, name, opts, block])
56
+ return
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,198 @@
1
+ module Sequel
2
+ module Plugins
3
+ module ThroughAssociations
4
+
5
+ ASSOCIATION_THROUGH_TYPES = {
6
+ :one_to_many => :one_through_many,
7
+ :many_to_one => :one_through_many,
8
+ :many_to_many => :many_through_many
9
+ # one_to_one
10
+ # many_to_pg_array
11
+ # pg_array_to_many
12
+ }
13
+
14
+ class MissingAssociation < Sequel::Error; end
15
+ class NoAssociationPath < Sequel::Error; end
16
+
17
+ # Ensure associations are loaded
18
+ def self.apply mod
19
+ mod.plugin :many_through_many
20
+ end
21
+
22
+ module ClassMethods
23
+
24
+ def associate type, name, opts = OPTS, &block
25
+
26
+ # Handle associations that are based on others
27
+ if opts[:through] && !type.to_s.include?("_through_")
28
+ return associate_through type, name, opts, &block
29
+ end
30
+
31
+ super
32
+
33
+ end
34
+
35
+ # Associates a related model with the current model using another association
36
+ # as the intermediary.
37
+ def associate_through type, name, opts, &block
38
+ raise Error, "#{type} does not support through associations" unless assoc_type = Sequel.synchronize{ASSOCIATION_THROUGH_TYPES[type]}
39
+
40
+ result = find_association_path **opts, name: name, models: self, from_through: true
41
+
42
+ # Remove the last table if it matches the destination table
43
+ dest_model = result[:models].pop
44
+ result[:tables].pop if result[:tables].last == dest_model.table_name
45
+
46
+ # Build the association path
47
+ path = []
48
+ left_key = result[:keys].shift
49
+ result[:tables].each do |table|
50
+ path.push [table, result[:keys].shift, result[:keys].shift]
51
+ end
52
+
53
+ # Create the association
54
+ if assoc_type.to_s.ends_with? "_through_many"
55
+ # *_through_many has a path argument
56
+ self.send(assoc_type,
57
+ name,
58
+ path,
59
+ left_primary_key: left_key,
60
+ right_primary_key: result[:keys].shift,
61
+ class: dest_model,
62
+ **opts,
63
+ originally_through: opts[:through],
64
+ &block
65
+ )
66
+ else
67
+ # *_through_one does not have a path argument
68
+ self.send(assoc_type,
69
+ name,
70
+ left_primary_key: left_key,
71
+ right_primary_key: result[:keys].shift,
72
+ class: dest_model,
73
+ **opts,
74
+ originally_through: opts[:through],
75
+ &block
76
+ )
77
+ end
78
+ end
79
+
80
+ # Recurses through associations until a path to the destination is completed
81
+ def find_association_path opts
82
+
83
+ # Initialize arguments
84
+ [:tables, :keys, :through, :models, :assocs].each do |k|
85
+ opts[k] ||= []
86
+ opts[k] = [opts[k]] unless Array === opts[k]
87
+ opts[k] = opts[k].dup
88
+ end
89
+
90
+ # Find the linked association
91
+ assoc = \
92
+ opts[:models].last.association_reflection(opts[:through].last.to_s.pluralize.to_sym) \
93
+ || opts[:models].last.association_reflection(opts[:through].last.to_s.singularize.to_sym)
94
+
95
+ # Short circuit if association does not exist
96
+ unless assoc
97
+
98
+ # Determine if finished or if the last relation is missing
99
+ if opts[:from_through]
100
+
101
+ m = opts[:models].pop
102
+ t = opts[:through].pop
103
+ path = [m]
104
+
105
+ opts[:models].zip(opts[:through]).each do |model, through|
106
+ path.push "#{model}.#{through}"
107
+ end
108
+
109
+ raise MissingAssociation, "#{m} is missing through association :#{t} from #{path.join " -> "}"
110
+
111
+ else
112
+
113
+ if opts[:assocs].last[:name].to_s.singularize != (opts[:using] || opts[:name]).to_s.singularize
114
+ text = "#{opts[:models].first}.#{opts[:name]} could not be resolved through path #{opts[:models].zip(opts[:through]).map{|model, through| "#{model}.#{through}"}.join " -> "}"
115
+ raise MissingAssociation, text
116
+ end
117
+
118
+ return opts
119
+
120
+ end
121
+
122
+ end
123
+
124
+ # Store the association
125
+ opts[:assocs].push assoc
126
+
127
+ # Handle *_through_many associations
128
+ if assoc[:type].to_s.ends_with? "_through_many"
129
+
130
+ opts[:through].push assoc[:originally_through]
131
+ opts[:from_through] = true
132
+
133
+ # Search through the existing model first, falling back to the associated model
134
+ search = [
135
+ opts[:models].last,
136
+ assoc[:class] || assoc[:class_name].constantize
137
+ ]
138
+ return begin
139
+ model = search.shift
140
+ raise NoAssociationPath, opts unless model
141
+ self.find_association_path **opts, models: opts[:models] + [model]
142
+ rescue MissingAssociation
143
+ # Try the next model in the search path
144
+ retry
145
+ end
146
+
147
+ end
148
+
149
+ # Move to the new model
150
+ opts[:models].push assoc[:class] || assoc[:class_name].constantize
151
+
152
+ # Read the through association if present
153
+ if assoc[:through]
154
+ opts[:through].push assoc[:using] || assoc[:through]
155
+ opts[:from_through] = true
156
+ opts[:using] = nil
157
+ return self.find_association_path **opts
158
+ end
159
+
160
+ # Otherwise, add the new table to the stack
161
+ if opts[:from_through] && opts[:models].last.respond_to?(:cti_tables)
162
+ opts[:tables].push opts[:models].last.cti_tables.first
163
+ else
164
+ opts[:tables].push opts[:models].last.table_name
165
+ end
166
+
167
+ # Left side
168
+ case assoc[:type]
169
+ when :one_to_many, :one_to_one # 1:_
170
+ opts[:keys].push assoc.primary_key
171
+ when :many_to_one # n:_
172
+ opts[:keys].push assoc[:key]
173
+ else
174
+ raise
175
+ end
176
+
177
+ # Right side
178
+ case assoc[:type]
179
+ when :many_to_one, :one_to_one # _:1
180
+ opts[:keys].push assoc.primary_key
181
+ when :one_to_many # _:n
182
+ opts[:keys].push assoc[:key]
183
+ else
184
+ raise
185
+ end
186
+
187
+ # Check for a source association
188
+ opts[:through].push opts[:using] || opts[:name]
189
+ opts[:from_through] = false
190
+ return self.find_association_path **opts
191
+
192
+ end
193
+
194
+ end
195
+
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,3 @@
1
+ require "sequel"
2
+ require "sequel/plugins/through_associations"
3
+ require "sequel/plugins/cyclical_association_solver"
@@ -0,0 +1,37 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "sequel-through"
6
+ spec.version = "0.3.0"
7
+ spec.authors = ["Kenaniah Cerny"]
8
+ spec.email = ["kenaniah@gmail.com"]
9
+
10
+ spec.summary = "Adds support for :through associations to sequel"
11
+ spec.description = spec.summary
12
+ spec.homepage = "https://github.com/kenaniah/sequel-through"
13
+ spec.license = "MIT"
14
+
15
+ spec.required_ruby_version = ">= 2.4"
16
+
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/kenaniah/sequel-through"
20
+ spec.metadata["changelog_uri"] = "https://github.com/kenaniah/sequel-through/blob/master/CHANGELOG.md"
21
+ end
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "sequel", "~> 5"
33
+
34
+ spec.add_development_dependency "bundler"
35
+ spec.add_development_dependency "rake"
36
+ spec.add_development_dependency "minitest"
37
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-through
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Kenaniah Cerny
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-03-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sequel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Adds support for :through associations to sequel
70
+ email:
71
+ - kenaniah@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - lib/sequel/plugins/cyclical_association_solver.rb
85
+ - lib/sequel/plugins/through_associations.rb
86
+ - lib/sequel/through.rb
87
+ - sequel-through.gemspec
88
+ homepage: https://github.com/kenaniah/sequel-through
89
+ licenses:
90
+ - MIT
91
+ metadata:
92
+ homepage_uri: https://github.com/kenaniah/sequel-through
93
+ source_code_uri: https://github.com/kenaniah/sequel-through
94
+ changelog_uri: https://github.com/kenaniah/sequel-through/blob/master/CHANGELOG.md
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '2.4'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubygems_version: 3.0.1
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Adds support for :through associations to sequel
114
+ test_files: []