activerecord-pull-alpha 0.1.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: a600203d416c9166308cc5685dfce526d73e4b5528a385de114eb7a63b4aef6b
4
+ data.tar.gz: c47287ef5de25b8f086bbe04449c1167757130b2648fb3311a02d1f2bb49809f
5
+ SHA512:
6
+ metadata.gz: 4764f99abb758043709052752442c3e66743d833dcb9039c4bb34c609ee20504e578155db033bb31a600c9b4032463c649a6a5c34e8d6d59c7a251d8a292acc7
7
+ data.tar.gz: 1537a8dde666ecd2e9cba6c0039b5e3fed877ee09c1033900411232191abb2c6884622548f9605e8b6423e87bf7a839c46dc338ba8103d31f9c41d0db2850d9e
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ tags
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in activerecord-pull.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Delon Newman
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,76 @@
1
+ # ActiveRecord::Pull::Alpha
2
+
3
+ A simple query interface for pulling deeply nested data from records.
4
+
5
+ ## Synopsis
6
+
7
+ ```ruby
8
+ class Survey < ActiveRecord::Base
9
+ has_many :questions
10
+ has_many :responses
11
+ end
12
+
13
+ class Question < ActiveRecord::Base
14
+ has_many :answers
15
+ end
16
+
17
+ class Answer < ActiveRecord::Base
18
+ belongs_to :reply
19
+ belongs_to :question
20
+ end
21
+
22
+ class Response < ActiveRecord::Base
23
+ belongs_to :survey
24
+ has_many :answers
25
+ end
26
+
27
+ Survey.find(34).pull(:name, questions: [:text, answers: [:created_at]])
28
+ Response.where('created_at < ?', Date.new(2018, 7, 5)).pull({ survey: :name }, { answers: :value })
29
+ ```
30
+
31
+ ## Why?
32
+
33
+ Modeling [trees][tree] and [ontologies][ontology] are and excercise in relational database design when using a SQL database.
34
+ Unfortunalty, when dealing with deeply nested and overlapping data (e.g. medical records, social graphs)
35
+ trees and ontologies won't do. We need to model graphs, a much more sparsely structured data model, but
36
+ this leads to very complex table structures and very complex SQL queries.
37
+
38
+ This pull syntax is inspired by the pull syntax that is used in [Datomic][datomic-pull], a database that is designed to model
39
+ graph-like structures. This gem plugs that syntax into ActiveRecord. As a bonus it makes querying trees and ontologies simple too!
40
+
41
+ ## Installation
42
+
43
+ Add this line to your application's Gemfile:
44
+
45
+ ```ruby
46
+ gem 'activerecord-pull-alpha'
47
+ ```
48
+
49
+ And then execute:
50
+
51
+ $ bundle
52
+
53
+ Or install it yourself as:
54
+
55
+ $ gem install activerecord-pull-alpha
56
+
57
+ ## See Also
58
+
59
+ - [Ancestry](https://github.com/stefankroes/ancestry)
60
+ - [Graphiti](https://www.graphiti.dev/)
61
+ - [Datomic][datomic]
62
+ - [GraphQL](https://graphql.org/)
63
+ - [Entity Attribute Value Model][eav-model]
64
+ - [RDF][rdf]
65
+
66
+ ## License
67
+
68
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
69
+
70
+ [datomic]: https://www.datomic.com/
71
+ [datomic-pull]: https://docs.datomic.com/on-prem/pull.html
72
+ [eav-model]: https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model
73
+ [rdf]: https://en.wikipedia.org/wiki/Resource_Description_Framework
74
+ [tree]: https://en.wikipedia.org/wiki/Tree_(data_structure)
75
+ [graph]: https://en.wikipedia.org/wiki/Graph_(abstract_data_type)
76
+ [ontology]: https://en.wikipedia.org/wiki/Ontology_(information_science)
@@ -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
@@ -0,0 +1,40 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "activerecord/pull/alpha/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "activerecord-pull-alpha"
7
+ spec.version = Activerecord::Pull::Alpha::VERSION
8
+ spec.authors = ["Delon Newman"]
9
+ spec.email = ["contact@delonnewman.name"]
10
+
11
+ spec.summary = %q{A simple query interface for pulling deeply nested data from records.}
12
+ spec.homepage = "https://github.com/delonnewman/activerecord-pull"
13
+ spec.license = "MIT"
14
+
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
19
+
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["source_code_uri"] = spec.homepage
22
+ spec.metadata["changelog_uri"] = "#{spec.homepage}#changelog"
23
+ else
24
+ raise "RubyGems 2.0 or newer is required to protect against " \
25
+ "public gem pushes."
26
+ end
27
+
28
+ # Specify which files should be added to the gem when it is released.
29
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
30
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
31
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
32
+ end
33
+ spec.bindir = "exe"
34
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ spec.add_development_dependency "bundler", "~> 1.17"
38
+ spec.add_development_dependency "rake", "~> 10.0"
39
+ spec.add_development_dependency "rspec", "~> 3.0"
40
+ end
@@ -0,0 +1,16 @@
1
+ require "activerecord/pull/alpha/core"
2
+ require "activerecord/pull/alpha/version"
3
+
4
+ module ActiveRecord
5
+ class Base
6
+ def pull(*pattern)
7
+ ActiveRecord::Pull::Alpha::Core.pull(self, pattern)
8
+ end
9
+ end
10
+
11
+ class Relation
12
+ def pull(*pattern)
13
+ ActiveRecord::Pull::Alpha::Core.pull_many(self, pattern)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,101 @@
1
+ module ActiveRecord
2
+ module Pull
3
+ module Alpha
4
+ module Core
5
+ class << self
6
+ def pull_many(records, q)
7
+ records.map { |r| pull(r, q) }
8
+ end
9
+
10
+ def pull(record, q)
11
+ case q
12
+ when :*, '*'
13
+ pull_all(record)
14
+ when Symbol, String
15
+ pull_symbol(record, q)
16
+ when Hash
17
+ pull_hash(record, q)
18
+ when Array
19
+ q.map { |pat| pull(record, pat) }.reduce(&:merge)
20
+ else
21
+ raise "Invalid pull syntax: #{q.inspect}"
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def resolved_value(record, attr)
28
+ if !(val = record[attr]).nil?
29
+ val
30
+ elsif val.nil? and !(reflection = record.class.reflect_on_association(attr)).nil?
31
+ model = reflection.class_name.constantize
32
+ p model
33
+ case reflection
34
+ when ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasAndBelongsToManyReflection
35
+ model.where(reflection.foreign_key => record[record.class.primary_key])
36
+ when ActiveRecord::Reflection::BelongsToReflection
37
+ model.find_by(model.primary_key => record[reflection.foreign_key])
38
+ else
39
+ raise "Don't know how to resolve value for reflection: #{reflection.inspect}"
40
+ end
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ def ref_type?(value)
47
+ value.is_a?(ActiveRecord::Relation) || value.is_a?(ActiveRecord::Base)
48
+ end
49
+
50
+ def pull_symbol(record, attr)
51
+ val = resolved_value(record, attr)
52
+ if ref_type?(val) && val.is_a?(Enumerable)
53
+ { attr => val.map { |v| pull_all(v) } }
54
+ elsif ref_type?(val)
55
+ { attr => pull_all(val) }
56
+ else
57
+ { attr => val }
58
+ end
59
+ end
60
+
61
+ def pull_hash(record, hash)
62
+ hash.reduce({}) do |h, (attr, pat)|
63
+ val = resolved_value(record, attr)
64
+ if val.nil?
65
+ h
66
+ elsif ref_type?(val) && val.is_a?(Enumerable)
67
+ h.merge!(attr => val.map { |v| pull(v, pat) })
68
+ elsif ref_type?(val)
69
+ h.merge!(attr => pull(val, pat))
70
+ else
71
+ h.merge!(attr => val)
72
+ end
73
+ end
74
+ end
75
+
76
+ # TODO: this should pull all associations
77
+ def pull_all(record)
78
+ names = record.class.attribute_names
79
+ nested = record.class.nested_attributes_options
80
+ attrs = pull(record, names)
81
+
82
+ if nested.empty?
83
+ attrs
84
+ else
85
+ nested.keys.reduce(attrs) do |h, assoc|
86
+ assoc_val = record.send(assoc)
87
+ if assoc_val.nil? || assoc_val.is_a?(Enumerable) && assoc_val.empty?
88
+ h
89
+ elsif assoc_val.is_a?(Enumerable)
90
+ h.merge!(assoc => assoc_val.map { |rec| pull_all(rec) }.reject(&:nil?))
91
+ else
92
+ h.merge!(assoc => pull_all(assoc_val))
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,7 @@
1
+ module Activerecord
2
+ module Pull
3
+ module Alpha
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-pull-alpha
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Delon Newman
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - contact@delonnewman.name
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - activerecord-pull-alpha.gemspec
69
+ - lib/activerecord/pull/alpha.rb
70
+ - lib/activerecord/pull/alpha/core.rb
71
+ - lib/activerecord/pull/alpha/version.rb
72
+ homepage: https://github.com/delonnewman/activerecord-pull
73
+ licenses:
74
+ - MIT
75
+ metadata:
76
+ allowed_push_host: https://rubygems.org
77
+ homepage_uri: https://github.com/delonnewman/activerecord-pull
78
+ source_code_uri: https://github.com/delonnewman/activerecord-pull
79
+ changelog_uri: https://github.com/delonnewman/activerecord-pull#changelog
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.0.6
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: A simple query interface for pulling deeply nested data from records.
99
+ test_files: []