rubocop-nueca 1.1.0 → 1.1.4

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
2
  SHA256:
3
- metadata.gz: 6cd3d37c24acab5f66947faec9dde637103acfa42f660988f38ea7354e605f2c
4
- data.tar.gz: 788b97be9c8bed6dfbcf2782ee80b779e2dcda463a693a1a2bff8faadb91ef4f
3
+ metadata.gz: 0b98f767dd653eaae37a3d6f45edafb1434f096bd0278a20639f254fa1cf2bd1
4
+ data.tar.gz: c905f9858e98c95c71eddcbb6d8039beae354618257d1526c4199c04da3dba59
5
5
  SHA512:
6
- metadata.gz: 34d5421a39b74e48d9eb999fe7ef10dd45b6e2868a6f6d2635fa90ecf2f85e1920ab092ff766be2ab920c342d539de2ff31e82945b92f42f3f1330a03d58a188
7
- data.tar.gz: 54a9044c2ee9c452c59478be3783402d9b161112b34e06fa11e69b2ff0b4aab1f26afb0fb23aad37faf8cf3b657f412245fd1631db04f80732966be6bf02ae0c
6
+ metadata.gz: 4b7ae1a86b531b5847e8554a767631cae2277f2773379cf0e63fc133e8b0938a61f28b7c6da0c5bb2b5bb4419c0776d70ad36ca83697cbfef794a08eb0d77c89
7
+ data.tar.gz: c65696377777f5e0740e1776d6bf72ec57408524448ccac2bfedee5361bd14d934121a78db4d65644fe1b27ebee0f9d2ef5752d9931607f3cdb5d703d41c86a7
@@ -0,0 +1,52 @@
1
+ name: Publish Gem
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Set up Ruby
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: '3.4'
18
+ bundler-cache: true
19
+
20
+ - name: Get gem version
21
+ id: gem_version
22
+ run: |
23
+ VERSION=$(ruby -e "require './lib/rubocop/nueca/version'; puts RuboCop::Nueca::VERSION")
24
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
25
+
26
+ - name: Check if version exists on RubyGems
27
+ id: version_check
28
+ run: |
29
+ if gem list -r rubocop-nueca | grep -q "${{ steps.gem_version.outputs.version }}"; then
30
+ echo "exists=true" >> $GITHUB_OUTPUT
31
+ else
32
+ echo "exists=false" >> $GITHUB_OUTPUT
33
+ fi
34
+
35
+ - name: Build gem
36
+ if: steps.version_check.outputs.exists == 'false'
37
+ run: gem build *.gemspec
38
+
39
+ - name: Publish to RubyGems
40
+ if: steps.version_check.outputs.exists == 'false'
41
+ run: |
42
+ mkdir -p $HOME/.gem
43
+ touch $HOME/.gem/credentials
44
+ chmod 0600 $HOME/.gem/credentials
45
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
46
+ gem push *.gem
47
+ env:
48
+ GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_AUTH_TOKEN }}
49
+
50
+ - name: Skip publishing
51
+ if: steps.version_check.outputs.exists == 'true'
52
+ run: echo "Version ${{ steps.gem_version.outputs.version }} already exists on RubyGems. Skipping publish."
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- class ModelAssociationSorting < RuboCop::Cop::Base
6
+ class ModelAssociationSorting < RuboCop::Cop::Base # rubocop:disable Metrics/ClassLength
7
7
  MSG = 'Sort associations of the same type alphabetically. Expected order: %<expected>s.'
8
8
  ASSOCIATION_METHODS = [
9
9
  :belongs_to,
@@ -93,14 +93,11 @@ module RuboCop
93
93
  end
94
94
 
95
95
  def check_group_sorting(group_associations)
96
- # Separate associations with and without through
97
96
  through_associations = group_associations.select { |assoc| assoc[:through] }
98
97
  regular_associations = group_associations.reject { |assoc| assoc[:through] }
99
98
 
100
- # Sort regular associations alphabetically
101
99
  regular_sorted = regular_associations.sort_by { |assoc| assoc[:name] }
102
100
 
103
- # Build expected order respecting through dependencies
104
101
  expected_order = build_expected_order(regular_sorted, through_associations)
105
102
  actual_order = group_associations.map { |assoc| assoc[:name] }
106
103
 
@@ -113,28 +110,67 @@ module RuboCop
113
110
  end
114
111
 
115
112
  def build_expected_order(regular_associations, through_associations)
116
- expected = []
113
+ all_associations = regular_associations + through_associations
114
+ association_lookup = build_association_lookup(all_associations)
115
+ dependency_graph = build_dependency_graph(all_associations, association_lookup)
117
116
 
118
- # Add regular associations first, sorted alphabetically
119
- regular_associations.each do |assoc|
120
- expected << assoc[:name]
117
+ topological_sort_alphabetically(dependency_graph, all_associations.map { |a| a[:name] })
118
+ end
119
+
120
+ def build_association_lookup(associations)
121
+ associations.each_with_object({}) { |assoc, lookup| lookup[assoc[:name]] = assoc } # rubocop:disable Rails/IndexBy
122
+ end
123
+
124
+ def build_dependency_graph(associations, lookup)
125
+ graph = associations.each_with_object({}) { |assoc, deps| deps[assoc[:name]] = [] }
126
+
127
+ associations.each do |assoc|
128
+ graph[assoc[:through]] << assoc[:name] if assoc[:through] && lookup[assoc[:through]]
129
+ end
130
+
131
+ graph
132
+ end
133
+
134
+ def topological_sort_alphabetically(dependency_graph, all_nodes)
135
+ in_degrees = calculate_in_degrees(dependency_graph, all_nodes)
136
+ available_nodes = nodes_with_zero_dependencies(in_degrees)
137
+ result = []
138
+
139
+ until available_nodes.empty?
140
+ current = available_nodes.shift
141
+ result << current
121
142
 
122
- # Add any through associations that depend on this one
123
- dependent_through = through_associations.select { |ta| ta[:through] == assoc[:name] }
124
- dependent_through.sort_by { |ta| ta[:name] }.each do |ta|
125
- expected << ta[:name]
126
- end
143
+ process_dependents(current, dependency_graph, in_degrees, available_nodes)
127
144
  end
128
145
 
129
- # Add any through associations that don't have dependencies in this group
130
- orphaned_through = through_associations.reject do |ta|
131
- regular_associations.any? { |ra| ra[:name] == ta[:through] }
146
+ result
147
+ end
148
+
149
+ def calculate_in_degrees(dependency_graph, all_nodes)
150
+ in_degrees = all_nodes.each_with_object({}) { |node, degrees| degrees[node] = 0 }
151
+
152
+ dependency_graph.each_value do |dependents|
153
+ dependents.each { |dependent| in_degrees[dependent] += 1 }
132
154
  end
133
- orphaned_through.sort_by { |ta| ta[:name] }.each do |ta|
134
- expected << ta[:name]
155
+
156
+ in_degrees
157
+ end
158
+
159
+ def nodes_with_zero_dependencies(in_degrees)
160
+ in_degrees.select { |_node, degree| degree.zero? }.keys.sort
161
+ end
162
+
163
+ def process_dependents(current_node, dependency_graph, in_degrees, available_nodes)
164
+ dependency_graph[current_node]&.each do |dependent|
165
+ in_degrees[dependent] -= 1
166
+
167
+ insert_alphabetically(available_nodes, dependent) if in_degrees[dependent].zero?
135
168
  end
169
+ end
136
170
 
137
- expected
171
+ def insert_alphabetically(sorted_array, element)
172
+ insert_position = sorted_array.bsearch_index { |x| x > element } || sorted_array.length
173
+ sorted_array.insert(insert_position, element)
138
174
  end
139
175
  end
140
176
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Nueca
5
- VERSION = '1.1.0'
5
+ VERSION = '1.1.4'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-nueca
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tien
@@ -133,6 +133,7 @@ executables: []
133
133
  extensions: []
134
134
  extra_rdoc_files: []
135
135
  files:
136
+ - ".github/workflows/publish.yml"
136
137
  - CODE_OF_CONDUCT.md
137
138
  - LICENSE.txt
138
139
  - README.md