active_record-virtual_joins 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +101 -0
- data/Rakefile +11 -0
- data/lib/active_record/virtual_joins/version.rb +8 -0
- data/lib/active_record/virtual_joins/virtual_table.rb +128 -0
- data/lib/active_record/virtual_joins.rb +49 -0
- data/lib/tapioca/dsl/compilers/active_record_virtual_joins.rb +69 -0
- metadata +65 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 59d1d1b0ae151d9115e013a4915f52555327779289019f736dbb551a187ad6f1
|
|
4
|
+
data.tar.gz: c1b133959d7ff43e37fe86e3728625bfaafdd939cfc0053d6aff7e935f57f751
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cb99c980f83b67ec6ad6332946407dd59dc2c0795b3f2856fedbb0e3ef7ccec9fd687ad0e438240d3073877fe963429a001ede9111998dd91e5ac9387af70275
|
|
7
|
+
data.tar.gz: de20998770a734153f1ffa2149990b9fe7540ebcc55a87c30670210d446bc367f3e2e9ed0255dd24880b6e978e3c0e157dd4754788efde72cec24446bc8c9c4a
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2026 Minty Fresh
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# ActiveRecord::VirtualJoins
|
|
2
|
+
|
|
3
|
+
Joins a list of Ruby hashes to the records of an ActiveRecord relation, as a virtual table.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'active_record-virtual_joins', github: 'mintyfresh/active_record-virtual_joins'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ bundle
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then extend your models with `ActiveRecord::VirtualJoins` (typically in `ApplicationRecord`, so all models get it):
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
class ApplicationRecord < ActiveRecord::Base
|
|
23
|
+
primary_abstract_class
|
|
24
|
+
|
|
25
|
+
extend ActiveRecord::VirtualJoins
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
`virtual_joins` takes the column to join by, the list of hashes, and the types of the other columns.
|
|
32
|
+
The values are left-joined to the records by the join column, and selected onto them:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
scores = [{ id: 1, score: 10 }, { id: 2, score: 20 }]
|
|
36
|
+
|
|
37
|
+
users = User.virtual_joins(:id, scores, score: :integer)
|
|
38
|
+
# SELECT "users".*, "users_id_json"."score" FROM "users"
|
|
39
|
+
# LEFT OUTER JOIN json_to_recordset('[{"id":1,"score":10},{"id":2,"score":20}]'::json)
|
|
40
|
+
# AS "users_id_json"("score" integer, "id" bigint)
|
|
41
|
+
# ON "users"."id" = "users_id_json"."id"
|
|
42
|
+
|
|
43
|
+
users.find(1).score # => 10
|
|
44
|
+
users.find(3).score # => nil
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Records without matching values are kept, with null values.
|
|
48
|
+
Keys of the hashes that aren't columns of the virtual table are ignored, and missing keys are null.
|
|
49
|
+
|
|
50
|
+
The values can be used in the rest of the query, by the name of the virtual table
|
|
51
|
+
(which defaults to the table and join column, eg. `users_id_json`):
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
User.virtual_joins(:id, scores, score: :integer).where('users_id_json.score > 15').order('users_id_json.score')
|
|
55
|
+
|
|
56
|
+
User.virtual_joins(:id, scores, join_table_name: :scores, score: :integer).order('scores.score')
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
It can be called on any relation (including associations), and keeps the relation's conditions and projection:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
user.posts.where(published: true).virtual_joins(:slug, views, views: :integer)
|
|
63
|
+
User.select(:id).virtual_joins(:id, scores, score: :integer) # selects only users.id and the score
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Types
|
|
67
|
+
|
|
68
|
+
Types can be given as ActiveRecord types (eg. `:integer`, `:string`), which are converted to the database's types,
|
|
69
|
+
or as SQL types (eg. `'decimal(10, 2)'`), which are used as they are.
|
|
70
|
+
The type of the join column defaults to the type of the model's column, but can be given like any other column.
|
|
71
|
+
|
|
72
|
+
Note that `:decimal` uses the database's default precision, which is a scale of 0 on MySQL
|
|
73
|
+
(so use an SQL type like `'decimal(10, 2)'` for fractional values).
|
|
74
|
+
|
|
75
|
+
### Databases
|
|
76
|
+
|
|
77
|
+
PostgreSQL (`json_to_recordset`), MySQL (`JSON_TABLE`), and SQLite (`json_each`) are supported.
|
|
78
|
+
|
|
79
|
+
SQLite has no column types to convert the values to, so values are read as their JSON types
|
|
80
|
+
(eg. strings rather than times, and `1`/`0` rather than booleans).
|
|
81
|
+
|
|
82
|
+
### Sorbet
|
|
83
|
+
|
|
84
|
+
A Tapioca DSL compiler is included, which adds `virtual_joins` to the relations generated for models
|
|
85
|
+
extended by `ActiveRecord::VirtualJoins`. It's loaded automatically by `bin/tapioca dsl`.
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
The specs run against a dummy Rails application in `spec/dummy`, using SQLite by default:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
$ bin/rspec
|
|
93
|
+
$ bin/rubocop
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
PostgreSQL and MySQL are selected with the `DATABASE_ADAPTER` and `DATABASE_URL` environment variables
|
|
97
|
+
(see `.github/workflows/ci.yml`).
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module VirtualJoins
|
|
6
|
+
# A table of values decoded from JSON by the database, with the SQL to join it to a model's table:
|
|
7
|
+
# * PostgreSQL - `json_to_recordset`
|
|
8
|
+
# * MySQL - `JSON_TABLE`
|
|
9
|
+
# * SQLite - `json_each` (which doesn't convert the values to the types of the columns)
|
|
10
|
+
class VirtualTable
|
|
11
|
+
#: String
|
|
12
|
+
attr_reader :name
|
|
13
|
+
|
|
14
|
+
#: (
|
|
15
|
+
#| singleton(ActiveRecord::Base) model,
|
|
16
|
+
#| Symbol | String name,
|
|
17
|
+
#| Symbol | String join_column_name,
|
|
18
|
+
#| Hash[Symbol | String, Symbol | String] column_types
|
|
19
|
+
#| ) -> void
|
|
20
|
+
def initialize(model, name, join_column_name, column_types)
|
|
21
|
+
@model = model
|
|
22
|
+
@name = name.to_s
|
|
23
|
+
@join_column_name = join_column_name.to_s
|
|
24
|
+
@column_types = column_types.transform_keys(&:to_s)
|
|
25
|
+
@column_types[@join_column_name] ||= join_column_type
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Joins the virtual table to the model's table, by the join column.
|
|
29
|
+
#
|
|
30
|
+
#: (Enumerable[Hash[Symbol | String, untyped]] values) -> String
|
|
31
|
+
def join_sql(values)
|
|
32
|
+
@model.with_connection do |connection|
|
|
33
|
+
source = source_sql(connection)
|
|
34
|
+
condition = "#{@model.quoted_table_name}.#{connection.quote_column_name(@join_column_name)} = " \
|
|
35
|
+
"#{qualified_column_name(connection, @join_column_name)}"
|
|
36
|
+
|
|
37
|
+
@model.sanitize_sql_array(
|
|
38
|
+
["LEFT OUTER JOIN #{source} ON #{condition}", ActiveSupport::JSON.encode(values.to_a)]
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The columns of the virtual table, other than the join column (which is the same as the model's).
|
|
44
|
+
#
|
|
45
|
+
#: -> Array[Arel::Nodes::SqlLiteral]
|
|
46
|
+
def select_values
|
|
47
|
+
@model.with_connection do |connection|
|
|
48
|
+
@column_types.keys.excluding(@join_column_name).map do |column_name|
|
|
49
|
+
Arel.sql(qualified_column_name(connection, column_name))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
#: -> String
|
|
57
|
+
def join_column_type
|
|
58
|
+
column = @model.columns_hash[@join_column_name]
|
|
59
|
+
return column.sql_type if column
|
|
60
|
+
|
|
61
|
+
raise ArgumentError, "The type of the join column `#{@join_column_name}` must be given, " \
|
|
62
|
+
"since it isn't a column of `#{@model.name}`."
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The table, which decodes the values from a JSON array of objects (given as a bind parameter).
|
|
66
|
+
#
|
|
67
|
+
#: (ActiveRecord::ConnectionAdapters::AbstractAdapter connection) -> String
|
|
68
|
+
def source_sql(connection)
|
|
69
|
+
case connection.adapter_name
|
|
70
|
+
when /postgres/i then postgresql_source_sql(connection)
|
|
71
|
+
when /mysql|trilogy/i then mysql_source_sql(connection)
|
|
72
|
+
when /sqlite/i then sqlite_source_sql(connection)
|
|
73
|
+
else
|
|
74
|
+
raise NotImplementedError, "virtual_joins isn't supported by the #{connection.adapter_name} adapter."
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
#: (ActiveRecord::ConnectionAdapters::AbstractAdapter connection) -> String
|
|
79
|
+
def postgresql_source_sql(connection)
|
|
80
|
+
columns = @column_types.map do |column_name, type|
|
|
81
|
+
"#{connection.quote_column_name(column_name)} #{sql_type(connection, type)}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
"json_to_recordset(?::json) AS #{connection.quote_table_name(name)}(#{columns.join(', ')})"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#: (ActiveRecord::ConnectionAdapters::AbstractAdapter connection) -> String
|
|
88
|
+
def mysql_source_sql(connection)
|
|
89
|
+
columns = @column_types.map do |column_name, type|
|
|
90
|
+
"#{connection.quote_column_name(column_name)} #{sql_type(connection, type)} " \
|
|
91
|
+
"PATH #{connection.quote(json_path(column_name))}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
"JSON_TABLE(?, '$[*]' COLUMNS(#{columns.join(', ')})) AS #{connection.quote_table_name(name)}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#: (ActiveRecord::ConnectionAdapters::AbstractAdapter connection) -> String
|
|
98
|
+
def sqlite_source_sql(connection)
|
|
99
|
+
columns = @column_types.keys.map do |column_name|
|
|
100
|
+
"json_extract(value, #{connection.quote(json_path(column_name))}) " \
|
|
101
|
+
"AS #{connection.quote_column_name(column_name)}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
"(SELECT #{columns.join(', ')} FROM json_each(?)) AS #{connection.quote_table_name(name)}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# ActiveRecord types are converted to the database's types (eg. `:string` to `character varying`),
|
|
108
|
+
# while SQL types are used as they are.
|
|
109
|
+
#
|
|
110
|
+
#: (ActiveRecord::ConnectionAdapters::AbstractAdapter connection, Symbol | String type) -> String
|
|
111
|
+
def sql_type(connection, type)
|
|
112
|
+
type.is_a?(Symbol) ? connection.type_to_sql(type) : type
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A path to a key of a JSON object (which is quoted, so that keys aren't parsed as paths).
|
|
116
|
+
#
|
|
117
|
+
#: (String column_name) -> String
|
|
118
|
+
def json_path(column_name)
|
|
119
|
+
"$.#{column_name.to_json}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
#: (ActiveRecord::ConnectionAdapters::AbstractAdapter connection, String column_name) -> String
|
|
123
|
+
def qualified_column_name(connection, column_name)
|
|
124
|
+
"#{connection.quote_table_name(name)}.#{connection.quote_column_name(column_name)}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'active_record'
|
|
5
|
+
require 'active_record/virtual_joins/version'
|
|
6
|
+
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
# Joins lists of hashes to the records of a model and its subclasses, as virtual tables:
|
|
9
|
+
#
|
|
10
|
+
# class ApplicationRecord < ActiveRecord::Base
|
|
11
|
+
# extend ActiveRecord::VirtualJoins
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# users = User.virtual_joins(:id, [{ id: 1, score: 10 }, { id: 2, score: 20 }], score: :integer)
|
|
15
|
+
# users.find(1).score # => 10
|
|
16
|
+
module VirtualJoins
|
|
17
|
+
autoload :VirtualTable, 'active_record/virtual_joins/virtual_table'
|
|
18
|
+
|
|
19
|
+
# Left-joins a list of hashes as a virtual table, matched to records by the join column,
|
|
20
|
+
# and selects the other columns of the virtual table onto the records.
|
|
21
|
+
#
|
|
22
|
+
# The columns of the virtual table are given with their types, as ActiveRecord types (eg. `:integer`)
|
|
23
|
+
# or SQL types (eg. `'numeric(10, 2)'`). The type of the join column defaults to that of the model's column.
|
|
24
|
+
# Keys of the hashes that aren't columns of the virtual table are ignored, and missing keys are null.
|
|
25
|
+
#
|
|
26
|
+
# The records' own columns are selected, unless the relation already selects other columns.
|
|
27
|
+
#
|
|
28
|
+
#: (
|
|
29
|
+
#| Symbol | String join_column_name,
|
|
30
|
+
#| Enumerable[Hash[Symbol | String, untyped]] join_values,
|
|
31
|
+
#| ?join_table_name: Symbol | String,
|
|
32
|
+
#| **Symbol | String join_value_types
|
|
33
|
+
#| ) -> ActiveRecord::Relation
|
|
34
|
+
def virtual_joins(
|
|
35
|
+
join_column_name,
|
|
36
|
+
join_values,
|
|
37
|
+
join_table_name: "#{table_name}_#{join_column_name}_json",
|
|
38
|
+
**join_value_types
|
|
39
|
+
)
|
|
40
|
+
virtual_table = VirtualTable.new(self, join_table_name, join_column_name, join_value_types)
|
|
41
|
+
|
|
42
|
+
relation = all.joins(virtual_table.join_sql(join_values))
|
|
43
|
+
relation = relation.select(arel_table[Arel.star]) if relation.select_values.empty?
|
|
44
|
+
|
|
45
|
+
select_values = virtual_table.select_values
|
|
46
|
+
select_values.empty? ? relation : relation.select(*select_values)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
return unless defined?(ActiveRecord::Base)
|
|
5
|
+
|
|
6
|
+
require 'tapioca/dsl/compilers/active_record_relations'
|
|
7
|
+
|
|
8
|
+
module Tapioca
|
|
9
|
+
module Dsl
|
|
10
|
+
module Compilers
|
|
11
|
+
# Adds `virtual_joins` to the relations generated by the ActiveRecordRelations compiler,
|
|
12
|
+
# for models extended by ActiveRecord::VirtualJoins.
|
|
13
|
+
# (Relations delegate it to their model, so it isn't otherwise known to be a relation method.)
|
|
14
|
+
# Compilers decorate a constant in order of their names, so this runs after ActiveRecordRelations.
|
|
15
|
+
#
|
|
16
|
+
#: [ConstantType = singleton(::ActiveRecord::Base)]
|
|
17
|
+
class ActiveRecordVirtualJoins < Compiler
|
|
18
|
+
include Helpers::ActiveRecordConstantsHelper
|
|
19
|
+
|
|
20
|
+
# @override
|
|
21
|
+
#: -> void
|
|
22
|
+
def decorate
|
|
23
|
+
return unless compiler_enabled?('ActiveRecordRelations')
|
|
24
|
+
|
|
25
|
+
root.create_path(constant) do |model|
|
|
26
|
+
model.create_module(RelationMethodsModuleName) do |mod|
|
|
27
|
+
create_virtual_joins_method(mod, RelationClassName)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
model.create_module(AssociationRelationMethodsModuleName) do |mod|
|
|
31
|
+
create_virtual_joins_method(mod, AssociationRelationClassName)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
# @override
|
|
38
|
+
#: -> Enumerable[Module[top]]
|
|
39
|
+
def gather_constants
|
|
40
|
+
ActiveRecordRelations.gather_constants.grep(::ActiveRecord::VirtualJoins)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
#: (RBI::Scope scope, String return_type) -> void
|
|
47
|
+
def create_virtual_joins_method(scope, return_type)
|
|
48
|
+
scope.create_method(
|
|
49
|
+
'virtual_joins',
|
|
50
|
+
parameters: [
|
|
51
|
+
create_param('join_column_name', type: 'T.any(Symbol, String)'),
|
|
52
|
+
create_param('join_values', type: 'T::Enumerable[T::Hash[T.any(Symbol, String), T.untyped]]'),
|
|
53
|
+
create_kw_opt_param('join_table_name', type: 'T.any(Symbol, String)', default: default_join_table_name),
|
|
54
|
+
create_kw_rest_param('join_value_types', type: 'T.any(Symbol, String)'),
|
|
55
|
+
],
|
|
56
|
+
return_type:
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The default depends on the join column, so it's given as the expression which computes it.
|
|
61
|
+
#
|
|
62
|
+
#: -> String
|
|
63
|
+
def default_join_table_name
|
|
64
|
+
'"#{table_name}_#{join_column_name}_json"' # rubocop:disable Lint/InterpolationCheck
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: active_record-virtual_joins
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Minty Fresh
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.1'
|
|
26
|
+
description: Joins a list of hashes to the records of an ActiveRecord relation as
|
|
27
|
+
a virtual table, selecting its values onto the records.
|
|
28
|
+
email:
|
|
29
|
+
- 7896757+mintyfresh@users.noreply.github.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- MIT-LICENSE
|
|
35
|
+
- README.md
|
|
36
|
+
- Rakefile
|
|
37
|
+
- lib/active_record/virtual_joins.rb
|
|
38
|
+
- lib/active_record/virtual_joins/version.rb
|
|
39
|
+
- lib/active_record/virtual_joins/virtual_table.rb
|
|
40
|
+
- lib/tapioca/dsl/compilers/active_record_virtual_joins.rb
|
|
41
|
+
homepage: https://github.com/mintyfresh/active_record-virtual_joins
|
|
42
|
+
licenses:
|
|
43
|
+
- MIT
|
|
44
|
+
metadata:
|
|
45
|
+
homepage_uri: https://github.com/mintyfresh/active_record-virtual_joins
|
|
46
|
+
source_code_uri: https://github.com/mintyfresh/active_record-virtual_joins
|
|
47
|
+
rubygems_mfa_required: 'true'
|
|
48
|
+
rdoc_options: []
|
|
49
|
+
require_paths:
|
|
50
|
+
- lib
|
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '3.2'
|
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
requirements: []
|
|
62
|
+
rubygems_version: 4.0.20
|
|
63
|
+
specification_version: 4
|
|
64
|
+
summary: Join lists of Ruby hashes to ActiveRecord relations
|
|
65
|
+
test_files: []
|