Can I include module from another file using if else condition - ruby-on-rails

Currently in my application I have one helper.rb (Helper module is defined in this file) which is included in my controller.rb file like this:
class Controller
before_action :authenticate_user!
include Helper
Problem is that I need to define one more module e.g. Helper2 and I don't know how to include them using if condition and I don't know if even this solution is possible.
example what I want to do:
class Controller
before_action :authenticate_user!
if variable = 1
include Helper
else
include Helper2
end
Thx for answers!

YAGNI.
There are better ways.
The easist way to make the behavior customizable is to just have a set of methods that can be overridden by classes that consume the module:
module Greeter
def initialize(name)
#name = name
end
def salution
"Hello"
end
def hello
"#{salution}!, my name is #{#name}"
end
end
class Person
include Greeter
end
puts Person.new('Bob').hello # Hello!, my name is Bob
class Dog
include Greeter
def salution
"Woof"
end
end
puts Dog.new('Laika').hello # Woof!, my name is Laika
For more complex tasks there is the "macro method" pattern you'll see all over in Ruby:
module Configurable
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
#options ||= {}
end
end
module ClassMethods
def configure(**kwargs)
#options.merge!(kwargs)
end
def options
#options
end
end
end
class Foo
include Configurable
configure(bar: :baz)
end
puts Foo.options.inspect
# {:bar=>:baz}
This is simply a class method that defines class variables / class instance variables, defines methods or whatever you need to be done. For example these very simplefied API clients:
class Client
include HTTParty
format :json
def answers
self.class.get('/answers')
end
end
class StackoverflowClient < Client
base_uri 'https://stackoverflow.com'
end
class SoftwareEngineeringClient < Client
base_uri 'https://softwareengineering.stackexchange.com'
end

Related

Is it okay to call a private method of a parent class's subclass from a module which is included in the parent class in rails?

Is it okay to call a private method of a parent class's subclass from a module which is included in the parent class especially when it concerns ApplicationController, Controllers and lib modules in Rails?
Consider if required to change the controller name the method name to reflect the model name(to Article) change.
I feel this is really bad coding and wanted to know what community thinks about this
Example from a Rails Application:
/lib/some_module.rb
module SomeModule
include SomeModuleResource
def filtering_method
calling_method
end
def calling_method
fetch_object
end
end
/lib/some_module_resource.rb
module SomeModuleResource
def fetch_object
note
end
end
/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include SomeModule
before_action :filtering_method
end
/app/controllers/notes_controller.rb
class NotesController < ApplicationController
def show
end
private
def note
#note ||= Note.find(param[:id]))
end
end
I'm of the opinion that this is not necessary bad, although when you expect a certain interface (methods, variables, etc.) from the class that includes the module I would add the following:
module SomeModuleResource
def fetch_object
note
end
private
def note
raise NotImplementedError
end
end
This way, when #note is called without implementing it (because you forgot it was needed or whatever) a NotImplementedError is raised.
Another option is to work around it and create a more general solution. For example, if all controllers behave the same way you described above you can do the following:
module SomeModuleResource
def fetch_object
note
end
private
def note
klass = params[:controller].classify.constantize
instance = klass.find(params[:id])
var_name = "##{klass.underscore}"
instance_variable_set(var_name, instance) unless instance_variable_get(var_name)
end
end
You could also create a class helper method like before_action so that you can pass your own implementation.
module SomeModule
include SomeModuleResource
def self.included(base)
base.extend(ClassMethods)
end
def filtering_method
calling_method
end
def calling_method
fetch_object
end
module ClassMethods
def custom_before_action(&block)
define_method(:note, &block)
private :note
before_action :filtering_method
end
end
end
Now you can use custom_before_filter { #note ||= Note.find(params[:id]) } in every controller (after including).
The above is just to present you with ideas. I'm sure you could find better solution to the problem, but this hopefully points you in the right direction.
See: Alternatives to abstract classes in Ruby?. Or search for abstract classes in Ruby and you'll find more on this subject.

Define options with class method in class like Action Mailer does

I'd like to have a module which can be included in class, and allow to set options for like this:
class MyService
include Healthcheck
healthcheck_id 'foobar'
end
And module will looks something like:
module Healthcheck
extend ActiveSupport::Concern
included do
def self.healthcheck_id(value)
# What do I do here?
end
end
end
The question is: how do I store this value which was passed as argument, so that I can use it later on?
Maybe some context might help here, I was inspired by Action Mailer:
class ExampleMailer < ActionMailer::Base
default from: "no-reply#example.com"
end
In the example above class method(?) default is accepting hash with arguments, and apparently from is used by Action Mailer when email is being sent.
The pb with #hieu-pham's solution is that you can't define different healthcheck_id values for different classes:
class MyService1
include Healthcheck
healthcheck_id 'foobar_1'
def foo
puts healthcheck_id_value
end
end
class MyService2
include Healthcheck
healthcheck_id 'foobar_2'
def foo
puts healthcheck_id_value
end
end
MyService1.new.foo # foobar_2
MyService2.new.foo # foobar_2
A better solution would be:
module Healthcheck
extend ActiveSupport::Concern
included do
class_attribute :healthcheck_id_value
def self.healthcheck_id(value)
self.healthcheck_id_value = value
end
def self.foo
healthcheck_id_value
end
end
end
class MyService1
include Healthcheck
healthcheck_id 'foobar_1'
end
class MyService2
include Healthcheck
healthcheck_id 'foobar_2'
end
MyService1.foo # foobar_1
MyService2.foo # foobar_2
You can use class variable to do it, so the code would be:
module Healthcheck
extend ActiveSupport::Concern
included do
def self.healthcheck_id(value)
##healthcheck_id_value = value
end
class_eval do
def healthcheck_id_value
self.class.class_variable_get(:##healthcheck_id_value)
end
end
end
end
So from now on you can access healthcheck_id_value, for example:
class MyService
include Healthcheck
healthcheck_id 'foobar'
def foo
puts healthcheck_id_value
end
end
Let's call MyService.new.foo, it will print 'foobar'
Store it in a class variable
##arguments_passed = value
You can look at ActionMailer::Base source code
It uses (class_attribute :default_params)

Creating a module for raising class-specific errors

In my rails projects, I often use this sort of behavior in my classes and models:
class Whatever
class WhateverError < StandardError; end
def initialize(params={})
raise WhateverError.new("Bad params: #{params}") if condition
# actual class code to follow
end
end
The trouble is, this is both hugely repetitive and fairly verbose. I'd love it if I could just do this whenever I need to raise a class-specific error:
class ErrorRaiser
include ClassErrors
def initialize(params={})
error("Bad params: #{params}") if condition
error if other_condition # has default message
# actual class code to follow
end
def self.class_method
error if third_condition # class method, behaves identically
end
end
I'm having major trouble creating such a module. My sad early attempts have tended to look something like the below, but I'm pretty confused about what's available within the scope of the module, how to dynamically create classes (within methods?) or whether I have straightforward access to the "calling" class at all.
My basic requirements are that error be both a class method and an instance method, that it be "namespaced" to the class calling it, and that it have a default message. Any thoughts/help? Is this even possible?
module ClassErrorable
# This and the "extend" bit (theoretically) allow error to be a class method as well
module ClassMethods
def self.error(string=nil)
ClassErrorable.new(string).error
end
end
def self.included(base)
set_error_class(base)
base.extend ClassMethods
end
def self.set_error_class(base)
# I'm shaky on the scoping. Do I refer to this with # in a class method
# but ## in an instance method? Should I define it here with # then?
##error_class = "##{base.class}Error".constantize
end
def self.actual_error
# This obviously doesn't work, and in fact,
# it raises a syntax error. How can I make my
# constant a class inheriting from StandardError?
##actual_error = ##error_class < StandardError; end
end
def initialize(string)
#string = string || "There's been an error!"
end
def error(string=nil)
raise ##actual_error.new(string)
end
end
How about something like this (written in pure Ruby; it could be refactored to use some Rails-specific features like .constantize):
module ClassErrorable
module ClassMethods
def error(message = nil)
klass = Object::const_get(exception_class_name)
raise klass.new(message || "There's been an error!")
end
def exception_class_name
name + 'Error'
end
end
def self.included(base)
base.extend ClassMethods
Object::const_set(base.exception_class_name, Class.new(Exception))
end
def error(message = nil)
self.class.error(message)
end
end

Call a class method with a multi level Module structure in Ruby

I have some modules to be included in my controller classes. These modules define before_filter:
module BasicFeatures
def filter_method
...
end
def self.included(base)
base.before_filter(:filter_method)
...
end
end
module AdvancedFeatures
include BasicFeatures
...
end
And the classes:
class BasicController < ApplicationController
include BasicFeatures
end
class AdvancedController < ApplicationController
include AdvancedFeatures
end
When BasicFeatures module is included in AdvancedFeatures module, there are no before_filter methods in it.
The AdvancedController didn't get the before_filter call.
I need both my controllers to get the before_filter without any code duplication. I don't know if I am using the best approach so, I'm open to any suggestion.
This is why ActiveSupport::Concern was created.
module BasicFeatures
extend ActiveSupport::Concern
included do
before_filter :require_user
end
def this_is_an_instance_method
'foo'
end
module ClassMethods
def this_is_a_class_method
'bar'
end
end
end
class SomeClass
include BasicFeatures
end
SomeClass.new.this_is_an_instance_method #=> 'foo'
You can also nest them — that is, create concerns that include concerns — and everything will work as expected. And here are the docs.
You can try this. Instead of including the module in AdvancedFeatures, You can include the BasicFeatures module on the class including AdvancedFeatures
module BasicFeatures
def filter_method
#code....
end
#some others basic methods...
def self.included(base)
base.before_filter(:filter_method)
#some other class method calls
end
end
module AdvancedFeatures
def self.included klass
klass.class_eval do
include BasicFeatures
end
end
#some advanced methods
end

Initialize a Ruby class depending on what modules are included

I'm wondering what is the best way to initialize a class in ruby depending on modules included. Let me give you an example:
class BaseSearch
def initialize query, options
#page = options[:page]
#...
end
end
class EventSearch < BaseSearch
include Search::Geolocalisable
def initialize query, options
end
end
class GroupSearch < BaseSearch
include Search::Geolocalisable
def initialize query, options
end
end
module Search::Geolocalisable
extend ActiveSupport::Concern
included do
attr_accessor :where, :user_location #...
end
end
What I don't want, is having to initialize the :where and :user_location variables on each class that include the geolocalisable module.
Currently, I just define methods like def geolocalisable?; true; end in my modules, and then, I initialize these attributes (added by the module) in the base class:
class BaseSearch
def initialize query, options
#page = options[:page]
#...
if geolocalisable?
#where = query[:where]
end
end
end
class EventSearch < BaseSearch
#...
def initialize query, options
#...
super query, options
end
end
Is there better solutions? I hope so!
Why not override initialize in the module? You could do
class BaseSearch
def initialize query
puts "base initialize"
end
end
module Geo
def initialize query
super
puts "module initialize"
end
end
class Subclass < BaseSearch
include Geo
def initialize query
super
puts "subclass initialize"
end
end
Subclass.new('foo') #=>
base initialize
module initialize
subclass initialize
Obviously this does require everything that includes your modules to have an initialize with a similar signature or weird stuff might happen
See this code :
module Search::Geolocalisable
def self.included(base)
base.class_eval do
attr_accessor :where, :user_location #...
end
end
end
class EventSearch < BaseSearch
include Search::Geolocalisable
end

Resources