require 'spec_helper' module RailsBestPractices module Reviews describe CheckSaveReturnValueReview do let(:runner) { Core::Runner.new(reviews: CheckSaveReturnValueReview.new) } describe 'check_save_return_value' do it 'should warn you if you fail to check save return value' do content =<<-EOF def my_method post = Posts.new do |p| p.title = "foo" end post.save end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(1) expect(runner.errors[0].to_s).to eq("app/helpers/posts_helper.rb:5 - check 'save' return value or use 'save!'") end it 'should allow save return value assigned to var' do content =<<-EOF def my_method post = Posts.new do |p| p.title = "foo" end check_this_later = post.save end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'should allow save return value used in if' do content =<<-EOF def my_method post = Posts.new do |p| p.title = "foo" end if post.save "OK" else raise "could not save" end end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'should allow save return value used in elsif' do content =<<-EOF def my_method post = Posts.new do |p| p.title = "foo" end if current_user "YES" elsif post.save "OK" else raise "could not save" end end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'should allow save return value used in unless' do content =<<-EOF def my_method unless @post.save raise "could not save" end end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'should allow save return value used in if_mod' do content =<<-EOF def my_method post = Posts.new do |p| p.title = "foo" end "OK" if post.save end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'should allow save return value used in unless_mod' do content =<<-EOF def my_method post = Posts.new do |p| p.title = "foo" end "NO" unless post.save end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'should allow save return value used in unless with &&' do content =<<-EOF def my_method unless some_method(1) && other_method(2) && @post.save raise "could not save" end end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'should allow save!' do content =<<-EOF def my_method post = Posts.new do |p| p.title = "foo" end post.save! end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'should warn you if you fail to check update_attributes return value' do content =<<-EOF def my_method @post.update_attributes params end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(1) expect(runner.errors[0].to_s).to eq("app/helpers/posts_helper.rb:2 - check 'update_attributes' return value or use 'update_attributes!'") end it 'should allow update_attributes if return value is checked' do content =<<-EOF def my_method @post.update_attributes(params) or raise "failed to save" end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end it 'is not clever enough to allow update_attributes if value is returned from method' do # This review is not clever enough to do a full liveness analysis # of whether the returned value is used in all cases. content =<<-EOF class PostsController def update @post = Post.find params(:id) if update_post redirect_to view_post_path post else raise "post not saved" end end def update_post @post.update_attributes(params) end end EOF runner.review('app/controllers/posts_controller.rb', content) expect(runner.errors.size).to eq(1) expect(runner.errors[0].to_s).to eq("app/controllers/posts_controller.rb:12 - check 'update_attributes' return value or use 'update_attributes!'") end it 'should warn you if you use create which is always unsafe' do content =<<-EOF class Post < ActiveRecord::Base end EOF runner.prepare('app/models/post.rb', content) content =<<-EOF def my_method if post = Post.create(params) # post may or may not be saved here! redirect_to view_post_path post end end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(1) expect(runner.errors[0].to_s).to eq("app/helpers/posts_helper.rb:2 - use 'create!' instead of 'create' as the latter may not always save") end it 'should warn you if you use create with a block which is always unsafe' do content =<<-EOF module Blog class Post < ActiveRecord::Base end end EOF runner.prepare('app/models/blog/post.rb', content) content =<<-EOF module Blog class PostsHelper def my_method post = Post.create do |p| p.title = 'new post' end if post # post may or may not be saved here! redirect_to view_post_path post end end end end EOF runner.review('app/helpers/blog/posts_helper.rb', content) expect(runner.errors.size).to eq(1) expect(runner.errors[0].to_s).to eq("app/helpers/blog/posts_helper.rb:4 - use 'create!' instead of 'create' as the latter may not always save") end it 'allows create called on non-model classes' do content =<<-EOF def my_method pk12 = OpenSSL::PKCS12.create( "", # password descr, # friendly name key, cert) end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.map(&:to_s)).to eq([]) end end it 'should not check ignored files' do runner = Core::Runner.new(reviews: CheckSaveReturnValueReview.new(ignored_files: /helpers/)) content =<<-EOF def my_method post = Posts.new do |p| p.title = "foo" end post.save end EOF runner.review('app/helpers/posts_helper.rb', content) expect(runner.errors.size).to eq(0) end end end end