Ruby Language
Finesser
Sök…
Anmärkningar
Förfiningar omfattar lexiskt, vilket innebär att de gäller från den tid de aktiveras (med det using
nyckelordet) tills kontrollen skiftas. Vanligtvis ändras kontrollen i slutet av en modul, klass eller fil.
Apaplackning med begränsad omfattning
Monkey patchings huvudsakliga fråga är att den förorenar det globala räckvidden. Ditt kod fungerar är priset för alla moduler du använder inte kliver på varandras tår. Ruby-lösningen på detta är förfiningar, som i princip är apapatcher i ett begränsat omfång.
module Patches
refine Fixnum do
def plus_one
self + 1
end
def plus(num)
self + num
end
def concat_one
self.to_s + '1'
end
end
end
class RefinementTest
# has access to our patches
using Patches
def initialize
puts 1.plus_one
puts 3.concat_one
end
end
# Main scope doesn't have changes
1.plus_one
# => undefined method `plus_one' for 1:Fixnum (NoMethodError)
RefinementTest.new
# => 2
# => '31'
Moduler med dubbla ändamål (förfining eller globala korrigeringar)
Det är en bra praxis att utjämna fläckar med förfining, men ibland är det trevligt att ladda det globalt (till exempel i utveckling eller testning).
Säg till exempel att du vill starta en konsol, kräva ditt bibliotek och sedan ha de korrigerade metoderna tillgängliga i det globala omfånget. Du kan inte göra detta med förfiningar eftersom using
måste kallas i en klass / moduldefinition. Men det är möjligt att skriva koden på ett sådant sätt att det är dubbelt syfte:
module Patch
def patched?; true; end
refine String do
include Patch
end
end
# globally
String.include Patch
"".patched? # => true
# refinement
class LoadPatch
using Patch
"".patched? # => true
end
Dynamiska förbättringar
Förfiningar har speciella begränsningar.
refine
kan endast användas i en modulomfång, men kan programmeras med send :refine
.
using
är mer begränsad. Det kan bara kallas i en klass / moduldefinition. Det kan fortfarande acceptera en variabel som pekar på en modul och kan åberopas i en slinga.
Ett exempel som visar dessa begrepp:
module Patch
def patched?; true; end
end
Patch.send(:refine, String) { include Patch }
patch_classes = [Patch]
class Patched
patch_classes.each { |klass| using klass }
"".patched? # => true
end
Eftersom using
är så statisk kan det utfärdas en belastningsordning om förädlingsfilerna inte laddas först. Ett sätt att ta itu med detta är att linda in den korrigerade klass / moduldefinitionen i en proc. Till exempel:
module Patch
refine String do
def patched; true; end
end
end
class Foo
end
# This is a proc since methods can't contain class definitions
create_patched_class = Proc.new do
Foo.class_exec do
class Bar
using Patch
def self.patched?; ''.patched == true; end
end
end
end
create_patched_class.call
Foo::Bar.patched? # => true
Att ringa proc skapar den lappade klassen Foo::Bar
. Detta kan försenas tills alla koder har laddats.