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
Related
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
Why when I do self.method from a class, I get an undefined method `my_method' for MyModule::MyOtherModule::MyClass:Class
module MyModule
module OtherModule
class MyClass < Base
def my_method
end
def self.my_self_method
my_method
end
end
end
end
I call my_self_method with send from an herited [sic] class:
class Base
class << self
my_method(method_name)
send("my_self_#{method_name}")
end
end
end
I don't understand it.
In your code, you're defining one instance method (my_method), and one class method (my_self_method).
This means you can call:
MyClass.my_self_method
or
MyClass.new.my_method
If you want my_method to be callable from my_self_method, you could define it as:
def self.my_method
...
end
Then the following would be available:
def self.my_self_method
my_method
end
Here's another alternative. There's a comment that suggests it's bad practice to call new.my_method from within a class method, but I've seen a pattern that applies this that I find quite idiomatic, for example:
class MyClass
def self.run(the_variables)
new(the_variables).process
end
def initialize(the_variables)
# setup the_variables
end
def process
# do whatever's needed
end
end
This allows a simple entry point of MyClass.run(the_variables). If your use case seems suitable, a similar pattern for you would be:
module MyModule
module OtherModule
class MyClass < Base
def my_method
end
def self.my_self_method
new.my_method
end
end
end
end
I'm sure there's scope to disagree with this pattern, and would be interested to hear others' opinions in the comments.
Hope this helps clear a few things up #N.Safi.
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.
I'm trying to understand some Ruby metaprogramming concepts.
I think I understand classes, objects, and metaclasses. Unfortunately, I'm very unclear on exactly what happens with included Modules with respect to their instance/'class' variables.
Here's a contrived question whose solution will answer my questions:
Suppose I'm writing my own crappy Rails "validates" method, but I want it to come from a mixed-in module, not a base class:
module MyMixin
# Somehow validates_wordiness_of() is defined/injected here.
def valid?
# Run through all of the fields enumerated in a class that uses
# "validate_wordiness_of" and make sure they .match(/\A\w+\z/)
end
end
class MyClass
include MyMixin
# Now I can call this method in my class definition and it will
# validate the word-ness of my string fields.
validate_wordiness_of :string_field1, :string_field2, :string_field3
# Insert rest of class here...
end
# This should work.
MyMixin.new.valid?
Ok, so how would you store that list of fields from the validate_wordiness_of invocation (in MyClass) in such a way that it can be used in the valid? method (from MyMixin)?
Or am I coming at this all wrong? Any info would be super appreciated!
So here are two alternative ways of doing it:
With "direct" access
module MyMixin
def self.included(base)
base.extend(ClassMethods)
end
def wordy?(value)
value.length > 2
end
module ClassMethods
def validates_wordiness_of(*attrs)
define_method(:valid?) do
attrs.all? do |attr|
wordy?(send(attr))
end
end
end
end
end
class MyClass
include MyMixin
validates_wordiness_of :foo, :bar
def foo
"a"
end
def bar
"asrtioenarst"
end
end
puts MyClass.new.valid?
The downside to this approach is that several consecutive calls to validates_wordiness_of will overwrite each other.
So you can't do this:
validates_wordiness_of :foo
validates_wordiness_of :bar
Saving validated attribute names in the class
You could also do this:
require 'set'
module MyMixin
def self.included(base)
base.extend(ClassMethods)
end
module Validation
def valid?
self.class.wordy_attributes.all? do |attr|
wordy?(self.send(attr))
end
end
def wordy?(value)
value.length > 2
end
end
module ClassMethods
def wordy_attributes
#wordy_attributes ||= Set.new
end
def validates_wordiness_of(*attrs)
include(Validation) unless validation_included?
wordy_attributes.merge(attrs)
end
def validation_included?
ancestors.include?(Validation)
end
end
end
class MyClass
include MyMixin
validates_wordiness_of :foo, :bar
def foo
"aastrarst"
end
def bar
"asrtioenarst"
end
end
MyClass.new.valid?
# => true
I chose to make the valid? method unavailable until you actually add a validation. This may be unwise. You could probably just have it return true if there are no validations.
This solution will quickly become unwieldy if you introduce other kinds of validations. In that case I would start wrapping validations in validator objects.
I am using Ruby v1.9.2 and the Ruby on Rails v3.2.2 gem. I had the following module
module MyModule
extend ActiveSupport::Concern
included do
def self.my_method(arg1, arg2)
...
end
end
end
and I wanted to alias the class method my_method. So, I stated the following (not working) code:
module MyModule
extend ActiveSupport::Concern
included do
def self.my_method(arg1, arg2)
...
end
# Note: the following code doesn't work (it raises "NameError: undefined
# local variable or method `new_name' for #<Class:0x00000101412b00>").
def self.alias_class_method(new_name, old_name)
class << self
alias_method new_name, old_name
end
end
alias_class_method :my_new_method, :my_method
end
end
In other words, I thought to extend the Module class someway in order to add an alias_class_method method available throughout MyModule. However, I would like to make it to work and to be available in all my Ruby on Rails application.
Where I should put the file related to the Ruby core extension of the Module class? Maybe in the Ruby on Rails lib directory?
How should I properly "extend" the Module class in the core extension file?
Is it the right way to proceed? That is, for example, should I "extend" another class (Object, BasicObject, Kernel, ...) rather than Module? or, should I avoid implementing the mentioned core extension at all?
But, more important, is there a Ruby feature that makes what I am trying to accomplish so that I don't have to extend its classes?
You could use define_singleton_method to wrap your old method under a new name, like so:
module MyModule
def alias_class_method(new_name, old_name)
define_singleton_method(new_name) { old_name }
end
end
class MyClass
def my_method
puts "my method"
end
end
MyClass.extend(MyModule)
MyClass.alias_class_method(:my_new_method, :my_method)
MyClass.my_new_method # => "my method"
Answering your comment, you wouldn't have to extend every single class by hand. The define_singleton_method is implemented in the Object class. So you could simply extend the Object class, so every class should have the method available...
Object.extend(MyModule)
Put this in an initializer in your Rails app and you should be good to go...
I found an answer on this website: http://engineering.lonelyplanet.com/2012/12/09/monitoring-our-applications-ruby-methods/
The solution is to use class_eval with a block. That enables using variables from the enclosing scope.
module Alias
def trigger
#trigger = true
end
def method_added(name)
if #trigger
#trigger = false
with_x = "#{name}_with_x"
without_x = "#{name}_without_x"
define_method(with_x) do
"#{send(without_x)} with x"
end
alias_method without_x, name
alias_method name, with_x
end
end
def singleton_method_added(name)
if #trigger
#trigger = false
with_x = "#{name}_with_x"
without_x = "#{name}_without_x"
define_singleton_method(with_x) do
"singleton #{send(without_x)} with x"
end
singleton_class.class_eval do
alias_method without_x, name
alias_method name, with_x
end
end
end
end
class TestAlias
extend Alias
trigger
def self.foo
'foo'
end
trigger
def bar
'bar'
end
end
TestAlias.foo # => 'singleton foo with x'
TestAlias.new.bar # => 'bar with x'
If you don't have singleton_class then you should probably upgrade your version of Ruby. If that's not possible you can do this:
class Object
def singleton_class
class << self
self
end
end
end
The accepted answer was confusing and did not work.
class Module
def alias_class_method(new_name, old_name)
define_singleton_method(new_name, singleton_method(old_name))
end
end
module MyModule
def self.my_method
'my method'
end
end
MyModule.alias_class_method(:my_new_method, :my_method)
MyModule.my_new_method # => "my_method"