association_selection 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6fb7c7317ba8cf62a11d85ff6774f3ae5880069cfe57982d1b2acc2b091bce28
4
+ data.tar.gz: aba4e0ec50f24443a17f4a2a06a78722e29da8c2dad11f840a0297ff73184177
5
+ SHA512:
6
+ metadata.gz: 47c5217acea4a685025595c0a8181b3f877a0c842ae06e342132390cb2a3e3131c2dcb86ba52ba7c4a929dbf9b35d6250d0cfd2ee11931da840a31731b98f587
7
+ data.tar.gz: ea5017c30fcd1faee3a680f390f01b755a066d74a4875ddf0409a6646434d68de6e797d99c8eeec1f8324993e38c1ba95e89bf28665d89f9f9b0fe93409a16a5
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # AssociationSelection
2
+
3
+ `AssociationSelection` is a gem that allows you to specify which fields to select when preloading associations. It offers finer control over database queries, optimizing memory usage and enhancing performance by fetching only the necessary fields.
4
+
5
+ Features
6
+ ---
7
+
8
+ - Select specific fields for associated records when using `includes` or `preload`.
9
+ - Seamlessly integrates with ActiveRecord.
10
+ - Reduces memory overhead and improves query efficiency.
11
+
12
+ Installation
13
+ ---
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'association_selection'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ ```ruby
24
+ bundle install
25
+ ```
26
+
27
+ Or install it yourself as:
28
+
29
+ ```
30
+ gem install association_selection
31
+ ```
32
+
33
+ Usage
34
+ ===
35
+
36
+
37
+ Quick Start
38
+ ---
39
+
40
+ 1. Include the gem in your Rails application.
41
+ 2. Use the `assoc_select` method to specify the fields to fetch for associated records.
42
+
43
+ Example
44
+ ---
45
+ #### Schema
46
+ ```ruby
47
+ ActiveRecord::Schema.define do
48
+ create_table :departments do |t|
49
+ t.string :name
50
+ end
51
+
52
+ create_table :employees do |t|
53
+ t.integer :department_id
54
+ t.string :name
55
+ t.string :email
56
+ t.integer :gender
57
+ end
58
+ end
59
+ ```
60
+
61
+ #### Models
62
+ ```ruby
63
+ class Department < ActiveRecord::Base
64
+ has_many :employees
65
+ end
66
+
67
+ class Employee < ActiveRecord::Base
68
+ belongs_to :department
69
+ end
70
+ ```
71
+
72
+
73
+ #### Query
74
+ ```ruby
75
+ # Preload employees with only selected fields
76
+ departments = Department.assoc_select(:employees, :id, :name).includes(:employees)
77
+
78
+ departments.each do |department|
79
+ department.employees.each do |employee|
80
+ puts employee.name # Only `id` and `name` are fetched from the database.
81
+ end
82
+ end
83
+ ```
84
+
85
+ How It Works
86
+ ---
87
+ The assoc_select method modifies the query to include only the specified fields for the given association. It works seamlessly with ActiveRecord's includes, preload, and eager_load methods.
88
+
89
+ Compatibility
90
+ ---
91
+ This gem is tested with ActiveRecord versions 5, 6, 7, and newer
92
+
93
+ Bug reports & suggested improvements
94
+ ---
95
+ If you have found a bug or want to suggest an improvement, please use our issue tracked at:
96
+
97
+ https://github.com/vuong-khai-ha/association_selection
98
+
99
+ If you want to contribute, fork the project, code your improvements and make a pull request on Github. Please don't forget to add tests.
100
+ If your contribution is fixing a bug it would be better if you could submit a failing test, illustrating the issue.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appraisal'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ t.pattern = 'test/**/*_test.rb'
9
+ end
10
+
11
+ task default: :appraisal if !ENV['APPRAISAL_INITIALIZED'] && !ENV['TRAVIS']
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociationSelection
4
+ module Querying
5
+ attr_accessor :assoc_select_fields
6
+
7
+ def assoc_select(**fields)
8
+ spawn.tap { |s| s.assoc_select_fields = fields }
9
+ end
10
+ end
11
+
12
+ module Delegations
13
+ delegate :assoc_select, to: :all
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociationSelection
4
+ module V5
5
+ module Preloader
6
+ attr_accessor :relation_klass, :assoc_select_fields
7
+
8
+ NULL_RELATION = Struct.new(:values, :where_clause, :joins_values)
9
+ .new({}, ::ActiveRecord::Relation::WhereClause.empty, [])
10
+
11
+ def preload(records, associations, preload_scope = nil)
12
+ records = Array.wrap(records).compact.uniq
13
+ associations = Array.wrap(associations)
14
+ preload_scope ||= nil
15
+
16
+ return [] if records.empty?
17
+
18
+ any_specified_fields = relation_klass.present? && assoc_select_fields.is_a?(Hash)
19
+ indexed_reflections = relation_klass.reflections.map { |k, r| [k, r.class_name] }.to_h if any_specified_fields
20
+ associations.flat_map do |association|
21
+ if any_specified_fields && indexed_reflections.key?(association.to_s)
22
+ preloaders_on(
23
+ association,
24
+ records,
25
+ indexed_reflections[association.to_s].constantize.select(assoc_select_fields[association])
26
+ )
27
+ else
28
+ preloaders_on association, records, preload_scope
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociationSelection
4
+ module V5
5
+ module Relation
6
+ def build_preloader
7
+ ActiveRecord::Associations::Preloader.new.tap do |new_preloader|
8
+ new_preloader.assoc_select_fields = assoc_select_fields
9
+ new_preloader.relation_klass = model
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociationSelection
4
+ module V6
5
+ module Relation
6
+ def preload_associations(records) # :nodoc:
7
+ preload = preload_values
8
+ preload += includes_values unless eager_loading?
9
+ preloader = nil
10
+
11
+ any_specified_fields = assoc_select_fields.is_a?(Hash)
12
+ indexed_reflections = model.reflections.map { |k, r| [k, r.class_name] }.to_h if any_specified_fields
13
+ preload.each do |associations|
14
+ preloader ||= build_preloader
15
+ if any_specified_fields && indexed_reflections.key?(associations.to_s)
16
+ preloader.preload(
17
+ records,
18
+ associations,
19
+ indexed_reflections[associations.to_s].constantize.select(assoc_select_fields[associations])
20
+ )
21
+ else
22
+ preloader.preload records, associations
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociationSelection
4
+ module V7
5
+ module Relation
6
+ def preload_associations(records) # :nodoc:
7
+ preload = preload_values
8
+ preload += includes_values unless eager_loading?
9
+ scope = strict_loading_value ? StrictLoadingScope : nil
10
+ any_specified_fields = scope.nil? && assoc_select_fields.is_a?(Hash)
11
+ indexed_reflections = model.reflections.map { |k, r| [k, r.class_name] }.to_h if any_specified_fields
12
+ preload.each do |associations|
13
+ if any_specified_fields && indexed_reflections.key?(associations.to_s)
14
+ scope = indexed_reflections[associations.to_s].constantize.select(assoc_select_fields[associations])
15
+ end
16
+ ActiveRecord::Associations::Preloader.new(records: records, associations: associations, scope: scope).call
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociationSelection
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'association_selection/version'
4
+ require_relative 'association_selection/querying'
5
+
6
+ module AssociationSelection
7
+ def self.setup
8
+ ActiveSupport.on_load(:active_record) do |base|
9
+ base.extend AssociationSelection::Delegations
10
+ ActiveRecord::Relation.include AssociationSelection::Querying
11
+
12
+ if ActiveRecord.version >= Gem::Version.new('7.0')
13
+ require_relative 'association_selection/v7/relation'
14
+ ActiveRecord::Relation.prepend AssociationSelection::V7::Relation
15
+ elsif ActiveRecord.version >= Gem::Version.new('6.0')
16
+ require_relative 'association_selection/v6/relation'
17
+ ActiveRecord::Relation.prepend AssociationSelection::V6::Relation
18
+ else
19
+ require_relative 'association_selection/v5/relation'
20
+ require_relative 'association_selection/v5/preloader'
21
+ ActiveRecord::Associations::Preloader.prepend AssociationSelection::V5::Preloader
22
+ ActiveRecord::Relation.prepend AssociationSelection::V5::Relation
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ AssociationSelection.setup
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: association_selection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vương Khải Hà
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Provides functionality to specify fields to preload for ActiveRecord
14
+ associations.
15
+ email:
16
+ - inki.personal@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README.md
22
+ - Rakefile
23
+ - lib/association_selection.rb
24
+ - lib/association_selection/querying.rb
25
+ - lib/association_selection/v5/preloader.rb
26
+ - lib/association_selection/v5/relation.rb
27
+ - lib/association_selection/v6/relation.rb
28
+ - lib/association_selection/v7/relation.rb
29
+ - lib/association_selection/version.rb
30
+ homepage: https://github.com/vuong-khai-ha/association_selection
31
+ licenses:
32
+ - MIT
33
+ metadata:
34
+ rubygems_mfa_required: 'true'
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '2.0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.5.22
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Select fields for ActiveRecord associations
54
+ test_files: []