How to use the same class name over multiple modules?
I've a main ApplicationService
# /app/services/application_service.rb
class ApplicationService; end
# /app/services/processing/queue/application_service.rb
module Processing
module Queue
class ApplicationService < ApplicationService; end
end
end
# /app/services/processing/callback/application_service.rb
module Processing
module Callback
class ApplicationService < ApplicationService; end
end
end
How to get rails to not be confused and to know to use /app/services/application_service.rb
All my services in /app/services/processing/queue/ have /app/services/processing/queue/application_service.rbas parent class.
Use the FQN (fully-qualified name) ::ApplicationService to refer to your top-level class.
module Processing
module Callback
class ApplicationService < ::ApplicationService; end
end
end
Related
I have a PaymentManager service:
/payment_manager/
- online_payment_creator.rb
- manual_payment_creator.rb
- payment_splitter.rb
Code for 3 services:
module PaymentManager
class ManualPaymentCreator < ApplicationService
def call
# do stuff in transaction
PaymentSplitter.call(a, b, c)
end
end
end
module PaymentManager
class OnlinePaymentCreator < ApplicationService
# do stuff in transaction
PaymentSplitter.call(a2, b2, c2)
end
end
module PaymentManager
class PaymentSplitter < ApplicationService
def call(x, y, z)
# do very dangerous stuff
end
end
end
So, the PaymentSplitter essentially splits a payment across multiple invoices, and must be done only inside either of the other 2 services, but should never be called on its own (from a controller or in the console, etc).
I normally would make a private method inside ManualPaymentCreator but because it's needed in BOTH services, I don't know how to do that, as I can't use a simple private instance method without duplication.
Perhaps you are looking for inheritance and protected methods
class Foo < ApplicationService
def public_method
end
protected
#everything after here is private to this class and its descendants
def some_method_to_be_shared_with_descendants
#I am only accessible from this class and from descendants of this class
...
end
private
#Everything after here is private to this class only
def some_private_methods_that_are_only_accessible_from_this_class
end
...
end
Then descendant classes like so
class Bar < Foo
def do_something
# can access protected methods from Foo class here as native methods
some_res = some_method_to_be_shared_here
# do something with some_res
end
end
So you should descend your other 2 classes from PaymentSplitter and set the shared methods as protected
Perhaps a private class contained in a module might work better, eg.
module PaymentSplitting
def self.included(klass)
raise 'Error' unless klass.in?([ManualPaymentCreator, OnlinePaymentCreator])
end
class PaymentSplitter
end
private_constant :PaymentSplitter
end
This should allow you to freely reference PaymentSplitter in any class which includes PaymentSplitting, and you can only include PaymentSplitting in ManualPaymentCreator or OnlinePaymentCreator. Referencing PaymentSplitting::PaymentSplitter will throw a NameError: private constant exception.
i have a controller inside payment folder named gateway_controller.rb
class Payment::GatewayController < PaymentController
include Payment::GatewayHelper::Handler
def set_handler
#handler = StandardGatewayInterface.get_handler("test")
end
end
And i have their helper files in helpers/payment/gateway_helper/handler.rb
which looks like
module Payment::GatewayHelper::Handler
class StandardGatewayInterface
def self.get_handler(payment_method)
puts self.descendants
end
# Get a list of subclasses.
def self.descendants
ObjectSpace.each_object(Class).select { |cls| cls < self }
end
end
end
and there is another file in /helpers/payment/test_fort.rb where there is a class that inherits from StandardGatewayInterface.
require_relative './gateway_helper/handler'
class TestFort < StandardGatewayInterface
include Payment::GatewayHelper::Handler
def build_redirect_url(user_id, product, success_url)
puts "===in==="
end
end
when i call StandardGatewayInterface.get_handler("test") it gets inside the helper method, where i call self.descendants which has to return TestFort since it inherits from StandardGatewayInterface.But what iam getting is an empty array.
How will i make this work?
What is a convention for reusing methods between presenters?
For example, say an app has the following presenters
class UserPresenter < BasePresenter
end
class AdminPresenter < BasePresenter
end
class EventPresenter < BasePresenter
end
User and Admin both have avatars. What is the correct way to share an avatar method between the User and Admin presenter?
One solution might be inheriting from an AvatarPresenter
class UserPresenter < AvatarPresenter
end
class AdminPresenter < AvatarPresenter
end
class EventPresenter < BasePresenter
end
class AvatarPresenter < BasePresenter
end
Which works OK in this simple example. But what if things become more complex in the future (e.g., an additional method shared between Admin and Event).
I suppose I'm looking to share Concerns between Presenters. Is this a conventional approach, and if so what would a template implementation look like? All my attempts are raising method not found errors.
What you are looking for is traits. In Ruby this takes the form of module mixins.
module Avatar
def self.included(base)
base.extend ClassMethods
base.class_eval do
# in this block you are operating on the singleton class
# where the module is included
end
end
def an_instance_method_from_avatar
end
module ClassMethods
def a_class_method_from_avatar
end
end
end
class UserPresenter
include Avatar
end
class AdminPresenter
include Avatar
end
This lets us create reusable components that can composed in many different ways. ActiveSupport::Concern takes the pattern above and simplifies it:
module Avatar
# modules can be extended by other modules
extend ActiveSupport::Concern
included do
# in this block you are operating on the singleton class
# where the module is included
end
class_methods do
def a_class_method_from_avatar
end
end
def an_instance_method_from_avatar
end
end
Inheritance (class based) on the other hand should only really be used if an object is a true subtype of its parent. While you could argue that an AdminPresenter is a presenter with an avatar this would lead to a really convoluted class diagram down the road if you need to add other functionality.
I have a model with the following two methods which are required in another model so I thought I'd try sharing them via a concern instead of duplicating the code.
class Region < ActiveRecord::Base
def ancestors
Region.where("lft < ? AND ? < rgt", lft, rgt)
end
def parent
self.ancestors.order("lft").last
end
end
I have created a file in app/models/concerns/sets.rb and my new model reads:
class Region < ActiveRecord::Base
include Sets
end
sets.rb is:
module Sets
extend ActiveSupport::Concern
def ancestors
Region.where("lft < ? AND ? < rgt", lft, rgt)
end
def parent
self.ancestors.order("lft").last
end
module ClassMethods
end
end
Question:
How do I share a method between models when the method references the model such as "Region.where..."
Either by referencing the class of the including model (but you need to wrap the instance methods in an included block):
included do
def ancestors
self.class.where(...) # "self" refers to the including instance
end
end
or (better IMO) by just declaring the method as a class method, in which case you can leave the class itself out altogether:
module ClassMethods
def ancestors
where(...)
end
end
I try to do this:
app/models/my_model.rb:
class MyModel < ActiveRecord::Base
include MyModule
...
end
lib/my_module.rb:
module MyModule
before_destroy :my_func #!
def my_func
...
end
end
but I get an error:
undefined method `before_destroy' for MyModule:Module
How can I correct it.
Also I'm new to ruby. What type has these "attributes": before_destroy, validates, has_many?
Are they variables or methods or what?
Thanks
before_destroy, validates, etc. are not attributes or anything like that. These are method calls.
In ruby, the body of a class is all executable code, meaning that each line of the class body is executed by the interpeter just like a method body would.
before_destroy :my_func is a usual ruby method call. The method that gets called is before_destroy, and it receives a symbol :my_func as an argument. This method is looked up in the class (or module) in the scope of which it is called.
So moving on to your question, I think now you should understand that when the interpreter loads your module
module MyModule
before_destroy :my_func #!
def my_func
...
end
end
it starts executing its body and searches for the method before_destroy in this module and cannot find one. What you want to do is call this method not on the module, but rather on the class where the module is included. For that we have a common idiom using the Module#included method:
module MyModule
module InstanceMethods
def my_func
...
end
end
def self.included(base)
base.send :include, InstanceMethods
base.before_destroy :my_func
end
end
In lib/my_module.rb, do this:
class MyInheritedClass < ActiveRecord::Base
before_destroy :my_func
def my_func
...
end
end
In app/models/my_model.rb, do this:
class MyModel < MyInheritedClass
...
end
There is no before_destroy filter in the module you are trying to create above. What my code does is creating a class that will inherit from ActiveRecord::Base and that will be your template class which all your other classes can inherit from. The template class also contains all properties of ActiveRecord::Base.
u can correct this by removing before_destroy from MyModule and place it in MyModel instead
before_destroy and other callbacks are only available to classes which extends ActiveRecord::Base, more info here
hope this helps =)