Définir une méthode en Ruby est assez simple, je peux simplement utiliser undef METHOD_NAME
.
Y a-t-il quelque chose de similaire pour une classe? Je suis sur MRI 1.9.2
.
Je dois annuler la définition d'un modèle ActiveRecord, exécuter deux lignes de code et restaurer le modèle dans sa forme d'origine.
Le problème est que j'ai un modèle Contact
et j'utilise l'API d'une entreprise et il se trouve qu'ils ont une classe appelée Contact
, et changer le nom de mon modèle serait beaucoup de travail pour moi.
Que puis-je faire dans cette situation?
>> class Foo; end
=> nil
>> Object.constants.include?(:Foo)
=> true
>> Object.send(:remove_const, :Foo)
=> Foo
>> Object.constants.include?(:Foo)
=> false
>> Foo
NameError: uninitialized constant Foo
MODIFIER Je viens de remarquer votre modification, la suppression de la constante n'est probablement pas la meilleure façon de réaliser ce que vous recherchez. Pourquoi ne pas simplement déplacer l'une des classes Contact
dans un espace de noms séparé.
EDIT2 Vous pouvez également renommer temporairement votre classe comme ceci:
class Foo
def bar
'here'
end
end
TemporaryFoo = Foo
Object.send(:remove_const, :Foo)
# do some stuff
Foo = TemporaryFoo
Foo.new.bar #=> "here"
Encore une fois, le problème avec cela est que vous aurez toujours la nouvelle classe Contact
, vous devrez donc la supprimer à nouveau. Je recommanderais vraiment d'espacer les noms de vos classes à la place. Cela vous aidera également à éviter tout problème de chargement
Dans une situation similaire - se moquant d'une classe utilisée en interne par une autre classe que j'essaie de tester - j'ai trouvé que c'était une solution viable:
describe TilesAuth::Communicator do
class FakeTCPSocket
def initialize(*_); end
def puts(*_); end
end
context "when the response is SUCCESS" do
before do
class TilesAuth::Communicator::TCPSocket < FakeTCPSocket
def gets; 'SUCCESS'; end
end
end
after { TilesAuth::Communicator.send :remove_const, :TCPSocket }
it "returns success" do
communicator = TilesAuth::Communicator.new Host: nil, port: nil, timeout: 0.2
response = communicator.call({})
expect(response["success"]).to eq(true)
expect(response).not_to have_key("error")
expect(response).not_to have_key("invalid_response")
end
end
end
J'aurais pensé qu'il y aurait une meilleure façon de le faire - c'est-à-dire que je ne pouvais pas voir un moyen de transmettre la valeur de retour souhaitée pour la réutilisation - mais cela semble assez bon pour l'instant. Je suis nouveau dans les moqueries/usines, et j'aimerais entendre parler de toutes les méthodes alternatives.
Éditer:
Ok, donc pas si similaire après tout.
J'ai trouvé une meilleure façon d'utiliser la maquette RSpec, grâce à ne excellente explication dans le groupe Google RSpec :
context "with socket response mocked" do
let(:response) do
tcp_socket_object = instance_double("TCPSocket", puts: nil, gets: socket_response)
class_double("TCPSocket", new: tcp_socket_object).as_stubbed_const
communicator = TilesAuth::Communicator.new Host: nil, port: nil, timeout: 0.2
communicator.call({})
end
context "as invalid JSON" do
let(:socket_response) { 'test invalid json' }
it "returns an error response including the invalid socket response" do
expect(response["success"]).to eq(false)
expect(response).to have_key("error")
expect(response["invalid_response"]).to eq(socket_response)
end
end
context "as SUCCESS" do
let(:socket_response) { 'SUCCESS' }
it "returns success" do
expect(response["success"]).to eq(true)
expect(response).not_to have_key("error")
expect(response).not_to have_key("invalid_response")
end
end
end