darthjee-active_ext 1.3.2 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fb91b628ca83024e054c1739dea5c23e3086c99c
4
- data.tar.gz: 7c95c17e1a61c92d9819bb9c4c206a4958d34d6b
2
+ SHA256:
3
+ metadata.gz: b4c421c51d5123103187852e1275c366949004f25b24b39e01e18862d3a62bb8
4
+ data.tar.gz: 76350778a8a203605014368a71cd98577f0110e5b4a047d88afe4781837c159c
5
5
  SHA512:
6
- metadata.gz: e7dad17a96b13c41e2d24185166006e1892c11ad2fb5a8bf9ccac364c8eb1f7a286a3cbbfd2adf147baaf3704d3c7fd1c574826158774b95743dcd13616e0b1f
7
- data.tar.gz: 83ad3e5604468f5ab2128d572fc4f4ffc510ee503d284bf3ba8150c179711b252502b19e8df976bf6207d24e3c1eb1a70bbe3a0a6cbd66f75e3c1b8225f07f97
6
+ metadata.gz: 3e25a672c1677961f700d52c39dfabcbe7a8f19ac538a33003a1a6f610d50d8ade5e68e4c5ea1336499d7dd134b0a2a0a09355dd513b661abb228bc49462a2d5
7
+ data.tar.gz: c8988d38003fa18fc2e67460016dced99c86c24a112b574c7ba1f49a1b4386298facb2d36a325a611058c77f574ffe71a82a1348dc9031ef442b7004205b4f4c
data/.circleci/config.yml CHANGED
@@ -1,13 +1,82 @@
1
1
  version: 2
2
+ workflows:
3
+ version: 2
4
+ test-and-build:
5
+ jobs:
6
+ - test:
7
+ filters:
8
+ tags:
9
+ only: /.*/
10
+ - checks:
11
+ filters:
12
+ tags:
13
+ only: /.*/
14
+ - build-and-release:
15
+ requires: [test, checks]
16
+ filters:
17
+ tags:
18
+ only: /\d+\.\d+\.\d+/
19
+ branches:
20
+ only:
21
+ - main
2
22
  jobs:
3
- build:
23
+ test:
4
24
  docker:
5
- - image: circleci/ruby:2.4.1
25
+ - image: darthjee/circleci_rails_gems:2.1.1
26
+ environment:
27
+ PROJECT: active_ext
6
28
  steps:
7
29
  - checkout
8
- - run: curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
9
- - run: chmod +x ./cc-test-reporter
10
- - run: ./cc-test-reporter before-build
11
- - run: bundle install
12
- - run: bundle exec rspec
13
- - run: ./cc-test-reporter after-build --exit-code $?
30
+ - run:
31
+ name: Bundle Install
32
+ command: bundle install
33
+ - run:
34
+ name: RSpec
35
+ command: bundle exec rspec
36
+ - run:
37
+ name: Upload coverage to Codacy
38
+ command: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r coverage/lcov/project.lcov
39
+ checks:
40
+ docker:
41
+ - image: darthjee/circleci_rails_gems:2.1.1
42
+ environment:
43
+ PROJECT: active_ext
44
+ steps:
45
+ - checkout
46
+ - run:
47
+ name: Bundle Install
48
+ command: bundle install
49
+ - run:
50
+ name: Rubocop
51
+ command: rubocop
52
+ - run:
53
+ name: Yardstick coverage check
54
+ command: bundle exec rake verify_measurements
55
+ - run:
56
+ name: Check version documentation
57
+ command: VERSION_PATH=lib/darthjee/active_ext/version.rb GEM_NAME=darthjee-active_ext check_readme.sh
58
+ - run:
59
+ name: Rubycritcs check
60
+ command: rubycritic.sh
61
+ - run:
62
+ name: Check unit tests
63
+ command: check_specs
64
+ build-and-release:
65
+ docker:
66
+ - image: darthjee/circleci_rails_gems:2.1.1
67
+ environment:
68
+ PROJECT: active_ext
69
+ steps:
70
+ - checkout
71
+ - run:
72
+ name: Bundle Install
73
+ command: bundle install
74
+ - run:
75
+ name: Signin
76
+ command: build_gem.sh signin
77
+ - run:
78
+ name: Build Gem
79
+ command: PROJECT=darthjee-active_ext build_gem.sh build
80
+ - run:
81
+ name: Push Gem
82
+ command: PROJECT=darthjee-active_ext build_gem.sh push
@@ -0,0 +1,137 @@
1
+ # Using `darthjee-active_ext` in This Project
2
+
3
+ This project uses the [`darthjee-active_ext`](https://github.com/darthjee/active_ext) gem,
4
+ which adds utility methods to `ActiveRecord::Relation` and `ActiveRecord::Base`.
5
+
6
+ ---
7
+
8
+ ## Installation
9
+
10
+ Add to your `Gemfile`:
11
+
12
+ ```ruby
13
+ gem 'darthjee-active_ext'
14
+ ```
15
+
16
+ Then run:
17
+
18
+ ```console
19
+ bundle install
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Methods Added
25
+
26
+ The gem adds the following methods to **`ActiveRecord::Relation`** and, by delegation,
27
+ to every **`ActiveRecord::Base`** subclass (i.e., every model):
28
+
29
+ | Method | Returns | Purpose |
30
+ |---|---|---|
31
+ | `#percentage(*filters)` | `Float` (0.0 – 1.0) | Fraction of records matching a condition within the current scope |
32
+ | `#pluck_as_json(*keys)` | `Array<Hash>` | Like `pluck`, but returns an array of hashes instead of an array of arrays |
33
+
34
+ ---
35
+
36
+ ## `#percentage`
37
+
38
+ Returns the fraction of records within the current relation that match the
39
+ given condition. The result is a `Float` between `0.0` and `1.0`.
40
+ Returns `0` (integer) when the relation is empty, to avoid division by zero.
41
+
42
+ ### Accepted filter forms
43
+
44
+ | Form | Example argument |
45
+ |---|---|
46
+ | Named scope (Symbol) | `:with_error` |
47
+ | Multiple chained scopes (Symbols) | `:active, :with_error` |
48
+ | Hash condition | `status: :error` |
49
+ | Raw SQL string | `"status = 'error'"` |
50
+
51
+ ### Examples
52
+
53
+ ```ruby
54
+ # Given a model and some data:
55
+ class Document < ActiveRecord::Base
56
+ scope :with_error, -> { where(status: :error) }
57
+ scope :with_success, -> { where(status: :success) }
58
+ scope :active, -> { where(active: true) }
59
+ end
60
+
61
+ # 3 error documents, 1 success document (4 total)
62
+ Document.percentage(:with_error) #=> 0.75
63
+ Document.percentage(status: :error) #=> 0.75
64
+ Document.percentage("status = 'error'") #=> 0.75
65
+
66
+ # Nested scope: among active documents only
67
+ Document.active.percentage(:with_error) #=> 0.5
68
+
69
+ # Multiple scope filters chained together
70
+ Document.percentage(:active, :with_error) #=> 0.25
71
+
72
+ # Empty relation → returns 0, not a float, to avoid division by zero
73
+ Document.where(id: nil).percentage(:with_error) #=> 0
74
+ ```
75
+
76
+ ### When to use `percentage`
77
+
78
+ - Displaying statistics or progress indicators (e.g., "75% of tasks completed").
79
+ - Feature-flag rollout checks across a filtered user base.
80
+ - Any situation where you need a ratio of one subset to its parent scope.
81
+
82
+ ---
83
+
84
+ ## `#pluck_as_json`
85
+
86
+ Works like `ActiveRecord`'s built-in `pluck`, but returns an **array of hashes**
87
+ instead of an array of arrays. Each hash maps column name (as a Symbol) to its
88
+ value.
89
+
90
+ When called **with no arguments**, it returns the full `as_json` representation
91
+ of every record in the relation (equivalent to `map(&:as_json)`).
92
+
93
+ ### Examples
94
+
95
+ ```ruby
96
+ # Standard pluck returns nested arrays — column order matters
97
+ Document.pluck(:id, :status)
98
+ #=> [[1, "error"], [2, "success"], [3, "success"]]
99
+
100
+ # pluck_as_json returns hashes — keys make meaning explicit
101
+ Document.pluck_as_json(:id, :status)
102
+ #=> [
103
+ # { id: 1, status: "error" },
104
+ # { id: 2, status: "success" },
105
+ # { id: 3, status: "success" }
106
+ # ]
107
+
108
+ # Works with any ActiveRecord scope chain
109
+ Document.active.pluck_as_json(:id, :status)
110
+ #=> [{ id: 3, status: "success" }]
111
+
112
+ # No arguments — returns all columns for every record as JSON hashes
113
+ Document.pluck_as_json
114
+ #=> [
115
+ # { id: 1, status: "error", active: false, created_at: ..., updated_at: ... },
116
+ # { id: 2, status: "success", active: false, created_at: ..., updated_at: ... },
117
+ # { id: 3, status: "success", active: true, created_at: ..., updated_at: ... }
118
+ # ]
119
+ ```
120
+
121
+ ### When to use `pluck_as_json`
122
+
123
+ - Building JSON API responses without loading full ActiveRecord objects.
124
+ - Feeding data into serializers or view helpers that expect hashes.
125
+ - Any scenario where column position in a plain array would be fragile or unclear.
126
+
127
+ ---
128
+
129
+ ## Notes
130
+
131
+ - Both methods are available directly on model classes (`Document.percentage(...)`)
132
+ **and** on any `ActiveRecord::Relation` (`Document.active.percentage(...)`),
133
+ because the class-level methods are delegated to `Model.all`.
134
+ - `percentage` with Symbol arguments chains named scopes; with a Hash or String it
135
+ uses `where`. Do **not** mix Symbols with Hashes/Strings in a single call.
136
+ - The gem requires `darthjee-core_ext` (pulled in automatically as a dependency),
137
+ which provides the `Array#as_hash` helper used internally by `pluck_as_json`.
@@ -0,0 +1,102 @@
1
+ # GitHub Copilot Instructions for `darthjee/active_ext`
2
+
3
+ ## Project Overview
4
+
5
+ `active_ext` is a Ruby gem that provides extension methods to be used with
6
+ **ActiveRecord** and **ActiveSupport** (Ruby on Rails components). It enriches
7
+ `ActiveRecord::Relation` and related classes with additional query helpers and
8
+ utilities, keeping the extensions idiomatic, lightweight, and Rails-friendly.
9
+
10
+ ---
11
+
12
+ ## Language
13
+
14
+ All contributions must be in **English**:
15
+
16
+ - Pull request titles and descriptions
17
+ - Code comments and inline documentation
18
+ - YARD docstrings and examples
19
+ - Commit messages
20
+ - Identifiers (class names, method names, variable names, etc.)
21
+
22
+ ---
23
+
24
+ ## Testing
25
+
26
+ - **Always add tests** for every change, bug fix, or new feature.
27
+ - Tests live under `spec/` and use **RSpec**.
28
+ - Follow the existing spec structure: place specs next to the corresponding
29
+ `lib/` file (e.g., `lib/active_record/my_extension.rb` →
30
+ `spec/lib/active_record/my_extension_spec.rb`).
31
+ - If a source file genuinely cannot have a spec (e.g., it only re-exports
32
+ constants or requires other files), list it in **`check_specs.yml`** so it
33
+ is explicitly acknowledged as exempt from the coverage requirement.
34
+
35
+ ---
36
+
37
+ ## Documentation
38
+
39
+ - Provide documentation compatible with **YARD**.
40
+ - Every **public** class, module, and method must include a YARD docstring.
41
+ - Include `@param`, `@return`, and `@example` tags where appropriate.
42
+ - Example:
43
+
44
+ ```ruby
45
+ # Returns the percentage of records matching +scope+ within the current
46
+ # relation.
47
+ #
48
+ # @param scope [Symbol, Hash, String] the scope or condition to evaluate
49
+ # @return [Float] percentage as a decimal between 0.0 and 1.0
50
+ #
51
+ # @example Using a named scope
52
+ # Document.percentage(:with_error) #=> 0.75
53
+ #
54
+ # @example Using a hash condition
55
+ # Document.percentage(status: :error) #=> 0.75
56
+ #
57
+ # @example Within a nested scope
58
+ # Document.active.percentage(:with_error) #=> 0.5
59
+ def percentage(scope)
60
+ # ...
61
+ end
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Design & Quality
67
+
68
+ Follow the principles championed by **Sandi Metz** (as described in
69
+ *99 Bottles of OOP* and *Practical Object-Oriented Design*):
70
+
71
+ - **Small classes and methods** — each should have a single, well-defined
72
+ responsibility.
73
+ - **Limit method length** — prefer short, readable methods over long ones.
74
+ - **Prefer composition over inheritance** when extending behavior.
75
+ - **Avoid Law of Demeter violations** — an object should only talk to its
76
+ immediate collaborators; avoid long method-chain calls like
77
+ `a.b.c.do_something`.
78
+ - **Favor clear, explicit boundaries** — modules and classes should expose
79
+ minimal public APIs and hide implementation details.
80
+
81
+ ---
82
+
83
+ ## Rails & ActiveRecord Compatibility
84
+
85
+ - Extensions must remain **Rails-friendly**: follow Rails conventions and
86
+ naming patterns.
87
+ - New methods added to `ActiveRecord::Relation` or `ActiveSupport` classes
88
+ should integrate naturally with existing Rails query interfaces (scopes,
89
+ chainability, etc.).
90
+ - Avoid monkey-patching core Ruby classes unless absolutely necessary; prefer
91
+ extending the appropriate Rails base classes or using `ActiveSupport::Concern`.
92
+ - Ensure compatibility with the Ruby and Rails versions declared in
93
+ `active_ext.gemspec` and `Gemfile`.
94
+
95
+ ---
96
+
97
+ ## Pull Request Guidelines
98
+
99
+ - Keep PRs focused and small — one logical change per PR.
100
+ - Include a clear description of *what* changed and *why*.
101
+ - Reference any related issues.
102
+ - Ensure all existing and new specs pass before requesting review.
data/.gitignore CHANGED
@@ -1,3 +1,8 @@
1
1
  coverage
2
2
  Gemfile.lock
3
3
  pkg
4
+
5
+ .yardoc/
6
+ doc/
7
+ rubycritic/
8
+ measurement
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ plugins:
2
+ - rubocop-rspec
3
+ - rubocop-rake
4
+
5
+ inherit_from: .rubocop_todo.yml
6
+
7
+ AllCops:
8
+ TargetRubyVersion: 3.3
9
+ NewCops: enable
10
+
11
+ Gemspec/RequireMFA:
12
+ Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,8 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2026-03-10 23:25:07 UTC using RuboCop version 1.85.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
data/Dockerfile ADDED
@@ -0,0 +1,21 @@
1
+ FROM darthjee/scripts:0.7.0 as scripts
2
+
3
+ FROM darthjee/rails_gems:2.1.1 as base
4
+
5
+ COPY --chown=app:app ./ /home/app/app/
6
+
7
+ ######################################
8
+
9
+ FROM base as builder
10
+
11
+ COPY --chown=app:app --from=scripts /home/scripts/builder/bundle_builder.sh /usr/local/sbin/bundle_builder.sh
12
+
13
+ ENV HOME_DIR /home/app
14
+ RUN bundle_builder.sh
15
+
16
+ #######################
17
+ #FINAL IMAGE
18
+ FROM base
19
+
20
+ COPY --chown=app:app --from=builder /home/app/bundle/ /usr/local/bundle/
21
+ RUN bundle install
data/Gemfile CHANGED
@@ -1,5 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in credential_builder.gemspec
4
6
  gemspec
5
7
 
8
+ gem 'activerecord', '7.2.2.1'
9
+ gem 'bundler', '>= 2.5.13'
10
+ gem 'pry', '0.14.2'
11
+ gem 'pry-nav', '1.0.0'
12
+ gem 'rake', '13.2.1'
13
+ gem 'reek', '6.5.0'
14
+ gem 'rspec', '3.13.2'
15
+ gem 'rspec-collection_matchers'
16
+ gem 'rspec-core', '3.13.6'
17
+ gem 'rspec-expectations', '3.13.5'
18
+ gem 'rspec-mocks', '3.13.8'
19
+ gem 'rspec-rails', '8.0.3'
20
+ gem 'rspec-support', '3.13.7'
21
+ gem 'rubocop', '1.85.1'
22
+ gem 'rubocop-rake', '0.7.1'
23
+ gem 'rubocop-rspec', '3.9.0'
24
+ gem 'rubycritic', '5.0.0'
25
+ gem 'simplecov', '0.22.0'
26
+ gem 'simplecov-html', '0.13.2'
27
+ gem 'simplecov-lcov', '0.9.0'
28
+ gem 'sqlite3', '1.4.2'
29
+ gem 'yard', '0.9.38'
30
+ gem 'yardstick', '0.9.9'
data/Makefile ADDED
@@ -0,0 +1,6 @@
1
+ .PHONY: dev
2
+
3
+ PROJECT?=active_ext
4
+
5
+ dev:
6
+ docker-compose run $(PROJECT) /bin/bash
data/README.md CHANGED
@@ -1,9 +1,19 @@
1
1
  ActiveExt
2
2
  ==========
3
3
 
4
- [![Code Climate](https://codeclimate.com/github/darthjee/active_ext/badges/gpa.svg)](https://codeclimate.com/github/darthjee/active_ext)
5
- [![Test Coverage](https://codeclimate.com/github/darthjee/active_ext/badges/coverage.svg)](https://codeclimate.com/github/darthjee/active_ext/coverage)
6
- [![Issue Count](https://codeclimate.com/github/darthjee/active_ext/badges/issue_count.svg)](https://codeclimate.com/github/darthjee/active_ext)
4
+ [![CircleCI](https://dl.circleci.com/status-badge/img/gh/darthjee/active_ext/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/darthjee/active_ext/tree/main)
5
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/ba204b06239b4e1483fe1ce2fb4aced2)](https://app.codacy.com/gh/darthjee/active_ext/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
6
+ [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/ba204b06239b4e1483fe1ce2fb4aced2)](https://app.codacy.com/gh/darthjee/active_ext/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
7
+ [![Gem Version](https://badge.fury.io/rb/darthjee-active_ext.svg)](https://badge.fury.io/rb/darthjee-active_ext)
8
+
9
+ **Current Release**: [1.4.0](https://github.com/darthjee/active_ext/tree/1.4.0)
10
+
11
+ **Next release**: [1.5.0](https://github.com/darthjee/active_ext/compare/1.4.0...main)
12
+
13
+ Yard Documentation
14
+ -------------------
15
+ [https://www.rubydoc.info/gems/darthjee-active_ext/1.4.0](https://www.rubydoc.info/gems/darthjee-active_ext/1.4.0)
16
+
7
17
 
8
18
  # Usage
9
19
  This project adds some new methods to the core active_record classes
data/Rakefile CHANGED
@@ -1,7 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
5
+ require 'yardstick/rake/measurement'
6
+ require './config/yardstick'
7
+ require './config/rubycritc'
3
8
 
4
9
  RSpec::Core::RakeTask.new
5
10
 
11
+ desc 'run rspec'
6
12
  task default: :spec
13
+
14
+ desc 'run rspec'
7
15
  task test: :spec
data/active_ext.gemspec CHANGED
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'darthjee/active_ext/version'
5
6
 
@@ -11,21 +12,13 @@ Gem::Specification.new do |gem|
11
12
  gem.summary = 'Active Extensions'
12
13
  gem.homepage = 'https://github.com/darthjee/active_ext'
13
14
  gem.description = 'Extension of active support classes with usefull methods'
15
+ gem.required_ruby_version = '>= 3.3.1'
14
16
 
15
17
  gem.files = `git ls-files -z`.split("\x0")
16
18
  gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
19
  gem.require_paths = ['lib']
19
20
 
20
- gem.add_runtime_dependency 'activesupport', '>= 5.x'
21
- gem.add_runtime_dependency 'darthjee-core_ext', '>= 1.5.6'
22
-
23
- gem.add_development_dependency 'activerecord', '~> 5.x'
24
- gem.add_development_dependency 'sqlite3'
25
-
26
- gem.add_development_dependency 'bundler', '>= 1.6'
27
- gem.add_development_dependency 'rake', '>= 11.3.0'
28
- gem.add_development_dependency 'rspec', '>= 2.14'
29
- gem.add_development_dependency 'rspec-mocks', '>= 2.99.4'
30
- gem.add_development_dependency 'simplecov', '>= 0.14.1'
21
+ gem.add_dependency 'activesupport', '~> 7.2.x'
22
+ gem.add_dependency 'darthjee-core_ext', '>= 3.1.0'
23
+ gem.metadata['rubygems_mfa_required'] = 'false'
31
24
  end
@@ -0,0 +1,6 @@
1
+ ignore:
2
+ - lib/darthjee/active_ext/version.rb
3
+ - lib/active_record/base_ext.rb
4
+ - lib/active_record/relation_ext.rb
5
+ - lib/darthjee/active_ext.rb
6
+ - lib/darthjee.rb
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/rake_task'
4
+
5
+ RubyCritic::RakeTask.new do |task|
6
+ options = %w[
7
+ --path rubycritic/
8
+ --no-browser
9
+ ]
10
+ task.options = options.join(' ')
11
+ task.paths = %w[lib]
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yardstick/rake/measurement'
4
+ require 'yardstick/rake/verify'
5
+ require 'yaml'
6
+
7
+ options = YAML.load_file('config/yardstick.yml')
8
+
9
+ Yardstick::Rake::Measurement.new(:yardstick_measure, options) do |measurement|
10
+ measurement.output = 'measurement/report.txt'
11
+ end
12
+
13
+ Yardstick::Rake::Verify.new(:verify_measurements, options)
@@ -0,0 +1,39 @@
1
+ threshold: 10.0
2
+ require_exact_threshold: false
3
+ rules:
4
+ ApiTag::Presence:
5
+ enabled: true
6
+ exclude: []
7
+ ApiTag::Inclusion:
8
+ enabled: true
9
+ exclude: []
10
+ ApiTag::ProtectedMethod:
11
+ enabled: true
12
+ exclude: []
13
+ ApiTag::PrivateMethod:
14
+ enabled: true
15
+ exclude: []
16
+ ExampleTag:
17
+ enabled: true
18
+ exclude:
19
+ - ActiveRecord::Base.percentage
20
+ - ActiveRecord::Base.pluck_as_json
21
+ - ActiveRecord::Base.scopped
22
+ ReturnTag:
23
+ enabled: true
24
+ exclude:
25
+ - ActiveRecord::Base.percentage
26
+ - ActiveRecord::Base.pluck_as_json
27
+ - ActiveRecord::Base.scopped
28
+ Summary::Presence:
29
+ enabled: true
30
+ exclude: []
31
+ Summary::Length:
32
+ enabled: true
33
+ exclude: []
34
+ Summary::Delimiter:
35
+ enabled: true
36
+ exclude: []
37
+ Summary::SingleLine:
38
+ enabled: true
39
+ exclude: []
data/docker-compose.yml CHANGED
@@ -1,18 +1,23 @@
1
- version: '2'
1
+ version: '3'
2
2
  services:
3
3
  base: &base
4
- image: ruby:2.4.0
5
- working_dir: /home/app/active_ext
4
+ image: active_ext
5
+ working_dir: /home/app/app
6
6
  volumes:
7
- - .:/home/app/active_ext
8
- - active_ext_gems_2_4_0:/usr/local/bundle
7
+ - .:/home/app/app
9
8
 
10
- #################### CONTAINERS ####################
9
+ base_build:
10
+ <<: *base
11
+ build: .
12
+ command: echo done
11
13
 
12
14
  active_ext:
13
15
  <<: *base
14
16
  container_name: active_ext
15
- command: /bin/bash -c 'bundle install && bundle exec rspec'
17
+ depends_on: [base_build]
18
+ command: /bin/bash -c 'rspec'
16
19
 
17
- volumes:
18
- active_ext_gems_2_4_0:
20
+ test_all:
21
+ <<: *base
22
+ depends_on: [base_build]
23
+ command: /bin/bash -c 'rspec && yard && rake yardstick_measure && rake verify_measurements'
@@ -1,10 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
 
3
5
  module ActiveRecord
6
+ # Extends +ActiveRecord::Base+ with additional class methods.
7
+
8
+ # These methods are delegated to the `all` scope of the model,
9
+ # allowing you to call them directly on the model class.
4
10
  class Base
5
11
  class << self
6
- delegate :percentage, :pluck_as_json, to: :all
12
+ delegate :percentage, :pluck_as_json, :scopped, to: :all
13
+
14
+ # @method percentage
15
+ # @api public
16
+ #
17
+ # Calculates the percentage of records that match a given condition
18
+ # @overload (see ActiveRecord::Relation#percentage)
19
+ #
20
+ # @see ActiveRecord::Relation#percentage
21
+ # @param (see ActiveRecord::Relation#percentage)
22
+ # @return (see ActiveRecord::Relation#percentage)
23
+ # @example (see ActiveRecord::Relation#percentage)
24
+
25
+ # @method scopped
26
+ # @api public
27
+ #
28
+ # Applies the given filters to the relation, returning a new relation
29
+ # @overload (see ActiveRecord::Relation#scopped)
30
+ # @see ActiveRecord::Relation#scopped
31
+ # @param (see ActiveRecord::Relation#scopped)
32
+ # @return (see ActiveRecord::Relation#scopped)
33
+ # @example (see ActiveRecord::Relation#scopped)
34
+
35
+ # @method pluck_as_json
36
+ # @api public
37
+ #
38
+ # Plucks specified columns and returns an array of hashes
39
+ #
40
+ # @overload (see ActiveRecord::Relation#pluck_as_json)
41
+ # @see ActiveRecord::Relation#pluck_as_json
42
+ # @param (see ActiveRecord::Relation#pluck_as_json)
43
+ # @return (see ActiveRecord::Relation#pluck_as_json)
44
+ # @example (see ActiveRecord::Relation#pluck_as_json)
7
45
  end
8
46
  end
9
47
  end
10
-
@@ -1,23 +1,121 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
 
3
5
  module ActiveRecord
6
+ # Extends +ActiveRecord::Relation+ with additional instance methods.
7
+ #
8
+ # These methods are available on all ActiveRecord relations.
4
9
  class Relation
10
+ # @api public
11
+ #
12
+ # Returns the percentage of records that match the given filter
13
+ #
14
+ # @overload percentage(**conditions)
15
+ # @param conditions [Hash] a hash of column names and values to match.
16
+ # @overload percentage(sql_condition)
17
+ # @param sql_condition [String] a raw SQL condition to evaluate.
18
+ # @overload percentage(*scopes)
19
+ # @param scopes [Array<Symbol>] one or more named scopes to chain.
20
+ #
21
+ # @return [Float] fraction of matching records (0.0–1.0), or +0.0+
22
+ # when the relation is empty (to avoid division by zero).
23
+ #
24
+ # @note Do *not* mix Symbol scope names with Hash or String conditions in a
25
+ # single call. Use Symbols together, or a single Hash/String — never both
26
+ # forms at once.
27
+ #
28
+ # @example Using a named scope
29
+ # Document.percentage(:with_error) #=> 0.75
30
+ #
31
+ # @example Using a hash condition
32
+ # Document.percentage(status: :error) #=> 0.75
33
+ #
34
+ # @example Using a raw SQL string
35
+ # Document.percentage("status = 'error'") #=> 0.75
36
+ #
37
+ # @example Chaining multiple named scopes
38
+ # Document.percentage(:active, :with_error) #=> 0.25
39
+ #
40
+ # @example Within a nested scope
41
+ # Document.active.percentage(:with_error) #=> 0.5
42
+ #
43
+ # @example Empty relation returns 0.0, not a Float
44
+ # Document.where(id: nil).percentage(:with_error) #=> 0.0
45
+ #
46
+ # @see #scopped for the underlying implementation of filter application.
5
47
  def percentage(*filters)
6
- return 0 if count == 0
48
+ return 0.0 if count.zero?
7
49
 
8
- if filters.first.is_a?(Symbol)
9
- filtered = filters.inject(self) do |relation, scope|
10
- relation.public_send(scope)
11
- end
12
- else
13
- filtered = where(*filters)
14
- end
50
+ scopped(*filters).count / count.to_f
51
+ end
15
52
 
16
- filtered.count * 1.0 / count
53
+ # @api private
54
+ #
55
+ # Applies the given filters to the relation, returning a new relation
56
+ #
57
+ # This is a helper method for +percentage+ to handle both named scopes and
58
+ # arbitrary conditions.
59
+ #
60
+ # @overload scopped(**conditions)
61
+ # @param conditions [Hash] a hash of column names and values to match.
62
+ # @overload scopped(sql_condition)
63
+ # @param sql_condition [String] a raw SQL condition to evaluate.
64
+ # @overload scopped(*scopes)
65
+ # @param scopes [Array<Symbol>] one or more named scopes to chain.
66
+ #
67
+ # @return [ActiveRecord::Relation] a new relation with the filters applied.
68
+ #
69
+ # @example Using a named scope
70
+ # Document.scopped(:with_error) #=> returns relation with :with_error scope applied
71
+ #
72
+ # @example Using a hash condition
73
+ # Document.scopped(status: :error) #=> returns relation where status is error
74
+ #
75
+ # @example Using a raw SQL string
76
+ # Document.scopped("status = 'error'") #=> returns relation where status is error
77
+ #
78
+ # @example Chaining multiple named scopes
79
+ # Document.scopped(:active, :with_error) #=> returns relation with both scopes applied
80
+ def scopped(*filters)
81
+ return where(*filters) unless filters.first.is_a?(Symbol)
82
+
83
+ filters.inject(self) do |relation, scope|
84
+ relation.public_send(scope)
85
+ end
17
86
  end
18
87
 
88
+ # @api public
89
+ #
90
+ # Returns an array of hashes for the selected columns, one hash per record
91
+ #
92
+ # This is similar to +pluck+, but each row is represented as a Hash keyed
93
+ # by column name (as a Symbol) rather than a plain Array.
94
+ #
95
+ # When called with no arguments, returns the full +as_json+ representation
96
+ # of every record in the relation.
97
+ #
98
+ # @overload pluck_as_json(*keys)
99
+ # @param keys [Array<Symbol>] column names to include in each hash.
100
+ # @overload pluck_as_json()
101
+ #
102
+ # @return [Array<Hash>] array of hashes mapping column name to value.
103
+ #
104
+ # @example Selecting specific columns
105
+ # Document.pluck_as_json(:id, :status)
106
+ # #=> [{ id: 1, status: "error" }, { id: 2, status: "success" }]
107
+ #
108
+ # @example No arguments returns all columns
109
+ # Document.pluck_as_json
110
+ # #=> [{ id: 1, status: "error", active: false, ... }, ...]
111
+ #
112
+ # @example Works with scope chains
113
+ # Document.active.pluck_as_json(:id, :status)
114
+ # #=> [{ id: 2, status: "success" }]
19
115
  def pluck_as_json(*keys)
20
- keys.empty? ? map(&:as_json) : pluck(*keys).map { |i| i.as_hash(keys) }
116
+ return map(&:as_json) if keys.empty?
117
+
118
+ pluck(*keys).map { |entry| entry.as_hash(keys) }
21
119
  end
22
120
  end
23
121
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Darthjee
2
4
  module ActiveExt
3
- VERSION = '1.3.2'
5
+ VERSION = '1.4.0'
4
6
  end
5
7
  end
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'darthjee/core_ext'
2
4
  require 'active_record/relation_ext'
3
5
  require 'active_record/base_ext'
4
6
 
5
7
  module Darthjee
8
+ # Namespace for the gem.
6
9
  module ActiveExt
7
10
  require 'darthjee/active_ext/version'
8
11
  end
data/lib/darthjee.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Namespace for the gem.
1
4
  module Darthjee
2
5
  require 'darthjee/active_ext'
3
6
  end
4
-
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe ActiveRecord::Relation do
4
- let(:subject) { Document.all }
6
+ subject { Document }
5
7
 
6
- describe '#percentage' do
8
+ describe '.percentage' do
7
9
  it_behaves_like 'a method that returns the percentage of objects found'
8
10
  end
9
11
 
10
- describe '#pluck_as_json' do
12
+ describe '.pluck_as_json' do
11
13
  it_behaves_like 'a method that works as pluck but returning the keys'
12
14
  end
13
15
  end
data/spec/spec_helper.rb CHANGED
@@ -1,12 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'simplecov'
2
4
 
3
- SimpleCov.profiles.define 'gem' do
5
+ if ENV['CI']
6
+ require 'simplecov-lcov'
7
+ SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
8
+ SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
9
+ end
10
+
11
+ SimpleCov.start do
4
12
  add_filter '/spec/'
5
13
  end
6
14
 
7
- SimpleCov.start 'gem'
8
15
  require 'darthjee/active_ext'
9
- ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
16
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
10
17
 
11
18
  # This file was generated by the `rspec --init` command. Conventionally, all
12
19
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
@@ -17,8 +24,8 @@ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:"
17
24
 
18
25
  # Requires supporting ruby files with custom matchers and macros, etc,
19
26
  # in spec/support/ and its subdirectories.
20
- support_files = File.expand_path("spec/support/**/*.rb")
21
- Dir[support_files].each { |file| require file }
27
+ support_files = File.expand_path('spec/support/**/*.rb')
28
+ Dir[support_files].each { |file| require file }
22
29
 
23
30
  RSpec.configure do |config|
24
31
  config.treat_symbols_as_metadata_keys_with_true_values = true
@@ -30,7 +37,4 @@ RSpec.configure do |config|
30
37
  # the seed, which is printed after each run.
31
38
  # --seed 1234
32
39
  config.order = 'random'
33
-
34
- config.before do
35
- end
36
40
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Document < ActiveRecord::Base
2
4
  scope :with_error, -> { where(status: :error) }
3
5
  scope :with_success, -> { where(status: :success) }
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveRecord::Schema.define do
2
4
  self.verbose = false
3
5
 
4
- create_table :documents, :force => true do |t|
6
+ create_table :documents, force: true do |t|
5
7
  t.string :status
6
8
  t.boolean :active, default: false
7
9
  t.timestamps null: true
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  shared_examples 'a method that returns the percentage of objects found' do
@@ -53,7 +55,7 @@ end
53
55
  shared_examples 'a method that works as pluck but returning the keys' do
54
56
  let(:json) { subject.pluck_as_json(:id, :status) }
55
57
  let(:expected) do
56
- [ { id: 1, status: 'error' }, { id: 2, status: 'success' } ]
58
+ [{ id: 1, status: 'error' }, { id: 2, status: 'success' }]
57
59
  end
58
60
 
59
61
  before do
@@ -70,7 +72,7 @@ shared_examples 'a method that works as pluck but returning the keys' do
70
72
  let(:keys) do
71
73
  subject.pluck_as_json.first.keys
72
74
  end
73
- let(:expected) {%w(id status updated_at created_at active)}
75
+ let(:expected) { %w[id status updated_at created_at active] }
74
76
 
75
77
  it 'returns all keys' do
76
78
  expect(keys).to match_array(expected)
metadata CHANGED
@@ -1,141 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: darthjee-active_ext
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darthjee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-19 00:00:00.000000000 Z
11
+ date: 2026-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 5.x
19
+ version: 7.2.x
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 5.x
26
+ version: 7.2.x
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: darthjee-core_ext
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.5.6
33
+ version: 3.1.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 1.5.6
41
- - !ruby/object:Gem::Dependency
42
- name: activerecord
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 5.x
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 5.x
55
- - !ruby/object:Gem::Dependency
56
- name: sqlite3
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
- - !ruby/object:Gem::Dependency
70
- name: bundler
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '1.6'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '1.6'
83
- - !ruby/object:Gem::Dependency
84
- name: rake
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: 11.3.0
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: 11.3.0
97
- - !ruby/object:Gem::Dependency
98
- name: rspec
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '2.14'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '2.14'
111
- - !ruby/object:Gem::Dependency
112
- name: rspec-mocks
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: 2.99.4
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: 2.99.4
125
- - !ruby/object:Gem::Dependency
126
- name: simplecov
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: 0.14.1
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: 0.14.1
40
+ version: 3.1.0
139
41
  description: Extension of active support classes with usefull methods
140
42
  email:
141
43
  - darthjee@gmail.com
@@ -144,20 +46,29 @@ extensions: []
144
46
  extra_rdoc_files: []
145
47
  files:
146
48
  - ".circleci/config.yml"
49
+ - ".github/active_ext-usage.md"
50
+ - ".github/copilot-instructions.md"
147
51
  - ".gitignore"
148
52
  - ".rspec"
53
+ - ".rubocop.yml"
54
+ - ".rubocop_todo.yml"
55
+ - Dockerfile
149
56
  - Gemfile
150
57
  - LICENSE
58
+ - Makefile
151
59
  - README.md
152
60
  - Rakefile
153
61
  - active_ext.gemspec
62
+ - config/check_specs.yml
63
+ - config/rubycritc.rb
64
+ - config/yardstick.rb
65
+ - config/yardstick.yml
154
66
  - docker-compose.yml
155
67
  - lib/active_record/base_ext.rb
156
68
  - lib/active_record/relation_ext.rb
157
69
  - lib/darthjee.rb
158
70
  - lib/darthjee/active_ext.rb
159
71
  - lib/darthjee/active_ext/version.rb
160
- - spec/lib/active_record/base_spec.rb
161
72
  - spec/lib/active_record/relation_spec.rb
162
73
  - spec/spec_helper.rb
163
74
  - spec/support/models/document.rb
@@ -165,7 +76,8 @@ files:
165
76
  - spec/support/shared_examples/active_relation.rb
166
77
  homepage: https://github.com/darthjee/active_ext
167
78
  licenses: []
168
- metadata: {}
79
+ metadata:
80
+ rubygems_mfa_required: 'false'
169
81
  post_install_message:
170
82
  rdoc_options: []
171
83
  require_paths:
@@ -174,22 +86,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
86
  requirements:
175
87
  - - ">="
176
88
  - !ruby/object:Gem::Version
177
- version: '0'
89
+ version: 3.3.1
178
90
  required_rubygems_version: !ruby/object:Gem::Requirement
179
91
  requirements:
180
92
  - - ">="
181
93
  - !ruby/object:Gem::Version
182
94
  version: '0'
183
95
  requirements: []
184
- rubyforge_project:
185
- rubygems_version: 2.6.11
96
+ rubygems_version: 3.5.9
186
97
  signing_key:
187
98
  specification_version: 4
188
99
  summary: Active Extensions
189
- test_files:
190
- - spec/lib/active_record/base_spec.rb
191
- - spec/lib/active_record/relation_spec.rb
192
- - spec/spec_helper.rb
193
- - spec/support/models/document.rb
194
- - spec/support/schema.rb
195
- - spec/support/shared_examples/active_relation.rb
100
+ test_files: []
@@ -1,13 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActiveRecord::Relation do
4
- let(:subject) { Document }
5
-
6
- describe '.percentage' do
7
- it_behaves_like 'a method that returns the percentage of objects found'
8
- end
9
-
10
- describe '.pluck_as_json' do
11
- it_behaves_like 'a method that works as pluck but returning the keys'
12
- end
13
- end