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
Related
Is there a way to have a model such that only code within the same module can access it?
Something like:
module SomeModule
class SomeActiveRecordModel
# has attribute `some_attribute`
...
end
end
module SomeModule
class SomeOtherClass
def self.sum_of_attribute
SomeActiveRecordModel.sum(:some_attribute)
end
end
end
class OutsideOfModule
def self.sum_of_attribute
SomeModule::SomeActiveRecordModel.sum(:some_attribute)
end
end
SomeModule::SomeOtherClass.sum_of_attribute # works
OutsideOfModule.sum_of_attribute # raises error
Short answer is no. Here's why
Ideally, you want to implement this in your SomeModule. But when you call SomeModule::SomeOtherClass.sum_of_attribute in other classes, you are in a scope of SomeModule::SomeOtherClass.
SomeModule::SomeActiveRecordModel.sum(:some_attribute)
||
\/
module SomeModule
class SomeActiveRecordModel
def sum(*args)
# Here, self => SomeModule::SomeActiveRecordModel
# That's why you won't be able to do any meta trick to the module
# or classes in the module to identify if it's being invoked outside
end
end
end
So you wouldn't know who the original caller is.
You might be able to dig through the call stack to do that. Here's another SO thread you might find helpful if you want to go down that path.
In short, no. But this is more a question of Ruby's approach and philosophy. There are other ways of thinking about the code that allow you achieve something similar to what you're looking for, in a more Rubyesque way.
This answer covers the different ways of making things private.
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)
In Python, you can write a decorator for memoizing a function's response.
Is there something similar for Ruby on Rails? I have a model's method that makes a query, which I would like to cache.
I know I can do something inside the method, like:
def foo(param)
if self.cache[param].nil?
self.cache[param] = self.get_query_result(param)
else
self.cache[param]
end
end
However, given that I would do this often, I'd prefer a decorator syntax. It is clearer and better IMO.
Is there something like this for Ruby on Rails?
I usually do this using custom accessors, instance variables, and the ||= operator:
def foo
#foo ||= something_or_other
end
something_or_other could be a private method on the same class that returns the object that foo should be.
EDIT
Here's a slightly more complicated solution that lets you cache any method based on the arguments used to call them.
class MyClass
attr_reader :cache
def initialize
#cache = {}
end
class << self
def cacheable(symbol)
alias_method :"_#{symbol}_uncached", symbol
define_method(symbol) do |*args|
cache[[symbol, *args]] ||= (send :"_#{symbol}_uncached", *args)
end
end
end
end
How this works:
class MyClass
def foo(a, b)
a + b
end
cacheable :foo
end
First, the method is defined normally. Then the class method cacheable is called, which aliases the original method to a new name, then redefines it under the original name to be executed only if it's not already cached. It first checks the cache for anything using the same method and arguments, returns the value if present, and executes the original method if not.
http://martinfowler.com/bliki/TwoHardThings.html:
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
Rails has a lot of built in caching(including query caching). You might not need to do anything:
http://guides.rubyonrails.org/caching_with_rails.html
Here is a recent blog post about problems with roll your own caching:
http://cmme.org/tdumitrescu/blog/2014/01/careful-what-you-memoize/
Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.
Given there is a model:
class MenuItem < ActiveRecord::Base
translates :title
end
and searchlogic is plugged in, I'd expect the following to work:
>> MenuItem.search(:title_like => 'tea')
Sadly, it doesn't:
Searchlogic::Search::UnknownConditionError: The title_like is not a valid condition. You may only use conditions that map to a named scope
Is there a way to make work?
P.S.
The closest I managed to get workging, was:
>> MenuItem.search(:globalize_translations_title_like => 'tea')
Which doesn't look nice.
I developed searchlogic. By default, it leverages existing named scopes and the database columns. It can't really go beyond that because ultimately it has to create the resulting SQL using valid column names. That said, there really is no way for searchlogic to cleanly understand what your :title attribute means. Even if it did, it would be specific to the logic defined in your translation library. Which is a red flag that this shouldn't be in the library itself, but instead a plugin or code that gets initialized within your app.
Why not override the method_missing method and do the mapping yourself? Searchlogic provides and easy way to alias scoped by doing alias_scope:
alias_scope :title_like, lambda { |value| globalize_translations_title_like(value) }
Here's a quick stab (this is untested):
module TranslationsMapping
def self.included(klass)
klass.class_eval do
extend ClassMethods
end
end
module ClassMethods
protected
def method_missing(name, *args, &block)
translation_attributes = ["title"].join("|")
conditions = (Searchlogic::NamedScopes::Conditions::PRIMARY_CONDITIONS +
Searchlogic::NamedScopes::Conditions::ALIAS_CONDITIONS).join("|"))
if name.to_s =~ /^(#{translation_attributes})_(#{conditions})$/
attribute_name = $1
condition_name = $2
alias_scope "#{attribute_name}_#{condition_name}", lambda { |value| send("globalize_translations_#{attribute_name}_#{condition_name}", value) }
send(name, *args, &block)
else
super
end
end
end
end
ActiveRecord::Base.send(:include, TranslationsMapping)
Hope that helps. Again, I haven't tested the code, but you should get the general idea. But I agree, the implementation of the translations should be behind the scenes, you really should never be typing "globalize_translations" anywhere in your app, that should be take care of transparently on the model level.