Skip to content

Commit 64086ed

Browse files
fix: Address code review issues and optimize package_json gem usage
- Fixed missing newline at end of js_dependency_manager.rb - Removed recursive add_js_dependencies wrapper in BaseGenerator - Fixed namespace issues in tests (ReactOnRails::Generators::InstallGenerator) - Improved RSpec test structure with proper matchers - Optimized package_json gem usage: - Added package_json_available? check - Better separation between single and batch dependency adds - Improved fallback handling for when package_json is unavailable - Use package_json.manager.install when available Co-authored-by: Justin Gordon <justin808@users.noreply.github.com>
1 parent 4deda05 commit 64086ed

File tree

3 files changed

+125
-65
lines changed

3 files changed

+125
-65
lines changed

lib/generators/react_on_rails/base_generator.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,6 @@ def add_base_gems_to_gemfile
9797
run "bundle"
9898
end
9999

100-
def add_js_dependencies
101-
setup_js_dependencies
102-
end
103-
104100
def update_gitignore_for_auto_registration
105101
gitignore_path = File.join(destination_root, ".gitignore")
106102
return unless File.exist?(gitignore_path)

lib/generators/react_on_rails/js_dependency_manager.rb

Lines changed: 99 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def setup_js_dependencies
1414
@added_dependencies_to_package_json ||= false
1515
@ran_direct_installs ||= false
1616
add_js_dependencies
17+
# Only run final install if package_json gem was used and no direct installs ran
1718
install_js_dependencies if @added_dependencies_to_package_json && !@ran_direct_installs
1819
end
1920

@@ -27,25 +28,24 @@ def add_js_dependencies
2728
def add_react_on_rails_package
2829
major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
2930

30-
# Try to use package_json gem first, fall back to direct npm commands
3131
react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
32-
["react-on-rails@#{ReactOnRails::VERSION}"]
32+
"react-on-rails@#{ReactOnRails::VERSION}"
3333
else
3434
puts "Adding the latest react-on-rails NPM module. " \
3535
"Double check this is correct in package.json"
36-
["react-on-rails"]
36+
"react-on-rails"
3737
end
3838

3939
puts "Installing React on Rails package..."
40-
if add_npm_dependencies(react_on_rails_pkg)
40+
if add_js_dependency(react_on_rails_pkg)
4141
@added_dependencies_to_package_json = true
42-
return
42+
else
43+
# Fallback to direct npm install
44+
puts "Using direct npm commands as fallback"
45+
success = system("npm", "install", react_on_rails_pkg)
46+
@ran_direct_installs = true if success
47+
handle_npm_failure("react-on-rails package", [react_on_rails_pkg]) unless success
4348
end
44-
45-
puts "Using direct npm commands as fallback"
46-
success = system("npm", "install", *react_on_rails_pkg)
47-
@ran_direct_installs = true if success
48-
handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
4949
end
5050

5151
def add_react_dependencies
@@ -58,14 +58,15 @@ def add_react_dependencies
5858
babel-plugin-transform-react-remove-prop-types
5959
babel-plugin-macros
6060
]
61-
if add_npm_dependencies(react_deps)
61+
62+
if add_js_dependencies_batch(react_deps)
6263
@added_dependencies_to_package_json = true
63-
return
64+
else
65+
# Fallback to direct npm install
66+
success = system("npm", "install", *react_deps)
67+
@ran_direct_installs = true if success
68+
handle_npm_failure("React dependencies", react_deps) unless success
6469
end
65-
66-
success = system("npm", "install", *react_deps)
67-
@ran_direct_installs = true if success
68-
handle_npm_failure("React dependencies", react_deps) unless success
6970
end
7071

7172
def add_css_dependencies
@@ -76,14 +77,15 @@ def add_css_dependencies
7677
mini-css-extract-plugin
7778
style-loader
7879
]
79-
if add_npm_dependencies(css_deps)
80+
81+
if add_js_dependencies_batch(css_deps)
8082
@added_dependencies_to_package_json = true
81-
return
83+
else
84+
# Fallback to direct npm install
85+
success = system("npm", "install", *css_deps)
86+
@ran_direct_installs = true if success
87+
handle_npm_failure("CSS dependencies", css_deps) unless success
8288
end
83-
84-
success = system("npm", "install", *css_deps)
85-
@ran_direct_installs = true if success
86-
handle_npm_failure("CSS dependencies", css_deps) unless success
8789
end
8890

8991
def add_dev_dependencies
@@ -92,29 +94,72 @@ def add_dev_dependencies
9294
@pmmmwh/react-refresh-webpack-plugin
9395
react-refresh
9496
]
95-
if add_npm_dependencies(dev_deps, dev: true)
97+
98+
if add_js_dependencies_batch(dev_deps, dev: true)
9699
@added_dependencies_to_package_json = true
97-
return
100+
else
101+
# Fallback to direct npm install
102+
success = system("npm", "install", "--save-dev", *dev_deps)
103+
@ran_direct_installs = true if success
104+
handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
98105
end
106+
end
107+
108+
# Add a single dependency using package_json gem
109+
def add_js_dependency(package, dev: false)
110+
return false unless package_json_available?
111+
112+
pj = package_json
113+
return false unless pj
114+
115+
begin
116+
if dev
117+
pj.manager.add(package, type: :dev)
118+
else
119+
pj.manager.add(package)
120+
end
121+
true
122+
rescue StandardError => e
123+
puts "Warning: Could not add #{package} via package_json gem: #{e.message}"
124+
false
125+
end
126+
end
99127

100-
success = system("npm", "install", "--save-dev", *dev_deps)
101-
@ran_direct_installs = true if success
102-
handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
128+
# Add multiple dependencies at once using package_json gem
129+
def add_js_dependencies_batch(packages, dev: false)
130+
return false unless package_json_available?
131+
132+
# Use the add_npm_dependencies helper from GeneratorHelper
133+
add_npm_dependencies(packages, dev: dev)
134+
end
135+
136+
# Check if package_json gem is available and loaded
137+
def package_json_available?
138+
# Check if Shakapacker or package_json gem is available
139+
return true if defined?(PackageJson)
140+
141+
begin
142+
require "package_json"
143+
true
144+
rescue LoadError
145+
false
146+
end
103147
end
104148

105149
def install_js_dependencies
106-
# Detect which package manager to use
107-
success = if File.exist?(File.join(destination_root, "yarn.lock"))
108-
system("yarn", "install")
109-
elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
110-
system("pnpm", "install")
111-
elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
112-
File.exist?(File.join(destination_root, "package.json"))
113-
# Use npm for package-lock.json or as default fallback
114-
system("npm", "install")
115-
else
116-
true # No package manager detected, skip
117-
end
150+
# First try to use package_json gem's install method if available
151+
if package_json_available? && package_json
152+
begin
153+
package_json.manager.install
154+
return true
155+
rescue StandardError => e
156+
puts "Warning: package_json gem install failed: #{e.message}"
157+
# Fall through to manual detection
158+
end
159+
end
160+
161+
# Fallback to detecting package manager and running install
162+
success = detect_and_run_package_manager_install
118163

119164
unless success
120165
GeneratorMessages.add_warning(<<~MSG.strip)
@@ -131,6 +176,21 @@ def install_js_dependencies
131176
success
132177
end
133178

179+
def detect_and_run_package_manager_install
180+
# Detect which package manager to use based on lock files
181+
if File.exist?(File.join(destination_root, "yarn.lock"))
182+
system("yarn", "install")
183+
elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
184+
system("pnpm", "install")
185+
elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
186+
File.exist?(File.join(destination_root, "package.json"))
187+
# Use npm for package-lock.json or as default fallback
188+
system("npm", "install")
189+
else
190+
true # No package manager detected, skip
191+
end
192+
end
193+
134194
def handle_npm_failure(dependency_type, packages, dev: false)
135195
install_command = dev ? "npm install --save-dev" : "npm install"
136196
GeneratorMessages.add_warning(<<~MSG.strip)

spec/react_on_rails/generators/message_deduplication_spec.rb

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727

2828
# Count occurrences of the success message
2929
success_count = output_text.scan("🎉 React on Rails Successfully Installed!").count
30-
expect(success_count).to eq(1),
30+
expect(success_count).to(
31+
eq(1),
3132
"Expected success message to appear exactly once, but appeared #{success_count} times"
33+
)
3234

3335
# Ensure post-install message components are present
3436
expect(output_text).to include("📋 QUICK START:")
@@ -43,8 +45,10 @@
4345

4446
# Count occurrences of the success message
4547
success_count = output_text.scan("🎉 React on Rails Successfully Installed!").count
46-
expect(success_count).to eq(1),
48+
expect(success_count).to(
49+
eq(1),
4750
"Expected success message to appear exactly once with Redux, but appeared #{success_count} times"
51+
)
4852

4953
# Ensure post-install message components are present
5054
expect(output_text).to include("📋 QUICK START:")
@@ -57,15 +61,17 @@
5761
end
5862

5963
describe "NPM install execution" do
60-
let(:install_generator) { InstallGenerator.new }
64+
let(:install_generator) { ReactOnRails::Generators::InstallGenerator.new }
6165

6266
before do
6367
# Mock the system to track NPM install calls
64-
allow(install_generator).to receive(:system).and_return(true)
65-
allow(install_generator).to receive(:add_npm_dependencies).and_return(false)
68+
allow(install_generator).to receive_messages(
69+
system: true,
70+
add_npm_dependencies: false,
71+
destination_root: "/test/path"
72+
)
6673
allow(File).to receive(:exist?).and_return(false)
67-
allow(File).to receive(:exist?).with(File.join(anything, "package.json")).and_return(true)
68-
allow(install_generator).to receive(:destination_root).and_return("/test/path")
74+
allow(File).to receive(:exist?).with(a_string_matching(/package\.json$/)).and_return(true)
6975

7076
# Initialize instance variables
7177
install_generator.instance_variable_set(:@added_dependencies_to_package_json, false)
@@ -104,33 +110,31 @@
104110

105111
describe "JS dependency method organization" do
106112
it "uses the shared JsDependencyManager module in base_generator" do
107-
expect(ReactOnRails::Generators::BaseGenerator.ancestors).to include(ReactOnRails::Generators::JsDependencyManager)
113+
expect(ReactOnRails::Generators::BaseGenerator.ancestors)
114+
.to include(ReactOnRails::Generators::JsDependencyManager)
108115
end
109116

110117
it "uses the shared JsDependencyManager module in install_generator" do
111-
expect(ReactOnRails::Generators::InstallGenerator.ancestors).to include(ReactOnRails::Generators::JsDependencyManager)
118+
expect(ReactOnRails::Generators::InstallGenerator.ancestors)
119+
.to include(ReactOnRails::Generators::JsDependencyManager)
112120
end
113121

114122
it "does not duplicate JS dependency methods between generators" do
115123
base_generator = ReactOnRails::Generators::BaseGenerator.new
116124
install_generator = ReactOnRails::Generators::InstallGenerator.new
117125

118126
# Both should respond to the shared methods
119-
shared_methods = [:setup_js_dependencies, :add_js_dependencies, :install_js_dependencies]
127+
shared_methods = %i[setup_js_dependencies add_js_dependencies install_js_dependencies]
120128

121129
shared_methods.each do |method|
122-
expect(base_generator).to respond_to(method, true)
123-
expect(install_generator).to respond_to(method, true)
124-
end
125-
126-
# The methods should come from the same module
127-
shared_methods.each do |method|
128-
base_method = base_generator.method(method)
129-
install_method = install_generator.method(method)
130-
131-
expect(base_method.owner).to eq(ReactOnRails::Generators::JsDependencyManager)
132-
expect(install_method.owner).to eq(ReactOnRails::Generators::JsDependencyManager)
130+
expect(base_generator.respond_to?(method, true)).to be(true)
131+
expect(install_generator.respond_to?(method, true)).to be(true)
132+
# Verify the methods are defined by the shared module
133+
expect(ReactOnRails::Generators::BaseGenerator.instance_method(method).owner)
134+
.to eq(ReactOnRails::Generators::JsDependencyManager)
135+
expect(ReactOnRails::Generators::InstallGenerator.instance_method(method).owner)
136+
.to eq(ReactOnRails::Generators::JsDependencyManager)
133137
end
134138
end
135139
end
136-
end
140+
end

0 commit comments

Comments
 (0)