Dynamically created method? _one_time_conditions_valid_718? - ruby-on-rails

I'm new to RoR and jumping into a big RoR project. I used railroady to create a diagram of all of the models and controllers. I've noticed that many of the controllers begin with as many as five methods in the form
_one_time_conditions_valid_XXX?
where XXX ranges from 200 to 1116. However, these methods don't appear in the actual code. Are these methods automagically generated by some gem? I can't find a reference to this for anywhere.

Okay so here is your Reference. These methods are defined in ActiveSupport::Callbacks::Callback in a method called #_compile_per_key_options Line 159. It looks like this
def _compile_per_key_options
key_options = _compile_options(#per_key)
#klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def _one_time_conditions_valid_#{#callback_id}?
true if #{key_options}
end
RUBY_EVAL
end
It is then used for supplying data to the before, after and around filters through 2 different methods called #start and #end.
Both #start and #end check for these methods like so
return if key && !object.send("_one_time_conditions_valid_#{#callback_id}?")
From what it looks like the whole purpose of these methods is simply to determine if a callback as been defined and then if so compile the appropriate hooks.
These hooks are about as clear as their names. before hooks run before the defined action and access the data before the action gets it, after hooks run after the defined action and access the data after the action gets it, and around hooks wrap an action and triggers the event they yield. You can even define your own such as:
class Record
include ActiveSupport::Callbacks
define_callbacks :magic
def magic
run_callbacks :magic do
puts "Abracadabra"
end
end
end
class Magician < Record
set_callback :magic, :before, :perform
def perform
puts "The Magician will now perform a Trick"
end
set_callback :magic, :after do |object|
puts "Tada"
end
end
This is clearly shown by
magician = Magician.new
magician.magic
#Output:
# The Magician will now perform a Trick #(before callback)
# Abracadabra #(actual event)
# Tada #(after callback)
This means if your controllers have "as many as five" of these that there are an equal amount of filters in the form of something like before_filter, after_filter, before_action, around_action, etc. (the list of available callbacks is pretty long)

Related

How to skip before filter in model with ruby on rails 5.2?

My application manages a hierarchy of documents. Each document has a hierarchycal index, which is calculated at creation only. File document.rb starts with
class BusinessRule < ActiveRecord::Base
### before filter
before_create :set_hierarchy
and the hierarchy is calculated based on parent and brothers so that self.hierarchy = last_one.next is evaluated in the scope of the parent.
Now, I add the version management feature. Thanks to a new_version method added to the controller, a document is duplicated using the #document.dup method, and then it is saved: the hierarchy is supposed to remain the same, and only the version number needs to be incremented.
Fine.
But the before_create filter is triggered by the save action in the model, and the hierarchy is incremented, which does not fit the requirements.
How can I prevent the before filter in the model from triggering in the case of the new_version action in the controller?
I'm not sure if this is the best way to do this, but I'd do something like this.
class BusinessRule < ActiveRecord::Base
attr_accessor :skip_set_hierarchy
before_action :set_hierarchy, unless: :skip_set_hierarchy
...
end
Now, if you don't want the callback to be triggered, you can set that to true on demand:
def new_version
business_rule = BusinessRule.new business_rule_params
business_rule.skip_set_hierarchy = true
business_rule.save
#this can be refactored a lot (set the skip_set_hierarchy to true inside params, use create instead of new, I made it verbose on purpose to make it clearer)
end
ActiveRecord will skip the callback because skip_set_hierarchy will return true. You don't need to change the rest of the code, since by default it will return nil.
I think this is the good case to use skip_callback method:
BusinessRule.skip_callback(:create, :before, :set_hierarchy)
# your code to create BusinessRule objects without setting hierarchy
# ...
BusinessRule.set_callback(:create, :before, :set_hierarchy)
If you're going to skip/set callbacks quite often you could simplify it using special helping method:
# config/initializers/without_callback.rb
module ActiveSupport::Callbacks::ClassMethods
def without_callback(*args, &block)
skip_callback(*args)
yield
set_callback(*args)
end
end
And you will be able to skip a callback like this:
BusinessRule.without_callback(:create, :before, :set_hierarchy) do
# your code to create BusinessRule objects without setting hierarchy
# ...
end

Start method just once for addition / removal of association elements

I have a Composition model which has a has_and_belongs_to_many :authors.
I need to fire a method after a composition changed its authors, although, since it involves the creation of a PDF file (with the name of the authors), I want to call this method only once, regardless of the number of authors added / removed.
Of course I can add / remove existing authors from the composition, so a before_save / after_save won't work here (somehow it recognizes new authors added to the composition, but not existing ones).
So I tried using after_add / after_remove, but the callbacks specified here will be invoked for every author item added to / removed from the composition.
Is there a way to have a method called only once for every "batch action" of adding / removing items from this kind of relationship?
Here's what a service might look like:
class UpdateCompositionAuthorsService
attr_accessor *%w(
args
).freeze
class << self
def call(args={})
new(args).call
end
end # Class Methods
#======================================================================================
# Instance Methods
#======================================================================================
def initialize(args={})
#args = args
assign_args
end
def call
do_stuff_to_update_authors
generate_the_pdf
end
private
def do_stuff_to_update_authors
# do your 'batch' stuff here
end
def generate_the_pdf
# do your one-time logic here
end
def assign_args
args.each do |k,v|
class_eval do
attr_accessor k
end
send("#{k}=",v)
end
end
end
You would call it something like:
UpdateCompositionAuthorsService.call(composition: #composition, authors: #authors)
I got sick of remembering what args to send to my service classes, so I created a module called ActsAs::CallingServices. When included in a class that wants to call services, the module provides a method called call_service that lets me do something like:
class FooClass
include ActsAs::CallingServices
def bar
call_service UpdateCompositionAuthorsService
end
end
Then, in the service class, I include some additional class-level data, like this:
class UpdateCompositionAuthorsService
SERVICE_DETAILS = [
:composition,
:authors
].freeze
...
def call
do_stuff_to_update_authors
generate_the_pdf
end
...
end
The calling class (FooClass, in this case) uses UpdateCompositionAuthorsService::SERVICE_DETAILS to build the appropriate arguments hash (detail omitted).
I also have a method called good_to_go? (detail omitted) that is included in my service classes, so my call method typically looks like:
class UpdateCompositionAuthorsService
...
def call
raise unless good_to_go?
do_stuff_to_update_authors
generate_the_pdf
end
...
end
So, if the argument set is bad, I know right away instead of bumping into a nil error somewhere in the middle of my service.

How to wrap every Rails ActiveRecord query using an around_action

I would like to create a module that when included, makes every active record query execute as a block that I will wrap.
Specifically I'm using https://github.com/zendesk/active_record_shards to shard to multiple databases. In this scenario we have 10 duplicate databases (same schema but different client instances) and 1 unique database. By default, any calls to our models that belong to the 10 duplicate databases will be wrapped in an application controller around_action that does
ActiveRecord::Base.on_shard(database_name) do
yield
end
However we have a set of models that should only execute queries against this 1 unique database. Rather than execute every ActiveRecord query on those models using something like
ActiveRecord::Base.on_shard('unique_db') { Model.find(id) }
I would rather make a module that when included executes the active record query as a block inside the on_shard method so they always execute agains the unique_db
Let me know if I'm going down the wrong path and should just stick with my helper method which basically just shortens the length
module UniqeDB
def self.exec!
ActiveRecord::Base.on_shard('unique_db') do
yield
end
end
end
Thanks!
Update:
I was able to do something like this, however it overrides every method with (*args, &block) however some methods (like default_scope) only take a block and then it throws exceptions
included do
ActiveRecord::Base.methods.each do |name|
define_singleton_method name do |*args, &block|
ActiveRecord::Base.on_shard('unique_db') do
super(*args, &block)
end
end
end
end
Update: I opened an issue with the gem to see if there is something i'm missing
https://github.com/zendesk/active_record_shards/issues/87
Include the below module in any controller like 'include ExecuteInUniqueDB'
All action inside the controller will only in unique_db
module ExecuteInUniqueDB
def self.included(base)
base.class_eval do
around_action :run_on_unique_db
end
end
def run_on_unique_db(&block)
ActiveRecord::Base.on_shard('unique_db', &block)
end
end

Rails: Extending existing code

I'm a PHP developer and have worked extensively with Laravel. However, I currently need to make small extension to Redmine (a Ruby Issue Tracker Tool) for work.
I'm brand new to Ruby and Rails, so I'm simultaneously trying to get up to speed on the language and the framework.
In general, I'll need to make some migrations which add a few columns to Redmines existing table. Then when various methods are trigged in Redmine (logging time entries, deleting entries, creating projects, etc), I'll need to make a couple API calls, and insert/update the returned data in said columns.
So not terribly complicated, however I'm wondering a few things as I get off the ground:
1) Because I'm extending an existing Rails app, should I be creating a Plugin? or a Gem? It seems Redmine has a 'plugin generator' that provides some boiler plate
2) I'll need to hook into existing Save and Update events in Redmine. From what I understand, you're not meant to override existing Controllers and Models. In that, what methods are used for implementing additional functionality to an existing application?
I found this helpful piece: http://www.redmine.org/projects/redmine/wiki/Plugin_Internals
However, it mentions:
As explained above: you rarely want to override a model/controller. Instead you should either:
1) add new methods to a model/controller or
2) wrap an existing method.
Presumably, you wouldn't be adding methods directly to the original source? I notice he uses Modules to implement this, but unsure of exactly how they work.
Yes, original source modification is not recomended because of:
Merge problems when You are updates Redmine
Problems with other plugins
For add new or modify existing methods You must create controller, model or helper patch:
require_dependency 'issues_controller'
module IssuesControllerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :some_method, :your_action # modify some_method method by adding your_action action
end
module InstanceMethods
# modified some_method
# You can call original method before or after
# even in the middle of your actions
# or not to call to all
def some_method_with_your_action # modified some_method
do_something_before # your actions before
some_method_with_your_action # call original some_method if needed
do_something_after # your actions after
end
# new method
def your_method
do_something
end
end
end
IssuesController.send :include, IssuesControllerPatch
And add
require 'path/to/your/issues_controller_patch'
to your_plugin/init.rb
Also, if You want call your code in the middle of original code, You must use hooks. Find nessecary hook in original code (controller, view, helper, model), they looks like this:
call_hook(:controller_account_success_authentication_after, {:user => user})
If not found suitable hook, You can add your own (still have modify original code) or add issue at Redmine page (will have to wait a long)
To use hooks, add hook listener like:
class IssuesControllerHookListener < Redmine::Hook::ViewListener
# use view hook - add path/to/your/view.html.erb redmine issues list
# position of your additions depends of used hook position
# view_issues_index_bottom is hook name
# :partial is parameter, value of that is your view
render_on :view_issues_index_bottom, :partial => 'path/to/your/view'
# use controller hook - call your code inside original
# controller_issues_ready_before_index is hook name, method must be named same
# context here is parameters come from hook calling method
# You can use it for your own purposes
def controller_issues_ready_before_index(context = {})
if context[:some_context_param] == some_value
do_something
end
end
end
And add
require 'path/to/your/hook'
to your_plugin/init.rb

Securely and dynamically calling methods based on user input in Rails

I have an API that I built in Rails. It runs some methods I've defined in a module and renders their return values as JSON. While I've been developing, the entire code for the API has been the module itself (contents irrelevant), a single route:
controller :cool do
get "cool/query/*args" => :query
end
and this:
class CoolController < ApplicationController
include CoolModule
def query
args = params[:args].split("/")
# convert the API URL to the method name
method_symbol = args[0].tr("-","_").to_sym
if !CoolModule.method_defined?(method_symbol)
return nil
end
# is calling self.method a good idea here, or is there a better way?
render json: self.method(method_symbol).call(args[1], args[2])
end
end
My API (i.e. the module) contains ~30 functions each accepting a variable number of arguments, the routing logic for which I'd like to keep nicely wrapped in the module (as it is now).
It will be used as a "mid-end" (one might say) between my cool ajax front-end and another API which I don't control and is really the back-end proper. So special concern needs to be given since it both receives user input and sends queries to a third party (which I am accountable for).
My questions specifically are:
Will this general strategy (method names directly from queries) be secure/stable for production?
If the strategy is acceptable but my implementation is not, what changes are necessary?
If the strategy is fundamentally flawed, what alternatives should I pursue?
The pessimist in me says 'miles of case-when,' but I'll thank you for your input.
The problem with Module#method_defined? is it may return true on indirect method definitions (other included modules, inherited methods if module is a Class) as well as private methods. This means you (and importantly anyone else who touches the code) will have to be very careful what you do with that module.
So, you could use this approach, but you need to be super explicit to your future maintainers that any method in the module is automatically an external interface. Personally, I would opt for something more explicit, like a simple whitelist of allowed api method names, eg:
require 'set'
module CoolModule
ALLOWED_API_METHODS = Set[
:foo,
:bar,
...
]
def self.api_allowed? meth
ALLOWED_API_METHODS.include? meth.to_sym
end
end
Yeah, you have to maintain the list, but it's not unsightly, it's documentation of an explicit interface; and means you wont get bit by a later coder deciding he needs to add some utility methods to the module for convenience and thus accidentally exporting them to your external api.
Alternately to the single list, you could have a define_for_api method and use that instead of def to declare the api interface methods
module CoolModule
#registered_api_methods = Set.new
def self.define_for_api meth, &block
define method meth, &block
#registered_api_methods << meth
end
def self.api_allowed? meth
#registered_api_methods.include? meth.to_sym
end
def api_dispatch meth, *args
raise ArgumentError unless self.class.api_allowed? meth
send(meth *args)
end
define_for_api :foo do |*args|
do_something_common
...
end
define_for_api :bar do
do_something_common
...
end
# this one is just ordinary method internal to module
private
def do_something_common
end
end

Resources