Ruby - method_missing
-
11-12-2019 - |
Question
Je suis en train de mettre en œuvre un method_missing pour la conversion $ à d'autres monnaies, comme en fait 5.dollars des rendements de 5, 5.yen aurait rendement de 0,065 5.euro 6.56 et ainsi de suite.Ce que je peux faire maintenant.Maintenant, j'ai besoin de le mettre en œuvre mais faire 5.de dollars.dans(:yen) par exemple.
C'est ce que j'ai en ce moment:
class Numeric
@@currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019}
def method_missing(method_id)
singular_currency = method_id.to_s.gsub( /s$/, '')
if @@currencies.has_key?(singular_currency)
self * @@currencies[singular_currency]
else
super
end
end
end
Quelqu'un peut-il expliquer comment je peux faire cela?
PS:Je préfèrerais que vous me donnez pas le code, mais une explication, afin que je puisse déterminer sur mon propre comment il est fait.
La solution
Perhaps this will be of more help. It's a working example (note, I'm expecting you to have ActiveSupport [part of Rails] and Ruby 1.9.2+):
require 'rubygems'
# This is allowing us to do the `pluralize` calls below
require 'active_support/inflector'
module Currency
CONVERSION_TABLE = { dollars: { dollars: 1, euros: 0.75 }, euros: { dollars: 1.3333334, euros: 1 } }.freeze
attr_accessor :currency
def method_missing(method_name, *args, &block)
# standardize on pluralized currency names internally so both singular
# and plural methods are handled
method_name = method_name.to_s.pluralize.to_sym
# Use the "from" keys in the conversion table to verify this is a valid
# source currency
if CONVERSION_TABLE.key?(method_name)
@currency = method_name
self # return self so a call to `1.dollar` returns `1` and not `:dollars`
else
super
end
end
# Convert `self` from type of `@currency` to type of `destination_currency`, mark the result with
# the appropriate currency type, and return. Example:
def to(destination_currency)
# Again, standardize on plural currency names internally
destination_currency = destination_currency.to_s.pluralize.to_sym
# Do some sanity checking
raise UnspecifiedSourceCurrency unless defined?(@currency)
raise UnsupportedDestinationCurrency unless CONVERSION_TABLE.key?(destination_currency)
# Do the actual conversion, and round for sanity, though a better
# option would be to use BigDecimal which is more suited to handling money
result = (self * CONVERSION_TABLE[@currency][destination_currency]).round(2)
# note that this is setting @currency through the accessor that
# was created by calling `attr_accessor :currency` above
result.currency = destination_currency
result
end
end
class Numeric
# Take all the functionality from Currency and mix it into Numeric
#
# Normally this would help us encapsulate, but right now it's just making
# for cleaner reading. My original example contained more encapsulation
# that avoided littering the Numeric clas, but it's harder for a beginner
# to understand. For now, just start here and you will learn more later.
include Currency
end
p 5.euros.to(:dollars) #=> 6.67
p 0.25.dollars.to(:euro) #=> 0.19
p 1.dollar.to(:euros).to(:dollar) #=> 1.0
Autres conseils
monnaie ajoutée 'dollar' et class Numeric
@@currencies = {'dollar' => 1, 'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019}
def method_missing(method_id)
singular_currency = method_id.to_s.gsub(/s$/, '')
if @@currencies.has_key?(singular_currency)
self * @@currencies[singular_currency]
else
super
end
end
def in(currency)
singular_currency = currency.to_s.gsub(/s$/, '')
self / @@currencies[singular_currency]
end
end
Ce n'est plus un problème mathématique de calcul un.
Chacun des @@currencies
les valeurs de hachage est normalisé à 'dollars':leurs unités sont yen/dollar, euro/dollar, roupie/dollar.Pour 5.euro.in(:yen)
, vous avez seulement besoin de diviser euro/dollar par yen/dollar pour exprimer la réponse en euro, en Yen.
Pour le calcul de ce à l'aide de Ruby, vous quittez le method_missing
méthode inchangée et mise à jour de la constante de classe pour inclure 'dollar' => 1
.Ajouter un Numeric#in
méthode avec une seule ligne de calcul pour résoudre ce problème.Que le calcul doit s'appliquer à la division dans la séquence correcte d'un nombre à virgule flottante.
Pour 5.euro.in(:yen)
exemple, rappelez-vous que 5.euro est calculé d'abord, mais qui aura des unités de euro/dollar.L' dans(:yen) la méthode qui vient suivant doit être appliqué à l'inverse de ce nombre.Cela vous donnera un certain nombre d'unités dans yen/euro, la réciproque de votre résultat souhaité.
Ne serait-il pas de définir une méthode appelée in
qui a envoyé le symbole de paramètre de retour à la self
?
irb(main):057:0> 5.dollar.in(:euro)
=> 6.46
irb(main):065:0> 5.euro.in(:dollar)
=> 6.46 # Which is wrong, by the way
Donc, pas tout à fait, parce que vous ne savez pas ce que le montant représente actuellement--votre method_missing
suppose que tout est en dollars, même si elle ne l'est pas.
C'est pourquoi il y a la l'argent gem :)
Plutôt que d'utiliser method_missing
ici, il serait plus facile de faire une itération sur chaque devises et de définir le singulier et le pluriel de méthodes de déléguer à votre méthode de conversion.
Je suis en supposant que vous avez ActiveSupport ici par souci de commodité.Vous pourriez faire tout cela sans, mais des choses comme constantize
et les préoccupations de la rendre plus facile.
module DavesMoney
class BaseMoney
# your implementation
end
class DollarConverter < BaseMoney
def initialize(value)
@value = value
end
def to(:currency)
# implemented in `BaseMoney` that gets extended (or included)
end
end
end
module CurrencyExtension
extend ActiveSupport::Concern
SUPPORTED_CURRENCIES = %w{ dollar yen euro rupee }
included do
SUPPORTED_CURRENCIES.each do |currency|
define_method :"#{currency}" do
return "#{currency}_converter".constantize.new(self)
end
alias :"#{currency.pluralize}" :"#{currency}"
end
end
end
# extension
class Numeric
include CurrencyExtension
end
Mon approche, basée sur l'acceptation de la limite du problème posé (étendre un method_missing mise en œuvre sur le Numérique, même si comme @coreyward indique c'est vraiment pas la bonne approche pour rien pas un problème) est comme suit:
La compréhension que 5.euros.in(:yen)
peut être traduite:
eur = 5.send(:euros)
eur.send( :in, yen )
ce qui est essentiellement ce qui se passe est que nous allons envoyer le message d'euros pour le Numérique 5 et l'envoi du in
méthode pour le résultat Numérique de 5.d'euros avec un paramètre de :yen.
Dans method_missing vous devez répondre à la euros
d'appel et de retour avec la suite de l'une d'euros de dollars de la conversion, puis (également dans method_missing) pour répondre à la in
d'appels avec les résultats de la conversion des dollars (de l'appel précédent) pour le symbole transmis comme paramètre à la in
appel.Qui va renvoyer la valeur correcte.
Bien sûr, vous pouvez convertir vers/à partir de n'importe quelle devise vous voulez tant que vos facteurs de conversion sont corrects avec le givens pour ce problème particulier, la conversion vers/à partir de dollars semblait le plus sensible.
Je fais aussi ce cours et j'ai vu quelques exemples de la manière d'accomplir la tâche.À un moment donné, Self.Send a été mentionné et je crois que quelqu'un d'autre l'a mis en œuvre aussi mais j'ai trouvé cette solution pour travailler pour moi:
Voici ce que j'ai fait ...
class Numeric @@currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019, 'dollar' => 1} def method_missing(method, *arg) singular_currency = method.to_s.gsub(/s$/,'') if @@currencies.has_key?(singular_currency) self * @@currencies[singular_currency] else super end end def in(arg) singular_currency = arg.to_s.gsub(/s$/,'') if @@currencies.has_key?(singular_currency) self * @@currencies[singular_currency] end end end puts "5.euro = "+5.euro.to_s puts "5.euros = "+5.euros.to_s puts "5.dollars.in(:euros) = "+5.dollars.in(:euros).to_s puts "10.euros.in(:rupees) = "+10.euros.in(:rupees).to_s
- Ajouter "Dollar '=> 1" dans les devises
- Ajoutez un nouvel argument dans la méthode_missing méthode ", * args"
- Ajouter une nouvelle méthode "dans (arg)" dans la classe numérique
- Cette méthode se multiplie par la monnaie spécifiée par l'argument "arg"
Tout d'abord, installer mes unités de la bibliothèque: gem install sy
.Ensuite, définir:
require 'sy'
Money = SY::Quantity.dimensionless #=> #<Quantity:Money>
USD = SY::Unit.standard of: Money #=> #<Unit:USD of Money >
YEN = SY::Unit.of Money, amount: 0.013 #=> #<Unit:YEN of Money >
EUR = SY::Unit.of Money, amount: 1.292 #=> #<Unit:EUR of Money >
INR = SY::Unit.of Money, amount: 0.019 #=> #<Unit:INR of Money >
Et maintenant vous pouvez calculer:
10 * 10.usd => #<Magnitude: 100 >
100.yen.in :usd #=> #<Magnitude: 1.3 >
1.eur + 1.usd #=> #<Magnitude: 2.29 >
Vous pouvez également définir
CENT = SY::Unit.of Money, amount: 0.01.usd
EUROCENT = SY::Unit.of Money, amount: 0.01.eur
Et puis
12.usd + 90.cent #=> #<Magnitude: 12.9 >