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.
Related
I've taken over porting a large application from rails 2 to rails 3. The new application uses Datamapper as its ORM (decision taken prior to me coming to the project, so I can't change this).
The old application used the activerecord "after_find" callback quite extensively;
class Foo < ActiveRecord::Base
after_find :bar
def bar
#This would be called after an object was "found"
end
end
I need to implement this in Datamapper, but I can't just monkey patch the find methods because datamapper uses composition rather than inheritence;
class Foo
include Datamapper::Resource
end
so if I monkey patched the find methods, there would be no way of me calling the original method! Could anyone point me in the right direction?
== EDIT ==
I have tried including the following:
module ClassMethods
def after_find(meth)
class << self
alias_method :orig_first, :first
def first(*args, &block)
puts "XXX FOUND THE FIRST"
orig_first args, &block
end
end
end
end
But this just sends the code into an infinite loop (constantly printing "XXX FOUND THE FIRST")
I have a several classes, each of which define various statistics.
class MonthlyStat
attr_accessor :cost, :size_in_meters
end
class DailyStat
attr_accessor :cost, :weight
end
I want to create a decorator/presenter for a collection of these objects, that lets me easily access aggregate information about each collection, for example:
class YearDecorator
attr_accessor :objs
def self.[]= *objs
new objs
end
def initialize objs
self.objs = objs
define_helpers
end
def define_helpers
if o=objs.first # assume all objects are the same
o.instance_methods.each do |method_name|
# sums :cost, :size_in_meters, :weight etc
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
end
end
end
YearDecorator[mstat1, mstat2].yearly_cost_sum
Unfortunately define method isn't available from within an instance method.
Replacing this with:
class << self
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
...also fails because the variables method_name and objs which are defined in the instance are no longer available. Is there an idomatic was to accomplish this in ruby?
(EDITED: I get what you're trying to do now.)
Well, I tried the same approaches that you probably did, but ended up having to use eval
class Foo
METHOD_NAMES = [:foo]
def def_foo
METHOD_NAMES.each { |method_name|
eval <<-EOF
def self.#{method_name}
\"#{method_name}\".capitalize
end
EOF
}
end
end
foo=Foo.new
foo.def_foo
p foo.foo # => "Foo"
f2 = Foo.new
p f2.foo # => "undefined method 'foo'..."
I myself will admit it's not the most elegant solution (may not even be the most idiomatic) but I've run into similar situations in the past where the most blunt approach that worked was eval.
I'm curious what you're getting for o.instance_methods. This is a class-level method and isn't generally available on instances of objects, which from what I can tell, is what you're dealing with here.
Anyway, you probably are looking for method_missing, which will define the method dynamically the first time you call it, and will let you send :define_method to the object's class. You don't need to redefine the same instance methods every time you instantiate a new object, so method_missing will allow you to alter the class at runtime only if the called method hasn't already been defined.
Since you're expecting the name of a method from your other classes surrounded by some pattern (i.e., yearly_base_sum would correspond to a base method), I'd recommend writing a method that returns a matching pattern if it finds one. Note: this would NOT involve making a list of methods on the other class - you should still rely on the built-in NoMethodError for cases when one of your objects doesn't know how to respond to message you send it. This keeps your API a bit more flexible, and would be useful in cases where your stats classes might also be modified at runtime.
def method_missing(name, *args, &block)
method_name = matching_method_name(name)
if method_name
self.class.send :define_method, name do |*args|
objs.inject(0) {|obj, sum| sum + obj.send(method_name)}
end
send name, *args, &block
else
super(name, *args, &block)
end
end
def matching_method_name(name)
# ... this part's up to you
end
I have a table of statuses, each of which have a name attribute. Currently I can do:
FooStatus.find_by_name("bar")
And that's fine. But I'm wondering if I could do:
FooStatus.bar
So I have this approach:
class FooStatus < ActiveRecord::Base
def self.method_missing(meth, *args, &block)
if self.allowed_statuses.include?(meth.to_s.titleize)
self.where("name = ?", meth.to_s.titleize).first
else
super(meth, *args, &block)
end
end
def self.allowed_statuses
self.pluck(:name)
end
end
The above code works, but it leads to the following weird behavior:
FooStatus.respond_to?(:bar) => false
FooStatus.bar => #<FooStatus name: 'bar'>
That's not great, but if I try to implement respond_to?, I get a recursion problem
class FooStatus < ActiveRecord::Base
def self.method_missing(meth, *args, &block)
if self.allowed_statuses.include?(meth.to_s.titleize)
self.where("name = ?", meth.to_s.titleize).first
else
super(meth, *args, &block)
end
end
def self.allowed_statuses
self.pluck(:name)
end
def self.respond_to?(meth, include_private = false)
if self.allowed_statuses.include?(meth.to_s.titleize)
true
else
super(meth)
end
end
end
And that gets me:
FooStatus.bar => ThreadError: deadlock; recursive locking
Any ideas on getting method_missing and respond_to to work together?
I agree with Philip Hallstrom's suggestion. If you know allowed_statuses when the class is built, then just loop through the list and define the methods explicitly:
%w(foo bar baz).each do |status|
define_singleton_method(status) do
where("name = ?", status.titleize).first
end
end
…or if you need that list of statuses elsewhere in the code:
ALLOWED_STATUSES = %w(foo bar baz).freeze
ALLOWED_STATUSES.each do |status|
define_singleton_method(status) do
where("name = ?", status.titleize).first
end
end
Clearer, shorter, and far less prone to future breakage and weird rabbit hole conflicts with ActiveRecord like the one you're in.
You can do really cool things with method_missing and friends, but it's not the first approach to go to when doing metaprogramming. Explicit is usually better when possible.
I also agree with Philip's concernt about creating conflicts with built-in methods. Having a hard-coded list of statuses prevents that from going too far, but you might consider a convention like FooStatus.named_bar instead of FooStatus.bar if that list is likely to grow or change.
I don't know if I'd recommend your approach... seems too magical to me and I worry about what happens when you have a status with a name of 'destroy' or some other method you might legitimately want to call (or that Rails' calls internally that you aren't aware of).
But... instead of mucking with method missing, I think you'd be better off extending the class and automatically defining the methods by looping through allowed_statuses and creating them. This will make respond_to? work. And you could also check to make sure it's not already defined somewhere else...
Use a scope.
class FooStatus < ActiveRecord::Base
scope :bar, where(:name => "bar")
# etc
end
Now, you can do FooStatus.bar which will return an ActiveRelation object. If you expect this to return a single instance, you could do FooStatus.bar.first or if many FooStatus.bar.all, or you could put the .first or .all on the end of the scope in which case it'll return the same thing as the finder.
You can also define a scope with a lambda if the input isn't constant (not always "bar"). Section 13.1 of this guide has an example
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
I'd like to override the setter for an association, but write_attribute() isn't working - probably because that method only works for database columns.
I have tried super(), but that doesn't work either (didn't think it would... but it was worth a guess).
How do I override the setter? Here is what I am trying to do:
def parent=(value)
# this line needs to be changed
write_attribute(:parent, value)
if value.subject.start_with?('Re:')
self.subject = "#{value.subject}"
else
self.subject = "Re: #{value.subject}"
end
self.receivers << value.sender
end
What worked for me is the following:
def parent=(new_parent)
# do stuff before setting the new parent...
association(:parent).writer(new_parent)
end
I found one way to do it, but I am disturbed by it:
alias_method :old_parent=, :parent=
def parent=(value)
self.old_parent = value
if value.subject.start_with?('Re:')
self.subject = "#{value.subject}"
else
self.subject = "Re: #{value.subject}"
end
self.receivers << value.sender
end
One thing I don't necessarily like about Rails is that whenever you want to do something that is out of the norm just a bit - but not unreasonable by any means - the "how" is very different than what your intuition would come up with.
It's not a problem when you know the exceptions, but when you're learning, this sort of irregularity and inconsistency on how to do things makes it harder to learn - not easier.
Java might be initially harder to learn, but it's way more consistent. Your intuition can take you a lot further once you think in Java. This is not true once you think in Rails. Rails is about memorization of methods to call and memorization on how to do things. In java, you can reason it out a lot more... and intellisense fills in the rest.
I'm just disappointed. This is a reoccurring pattern for me - I want do something that is just "a little more complex" than the framework examples... and the "how" is inconsistent and takes 30 minutes or maybe even hours to locate and find the answer for it.
In Rails 4.2.1 doc:
# Association methods are generated in a module that is included into the model class,
# which allows you to easily override with your own methods and call the original
# generated method with +super+. For example:
#
# class Car < ActiveRecord::Base
# belongs_to :owner
# belongs_to :old_owner
# def owner=(new_owner)
# self.old_owner = self.owner
# super
# end
# end
Instead of
def parent=(value)
write_attribute(:parent, value)
end
Couldn't you just do:
def parent=(parent)
parent_id = parent.id
end