Как определить класс в Ruby?
Определение метода в Ruby довольно просто, я могу просто использовать undef METHOD_NAME
,
Есть ли что-нибудь подобное для класса? Я на MRI 1.9.2
,
Мне нужно отменить определение модели ActiveRecord, запустить две строки кода и восстановить модель до ее первоначальной формы.
Проблема в том, что у меня есть модель Contact
и я использую API компании, и бывает, что у них есть некоторый класс под названием Contact
и изменение названия моей модели будет большой работой для меня.
Что я могу сделать в этой ситуации?
2 ответа
>> 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
РЕДАКТИРОВАТЬ Только что заметил ваше редактирование, удаление константы, вероятно, не лучший способ достичь того, что вы ищете. Почему бы просто не переместить один из Contact
классы в отдельном пространстве имен.
РЕДАКТИРОВАТЬ2 Вы также можете временно переименовать свой класс, как это:
class Foo
def bar
'here'
end
end
TemporaryFoo = Foo
Object.send(:remove_const, :Foo)
# do some stuff
Foo = TemporaryFoo
Foo.new.bar #=> "here"
Опять же, проблема в том, что у вас все еще будет более новый Contact
класс, так что вам придется удалить это снова. Я бы порекомендовал вместо этого интервалы имен. Это также поможет вам избежать проблем с загрузкой
В аналогичной ситуации - насмешка над классом, который используется внутри другого класса, который я пытаюсь протестировать, - я обнаружил, что это работоспособное решение:
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
Я бы подумал, что есть лучший способ сделать это - то есть я не мог найти способ передать желаемое возвращаемое значение для повторного использования - но пока это кажется достаточно хорошим. Я новичок в издевательствах / фабриках, и я хотел бы услышать о любых альтернативных методах.
Редактировать:
Хорошо, так что не так похоже.
Я нашел лучший способ использования RSpec mock, благодаря прекрасному объяснению в RSpec Google Group:
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