sequel-through 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []