i want to make a method inside a module (for grouping reason) that can be called as a module.method, something like this:
helpers do
module UserSession
def logged_in?
not session[:email].nil?
end
def logout!
session[:email] = nil
end
end
end
but when i try to call it using UserSession.logged_in? it said that logged_in is not UserSession's method
undefined method `logged_in?' for UserSession:Module
when i move the method as UserSession's method:
helpers do
module UserSession
def self.logged_in?
not session[:email].nil? # error
end
def self.logout!
session[:email] = nil
end
end
end
it gives an error, that i could not access the session variable
undefined local variable or method `session' for UserSession:Module
what is the best solution for this problem?
You can use a different convention for the helpers method.
module UserSession
def logged_in?
not session[:email].nil?
end
def logout!
session[:email] = nil
end
end
helpers UserSession
get '/foo' do
if logged_in?
'Hello you!'
else
'Do I know you?'
end
end
The module definition can of course be in another (required) file.
Behind the scenes, helpers <Module> is doing an include, but not simply into the Sinatra application sub-class you are using for your app. The include needs to be made compatible with how get, post etc work their magic, and helpers does that for you.
nevermind, i found the answer, i have tried define_method('UserSession.logged_in?') also, but no luck
last thing i've tried is:
# outside helpers
class UserSession
##session = nil
def initialize session
##session ||= session
end
def self.check
throw('must be initialized first') if ##session.nil?
end
def self.logged_in?
self.check
not ##session[:email].nil?
end
def self.logout
self.check
##session.delete :email
end
end
but something must be called first
before // do
UserSession.new session
end
then it can be used as desired:
get '/' do
if UserSession.logged_in?
# do something here
end
end
Related
I have a class that loads a collection and loads items into a collection based on a certain criteria
require_relative 'thing'
class Someclass
def self.tings
things=load_things()
end
def self.select_things(something)
return things.select { |thing| thing.some_property == something }
end
end
I would like to use method_missing instead of the direct defintions
require_relative 'thing'
class Someclass
def self.method_missing(method, *args)
things=load_things()
if method == 'select_things'
self.send("things.select { |thing| thing.some_property == args }")
end
end
end
However, this approach doesn't work and method_missing just outputs the code string. Is there a proper way to call a code from method_missing?
Thank you very much everyone in advance
There are two issues with your method_missing implementation:
The method name is given as a symbol, not as a string:
def self.method_missing(method, *args)
if method == :select_things
# ...
end
end
You have to call super if you don't process the message yourself:
def self.method_missing(method, *args)
if method == :select_things
# ...
else
super
end
end
If you don't call super your object is going to swallow any message without ever raising a NoMethodError and you'll have a very hard time understanding why your code isn't working.
In addition, you should also implement respond_to_missing? to return true for the messages you are responding to, e.g.:
def self.respond_to_missing?(method, include_all = false)
[:select_things].include?(method) || super
end
The above gives you:
Someclass.respond_to?(:select_things) #=> true
In Rails we can define a class like:
class Test < ActiveRecord::Base
before_initialize :method
end
and when calling Test.new, method() will be called on the instance. I'm trying to learn more about Ruby and class methods like this, but I'm having trouble trying to implement this in plain Ruby.
Here's what I have so far:
class LameAR
def self.before_initialize(*args, &block)
# somehow store the symbols or block to be called on init
end
def new(*args)
## Call methods/blocks here
super(*args)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
I'm trying to figure out where to store the blocks in self.before_initialize. I originally tried an instance variable like #before_init_methods, but that instance variable wouldn't exist in memory at that point, so I couldn't store or retrieve from it. I'm not sure how/where could I store these blocks/procs/symbols during the class definition, to later be called inside of new.
How could I implement this? (Either having before_initialize take a block/proc/list of symbols, I don't mind at this point, just trying to understand the concept)
For a comprehensive description, you can always check the Rails source; it is itself implemented in 'plain Ruby', after all. (But it handles lots of edge cases, so it's not great for getting a quick overview.)
The quick version is:
module MyCallbacks
def self.included(klass)
klass.extend(ClassMethods) # we don't have ActiveSupport::Concern either
end
module ClassMethods
def initialize_callbacks
#callbacks ||= []
end
def before_initialize(&block)
initialize_callbacks << block
end
end
def initialize(*)
self.class.initialize_callbacks.each do |callback|
instance_eval(&callback)
end
super
end
end
class Tester
include MyCallbacks
before_initialize { puts "hello world" }
end
Tester.new
Left to the reader:
arguments
calling methods by name
inheritance
callbacks aborting a call and supplying the return value
"around" callbacks that wrap the original invocation
conditional callbacks (:if / :unless)
subclasses selectively overriding/skipping callbacks
inserting new callbacks elsewhere in the sequence
... but eliding all of those is what [hopefully] makes this implementation more approachable.
One way would be by overriding Class#new:
class LameAR
def self.before_initialize(*symbols_or_callables, &block)
#before_init_methods ||= []
#before_init_methods.concat(symbols_or_callables)
#before_init_methods << block if block
nil
end
def self.new(*args, &block)
obj = allocate
#before_init_methods.each do |symbol_or_callable|
if symbol_or_callable.is_a?(Symbol)
obj.public_send(symbol_or_callable)
else
symbol_or_callable.(obj)
end
end
obj.__send__(:initialize, *args, &block)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
Suppose I have a mailer that sends different emails, but is expected to be called with the same parameters. I want to process those parameters for all mailer actions. So, calling a before_action that would read the parameters sent to the mailer method
/mailers/my_mailer.rb
class MyMailer < ApplicationMailer
before_filter do |c|
# c.prepare_mail # Will fail, because I need to pass `same_param` arguments
# # I want to send the original arguments
# c.prepare_mail(same_param) # How do I get `same_param` here ?
end
def action1(same_param)
# email view is going to use #to, #from, #context
method_only_specific_to_action1
end
def action2(same_param)
# email view is going to use #to, #from, #context
method_only_specific_to_action2
end
private
def prepare_mail(same_params)
#to = same_params.recipient
#from = same_params.initiator
#context = same_params.context
end
end
Then in my controller/service I do somewhere
MyMailer.actionx(*mailer_params).deliver_now
How can I access the same_param arguments list inside the before_action block ?
EDIT :
I want to refactor from
/mailers/my_mailer.rb
class MyMailer < ApplicationMailer
def action1(same_param)
#to = same_params.recipient
#from = same_params.initiator
#context = same_params.context
method_only_specific_to_action1
end
def action2(same_param)
#to = same_params.recipient
#from = same_params.initiator
#context = same_params.context
method_only_specific_to_action2
end
def actionx
...
end
end
And this refactoring
/mailers/my_mailer.rb
class MyMailer < ApplicationMailer
def action1(same_param)
prepare_mail(same_params)
method_only_specific_to_action1
end
def action2(same_param)
prepare_mail(same_params)
method_only_specific_to_action2
end
def actionx
...
end
private
def prepare_mail(same_params)
#to = same_params.recipient
#from = same_params.initiator
#context = same_params.context
end
end
Feels non-optimal (prepare_mail(same_params) duplicated in every action)
Hence what was suggested above
ActionMailer uses the AbstractController::Callbacks module. I tried it and it seems to work for me.
The code
class MyMailer < ApplicationMailer
def process_action(*args)
# process the args here
puts args
super
end
def some_mail(*args)
end
end
MyMailer.some_mail(1, 2) #=> prints ['some_mail', 1, 2]
The documentation
UPDATE
If you're using Rails 5.1, you can have a look at ActionMailer::Parameterized
Solution1:
I would suggest you use this if you do not care about the format
MyMailer.generic("actionx", *mailer_params).deliver_now
def generic(actiontype, *mailer_params)
# custom logic goes here to construct the to, from, etc.,
# new_options from custom logic
self.send(actiontype, new_options)
end
alternative solution below using method_missing from the parent controller
Its not right to put your logic there, but if you still want to do it, you can use the method_missing to put your logic there and skip the action1 and action2 methods.
Original method_missing from action_mailer which can be used as a reference:
def method_missing(method_name, *args)
if action_methods.include?(method_name.to_s)
MessageDelivery.new(self, method_name, *args)
else
super
end
end
https://github.com/rails/rails/blob/c8a18aaf44a84f1ef0df007aa3f8446752dc327d/actionmailer/lib/action_mailer/base.rb#L561-L567
Based on Sairam's answer I though of the following but that feels a bit weird, can it not be done with before_action callback ??
class MyMailer < ApplicationMailer
# Simulation of before_action callback that would support passing the *args to the callback method
def self.method_missing(method_name, *args)
method_name = :"#{method_name.to_s}_headers_prefilled"
if action_methods.include?(method_name)
mailer = MyMailer.generic(*args) # The before_action callback that passes *args
mailer.send(method_name, *args) # The action itself
else
super
end
end
def generic(*mailer_params)
# custom logic goes here to construct the headers to, from, etc.,
end
def action1_headers_prefilled(mailer_params)
# Logic only relevant for action1
end
Also I lose all the cool stuff from before_action (passing an only or except array, etc.)
I have the following class:
module APIWrapper
include HTTParty
BASE_URI = 'https://example.com/Api'
def self.const_missing(const_name)
anon_class = Class.new do
def self.method_missing method_name, *params
params = {
'Target' => const_name.to_s,
'Method' => method_name.to_s,
}
APIWrapper.call_get params
end
end
end
def self.call_get(params)
get(APIWrapper::BASE_URI, {:query => params})
end
def self.call_post(params)
post(APIWrapper::BASE_URI, params)
end
end
I want to be able to make a call to my wrapper like this:
APIWrapper::User::getAll
I'm getting a stack level too deep error:
1) Error:
test_User_getAll(APITest):
SystemStackError: stack level too deep
api_test.rb:16
What am I doing wrong?
After using the keyword def, a new scope is created, so the issue here is that the const_name variable is no longer in scope inside the body of the method_missing method.
You can keep the variable in scope by using blocks like so:
def self.const_missing(const_name)
anon_class = Class.new do
define_singleton_method(:method_missing) do |method_name, *params|
params = {
'Target' => const_name.to_s,
'Method' => method_name.to_s,
}
APIWrapper.call_get params
end
end
end
You might want to also set the constant to the anonymous class you just created:
anon_class = Class.new do
...
end
const_set const_name, anon_class
The problem is that const_name is recursively calling method_missing. When you pass a block to Class.new the block is evaluated in the scope of the class. (See the docs)
Methods do not see any local outside variables. In your case it is const_name, which is triggering method_missing recursively.
name = "Semyon"
def greet
puts "hello, #{name}!"
end
greet # undefined local variable or method ‘name’
You can name anonymous modules (and classes) in Ruby using const_set, and from there you can easily see the name. I'd also not recommend defining new methods for every class, this is what modules are for. Here is my short, self-contained example:
module Greeting
module Base
def method_missing(word)
Greeting.greet word, self.name.split("::").last
end
end
def self.greet(word, name)
puts "#{word}, #{name}!"
end
def self.const_missing(name)
const_set name, Module.new.extend(Base)
end
end
Greeting::Semyon.hello # hello, Semyon!
I'm pretty curious about how this thing works.
after require 'sinatra'
then I can invoke get() in the top level scope.
after digging into the source code, I found this get() structure
module Sinatra
class << self
def get
...
end
end
end
know the class << self is open up the self object's singleton class definition and add get() inside, so it starts to make sense.
But the only thing left I can't figure out is it's within module Sinstra, how could get() be invoked without using Sinatra:: resolution operation or something?
It is spread out in a few places, but if you look in lib/sinatra/main.rb, you can see this line at the bottom:
include Sinatra::Delegator
If we go into lib/sinatra/base.rb we see this chunk of code around like 1470.
# Sinatra delegation mixin. Mixing this module into an object causes all
# methods to be delegated to the Sinatra::Application class. Used primarily
# at the top-level.
module Delegator #:nodoc:
def self.delegate(*methods)
methods.each do |method_name|
define_method(method_name) do |*args, &block|
return super(*args, &block) if respond_to? method_name
Delegator.target.send(method_name, *args, &block)
end
private method_name
end
end
delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout,
:before, :after, :error, :not_found, :configure, :set, :mime_type,
:enable, :disable, :use, :development?, :test?, :production?,
:helpers, :settings
class << self
attr_accessor :target
end
self.target = Application
end
This code does what the comment says: if it is included, it delegates all calls to the list of delegated methods to Sinatra::Application class, which is a subclass of Sinatra::Base, which is where the get method is defined. When you write something like this:
require "sinatra"
get "foo" do
"Hello World"
end
Sinatra will end up calling the get method on Sinatra::Base due to the delegation it set up earlier.
I haven't looked at the source of Sinatra, but the gist of it should be something like
>> module Test
.. extend self
.. class << self
.. def get; "hi";end
.. end
.. end #=> nil
>> include Test #=> Object
>> get #=> "hi"