Extending Modules from a gem instead of monkey patching - ruby

Assuming that "clean" Ruby monkey patching isn't an option, and all gem extension methods need to be completely contained within their own namespace, and given a structure like
module ARubyGem
class GemClassOne
def method1
# instance method to be available when calling extended module
end
def self.method2
# singleton method to also be available in extended module
end
end
end
Then which of these is the better way to extend a gem if the original gem code has some singleton methods that begin with "self"?
module ARubyGemExtension
class GemClassOneExtension < ARubyGem::GemClassOne
def method_to_override
# new code here
end
end
end
vs.
module ARubyGemExtension
include ARubyGem
class GemClassOneExtension
def method_to_override
# new code here
end
end
end

Seems like you would need to go with:
module ARubyGemExtension
class GemClassOneExtension < ARubyGem::GemClassOne
def method_to_override
# new code here
end
end
end
(Remember to namespace GemClassOne.)
I don't know anything about the gem you're using, but unless it's specifically set up for providing methods through include, I think you may not get what you're after.

Related

Unable to override RSpec Sysntax module method

I wanted to override the expect in under Syntax module. So, i have placed the below code into the .config/initializers/syntax.rb file
module RSpec
module Expectations
module Syntax
def enable_expect(syntax_host=::RSpec::Matchers)
return if expect_enabled?(syntax_host)
syntax_host.module_exec do
def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block)
::RSpec::Expectations::ExpectationTarget.for(value, block)
end
end
end
end
end
end
And required this inside the env.rb file.
require_relative '../../.config/initializers/syntax'
This is not overriding the existing method. I'm using RSpec gem 3.2.0
What went wrong with the configuration?
I suggest you to put this override codes in spec/support directory and require it in rails or spec helper instead of putting in initializers.

NoMethodError when trying to access a "nested method" (or def) in the same module

The piece of code below gives me a NoMethodError. I'm a little confused why it gives me an error, and why I can't find anything about nesting methods in modules. Could someone please explain why this isn't working? Can I nest "defs" in modules?
module HowToBasic
module_function
def say_id_and_say_name(id)
# nested method
def say_id(id)
p id
end
# errors here with `say_id_and_say_name':
# undefined method `say_id' for HowToBasic:Module (NoMethodError)
# from teststuff.rb:24:in `<main>'
say_id(id)
end
end
HowToBasic.say_id_and_say_name("99999")
Version:
ruby 2.3.1p112
I had a look and couldn't find anything about this:
relates to includes NoMethodError when trying to access method defined in included module
relates to classes https://bugs.ruby-lang.org/issues/11665
seems weird Access a Ruby module's method within same module
you're missing self keyword in method definition - without it say_id_and_say_name is just an instance method, thus it can't be invoked on Module.
module HowToBasic
module_function
def self.say_id(id)
p id
end
def self.say_id_and_say_name(id)
say_id(id)
end
end
HowToBasic.say_id_and_say_name("99999")

Database Cleaner not working in minitest rails

My Minitest controller tests are working fine if I run them alone using rake minitest:controllers but when I run rake minitest:all then I get validation failed error. It is because email is already used in model tests. I used DatabaseCleaner to clean the database but unable to clean database.
My code for database cleaner:
require "database_cleaner"
DatabaseCleaner.strategy = :transaction
class MiniTest::Rails::ActionController::TestCase
include Devise::TestHelpers
def setup
DatabaseCleaner.start
end
def teardown
DatabaseCleaner.clean
end
Short answer:
gem install "minitest-around"
Long answer:
before/after or setup/teardown in minitest are NOT hooks as in rspec, therefore you can't have multiple before/after or setup/teardown in minitest, since what they do is just redefining the method.
To solve this issue, you can use minitest-around, which adds support for multiple before/after or setup/teardown and around, simply add the gem to your test group:
# put in your Gemfile
gem 'minitest-around', group: :test
For setting up the database_cleaner, you can have it as you want, following is an example of the setup:
# tests/support/database_cleaner.rb
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
class Minitest::Rails::ActionController::TestCase
def setup
DatabaseCleaner.start
end
def teardown
DatabaseCleaner.clean
end
end
And in your test files:
# tests/your/test/file_test.rb
require 'support/database_cleaner'
# assertions here ...
That's it, see the Github for detailed info.
If for whatever reason you don't want to add the 'minitest-around' gem (to have more than one setup and teardown method), you can do this in your test_helper.rb...
require "database_cleaner"
DatabaseCleaner.strategy = :transaction
module AroundEachTest
def before_setup
super
DatabaseCleaner.start
end
def after_teardown
super
DatabaseCleaner.clean
end
end
class Minitest::Test
include AroundEachTest
end
I found my mistake ,may be it helps someone else ..
I should write DatabaseCleaner.start in setup of every model test where setup is defined, as i am overwriting setup method in every test file.
This is why I like Minitest; no fancy DSL to block thinking about how to use Ruby properly.
My setup is as follows:
In test_helper.rb
class MyTest < Minitest::Test
def setup
DatabaseCleaner.start
end
def teardown
DatabaseCleaner.clean
end
end
Then I just subclass this in any test that need database cleaning. Note the call super first cleans the db before any subclass-specific setup. The same call to super would need to be included in any subclass teardown method, but this can usually be omitted entirely.
class FooTest < MyTest
def setup
super
#foo = Foo.new(bar: 'whatever')
end
def test_save
#foo.save
assert_equal 1, Foo.count
end
end
If I need to subclass MyTest further (e.g. for integration tests) I include its own setup & teardown methods with calls to super so it goes right up the inheritance tree.
You can DRY up that repetition with this
DatabaseCleaner.strategy = :truncation
class MiniTest::Spec
before :each do
DatabaseCleaner.clean
end
end
This example subclasses the spec runner, but you can pick your test environment of choice.

How to define a method to be called from the configure block of a modular sinatra application?

I have a Sinatra app that, boiled down, looks basically like this:
class MyApp < Sinatra::Base
configure :production do
myConfigVar = read_config_file()
end
configure :development do
myConfigVar = read_config_file()
end
def read_config_file()
# interpret a config file
end
end
Unfortunately, this doesn't work. I get undefined method read_config_file for MyApp:Class (NoMethodError)
The logic in read_config_file is non-trivial, so I don't want to duplicate in both. How can I define a method that can be called from both my configuration blocks? Or am I just approaching this problem in entirely the wrong way?
It seems the configure block is executed as the file is read. You simply need to move the definition of your method before the configure block, and convert it to a class method:
class MyApp < Sinatra::Base
def self.read_config_file()
# interpret a config file
end
configure :production do
myConfigVar = self.read_config_file()
end
configure :development do
myConfigVar = self.read_config_file()
end
end
Your configure blocks are run when the class definition is evaluated. So, the context is the class itself, not an instance. So, you need a class method, not an instance method.
def self.read_config_file
That should work. Haven't tested though. ;)

How to monkeypatch Ruby properly?

I'm trying to monkeypatch a line in Net class in the standard library. I created a file called patches.rb into the lib folder of the project and added this
module Net
class HTTP < Protocol
module HTTPHeader
def initialize_http_header(initheader)
#header = {}
return unless initheader
initheader.each do |key, value|
#header[key.downcase] = [value.strip] rescue ""
end
end
end
end
end
But it doesn't work. Am I doing this right? (That parallels the inheritance hierarchy exactly.)
Edit: part of the problem was I had to put the file in the initalizers folder. But still seeing the same error.
Since things in the lib/ directory are only loaded on demand, you may have more success putting patches like this in config/initializers/ where they are automatically loaded after the stack has been initialized.
You can also collapse the definition for extensions to something like this:
module Net::HTTP::HTTPHeader
# ... (redefined methods) ...
end

Resources