I've got a codebase which is tested in two scenarios: run via entry point A, and B. When it's run via A, the db connection is used as is. When it's run via B, ActiveRecord::Base.connection is monkey patched.
Since B is just a helper script, it's currently tested in rspec by running it as an external command and checking the output. I'd like to bring some sanity back and test the behaviour without spawning new processes though.
Is there a way in rspec mocks to "temporarily extend" a class? I'd like to get the behaviour of doing:
before do
ActiveRecord::Base.connection.extend(App::SomePatch)
end
after do
ActiveRecord::Base.connection.unextend(App::SomePatch)
end
Of course unextend doesn't exist. I have only 3 methods to patch, so I could potentially use the mocks for each method instead, but a method alias makes this complicated.
The patch module looks like this:
module SomePatch
def SomePatch.included(mod)
alias :old_execute :execute
end
def execute(*args) ... end
def some_storage
#some_storage ||= []
end
end
I would go with cloning, something along this lines:
before do
#original_connection = ActiveRecord::Base.connection
ActiveRecord::Base.connection = #original_commention.dup
ActiveRecord::Base.connection.extend(App::SomePatch)
end
after do
ActiveRecord::Base.connection = #original_connection
end
I did not test that, but as long there are not "quirks" with cloning the object, this should be fine.
Edit: Ok, this does not work, because there's no connection= method, so you can probably try with mocking:
before do
#original_connection = ActiveRecord::Base.connection
new_connection = #original_connection.dup
new_connection.extend(App::SomePatch)
allow(ActiveRecord::Base).to receive(:connection).and_return(new_connection)
end
And you probably don't need after because the mock will be "undone"
Related
I want to run a service object in a rake task but need to change a method in a different service that my main service object calls. For example if I have:
main_service.rb
class MainService
def perform
SecondaryService.new.perform
end
end
secondary_service.rb
class SecondaryService
def perform
some_method
end
def some_method
puts 'something'
end
end
And I want to change some_method to be puts 'anything' for a one time data fix in a rake task, could I override it by simply redefining the method and is there a way to scope it to just the rake task? I don't want this service to accidentally be called while I run the rake task in case. I was thinking something like this:
one_time.rake
class SecondaryService
def some_method
puts 'anything'
end
end
def one_time_change
MainService.new.perform
end
The code you've suggested should work fine. You could go with that.
However, depending on the context, there is a risk that this strategy may have unintended consequences - i.e. What happens if you accidentally execute the class monkey-patching but expected the original behaviour to be preserved?
A more robust approach is to use dependency injection. By doing this, you can override behaviour with classical inheritance (or even by passing an entirely new object!). For example, something like:
class MainService
def perform(secondary_service: SecondaryService.new)
secondary_service.perform
end
end
class ModifiedSecondaryService < SecondaryService
def some_method
puts 'anything'
end
end
Now, for your one-off rake task, you can run:
PrimaryService.new.perform(secondary_service: ModifiedSecondaryService.new)
I was wondering if it's possible to make it so that if I had something like
class Test
attr_reader :access_times
def initialize
#access_times = 0
end
def get_two
2
end
...
end
t = Test.new
That any access to t would run a particular piece of code before actually running the method?
For example, if I suddenly decided to say t.get_two, the fact that I used the . syntax would increment #access_times by 1. Or perhaps I made a check t.is_a?(Test), it would also increment #access_times by 1. Accessing any methods or attributes inherited by Test would also increment the variable by 1.
Basically I want to add some stuff to the . syntax if possible.
I am not asking whether this is good or bad code, just whether it's possible and how it would be done. I wouldn't normally use it since I could just add the increment logic to every method manually and replace all direct instance variable accessing with methods (even things like is_a? and other things inherited from Object)
a pretty hardcore-version would be to use set_trace_func: http://apidock.com/ruby/Kernel/set_trace_func
this allows you to subscribe to all the ruby events fired throughout your program, which can be a ton of calls...
i don't think that there is a build-in hook for registering to arbitrary method-calls. you could implement something with method-missing, method-chaining or delegation, but that would depend on your requirments.
If you don't need everything to be standalone, a suggestion would just be to extend ActiveModel::Callbacks. Simply extend the class and you'll have all of the functionality of a before_filter without requiring all of the other Rails stuff.
Here is a workaround according to your description. Basically it will incremental #access_times for each of the instance method, and the method also does what it does before.
class Test
attr_accessor :access_times
def initialize
#access_times = 0
end
def get_two
2
end
end
class Test
##im = instance_methods
##im.each do |m|
class_eval <<-END
alias temporary #{m}
END
define_method(m) do |*args, &block|
#access_times += 1
temporary(*args, &block)
end
end
undef :temporary
end
Test.new.get_two # => #access_times += 1 and original get_two is called: 2
While this piece of code doesn't work as expected, I'll have a look at it later. Thanks.
Let's say that I have a class called MyClass, basically I want to do something like this:
class MyClass
def initialize(a)
do_stuff a, 4, 11
end
def do_stuff(a,b,c)
# ...
end
end
# rspec below
MyClass.any_instance.should_receive(:do_stuff).with_the_values(10,
anything, anything)
MyClass.new 10
Basically, I want to check that initialize will call on, and pass the correct value to do_stuff.
I think you should test if your class behaves correctly or not instead of watching who passes what to whom.
I guess, the do_stuff method is supposed to produce a side-effect of some sort. If so, then check if this side-effect is the one you expect. This way you're free to change actual implementation of your class without needing to rewrite your specs every time.
I'm having some difficulty with referring to module-level variables in ruby. Say I have a situation like this, where I'm referring to M.a internally:
module M
##a=1
def self.a
##a
end
class A
def x
M.a
end
end
end
Now, this example works fine for me but it is failing in a slightly more complicated context (where the module is spread over a number of files installed in a local gem - but my understanding is that that should not effect the way the code is executed) with an error like this: undefined method `a' for M::M (NoMethodError).
So, is this the correct way to refer to module level variables in context? is there a simpler/more idiomatic way?
If the module is spread out over other files, you need to ensure that your initialization is run before the method is called. If they are in the same file, this should be as much as guaranteed, but if you somehow split them there could be trouble.
I've found you can usually get away with this:
module M
def self.a
#a ||= 1
end
end
If this variable is subject to change, you will need a mutator method. Rails provides mattr_accessor that basically does what you want, part of ActiveSupport.
I'm trying to write a method that tells me every class that includes a particular Module. It looks like this -
def Rating.rateable_objects
rateable_objects = []
ObjectSpace.each_object(Class) do |c|
next unless c.include? Rateable
rateable_objects << c
end
rateable_objects
end
Where "Rateable" is my module that I'm including in several models.
What i'm finding is that this method return [] if i call it immediately after booting rails console or running the server. But if i first instantiate an instance of one of the consuming models it will return that model in the result.
So when do modules get included? I'm guessing later in the process than when he app starts up. If i can't get this information this way early in the process, is there anyway to accomplish this?
They are included when the include statement is executed when the class is defined. Rails autoloads modules/classes when you first use them. Also, you might try something like this:
module Rateable
#rateable_object = []
def self.included(klass)
#rateable_objects << klass
end
def rateable_objects
#rateable_objects
end
end
This will build the list as classes include the module.