Define a method to catch a_method[] in a class - ruby-on-rails

I have a lots of call to something like this :
User.active[0..5]
Which call :
class User
def active
(an ActiveRelation)
end
end
I am trying to do something like this for performance reasons :
class User
def active[limit]
(an ActiveRelation).limit(limit.to_a.size)
end
end
Unfortunately it doesn't work, any ideas to implement this ?
== EDIT
More cleaner :
class RelationWithLimit < ActiveRecord::Relation
def [] selector
case selector
when Integer
self.offset(selector).limit(1)
when Range
self.offset(selector.to_a[0]).limit(selector.to_a.size)
end
end
end
class ActiveRecord::Base
private
def self.relation #:nodoc:
#relation ||= RelationWithLimit.new(self, arel_table)
finder_needs_type_condition? ? #relation.where(type_condition) : #relation
end
end

You could have your own special subclass of ActiveRelation
class UserReturnRelation < ActiveRecord::Relation
def [] lim
self.limit lim
end
end
class User
def active
# Without knowing exactly what relation you are using
# One way to instantiate the UserReturnRelation for just this call
UserReturnRelation.new(self, arel_table).where("state = active")
end
end
Then User.active[5] should work as expected.
EDIT: Added instantiation info. You may want to look at Base#scoped and Base#relation for more info

Can you try it as params instead of array-indices? eg:
class User
def active(the_limit)
(an ActiveRelation).limit(the_limit)
end
end
User.active(5)
(note: not tested on any actual ActiveRelations...)

You can do it like this:
class User
def active
Limiter.new((an ActiveRelation))
end
class Limiter
def initialize(relation)
#relation = relation
end
def method_missing(method, *arguments, &block)
#relation.send(method, *arguments, &block)
end
def respond_to?(method, include_private = false)
#relation.respond_to?(method, include_private) || super
end
def [](value)
offset = value.to_a.first
limit = value.to_a.last - offset
#relation.offset(offset).limit(limit)
end
end
end

Well, you are defining the method in the wrong class. User.active[0..5] calls the class method active in User and the method [] in whatever class User.active is returning, I'll assume that it is returning an array of users, and Array has already defined the method [] so no worries about that.
You may be getting confused thinking that brackets are some kind of parenthesis for passing arguments to a function while they're not. Try this:
class User
class << self
def [](values)
self.find(values)
end
end
end
So, if you wanna use find with an arrays of ids, you may just use User[1,2,3].

Related

Checking a condition before running multiple methods. Ruby on Rails

Lets say I have a class with hundreds of instance methods in it. Now I have the requirement to run each method only if a certain thing is detected. Also I want to run my detection algorithm once for whole class instance no matter how many methods got called. If not detected first time no methods get called. I cannot afford if else around that many methods so I have to get a workaround. I have the following said class:
class CrawlerModule
extend Callbacks
before_run [:method_names, :of, :my, :class], :check_if_detected
#detected = nil
def check_if_detected
if #detected.nil?
detect
end
#detected
end
#hundreds of methods
private
def detect
detected_now = #my_detection_algorithm
#detected = detected_now
end
end
What I have done so far is to include following Callbacks module to call my check_if_detected method before every method but it doesn't work because method_added called at the very start of program and my detect function need some things to get initialized before detection. So the result array is always nil. Here's that complete module:
module Callbacks
def self.extended(base)
base.send(:include, InstanceMethods)
end
def overridden_methods
#overridden_methods ||= []
end
def callbacks
#callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end
def method_added(method_name)
return if should_override?(method_name)
overridden_methods << method_name
original_method_name = "original_#{method_name}"
alias_method(original_method_name, method_name)
define_method(method_name) do |*args|
result = run_callbacks_for(method_name)
if result[0] || (self.class.callbacks.values.flatten.include? method_name)
send(original_method_name, *args)
end
end
end
def should_override?(method_name)
overridden_methods.include?(method_name) || method_name =~ /original_/
end
def before_run(method_names, callback)
method_names.each do |method_name|
callbacks[method_name] << callback unless method_name.eql? callback
end
end
module InstanceMethods
def run_callbacks_for(method_name)
result = []
self.class.callbacks[method_name].to_a.each do |callback|
result << send(callback)
end
result
end
end
end
This solution came to me while trying to get to sleep, so pardon the brevity and untested code.
Forget all of the callback stuff. Instead...
You could rename every method to include a prefix like prefix_method_name (or suffix if you prefer).
Then implement a method_missing method which implements your check, and then calls the appropriate method afterward.
Something like this:
def method_missing(method_name, *args, &block)
if detected_now
send("prefix_#{method_name}")
end
end
And then to run the detection once for the whole class instance do it in the constructor:
def initialize
detected_now
super
end
Cache the detected_now results if you wish in an instance variable as normal and work with it that way if that is something you want to do.

How to access class method from instance method in ruby on rails non activerecord model

I have a non activerecord rails model:
class Document
attr_accessor :a, :b
include ActiveModel::Model
def find(id)
initialize_parameters(id)
end
def save
...
end
def update
...
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
end
In order to find the Document, I can use:
Document.new.find(3)
So, to get it directly I changed the find method to
def self.find(id)
initialize_parameters(id)
end
And I get the following error when I run
Document.find(3)
undefined method `initialize_parameters' for Document:Class
How can I make this work?
You can't access an instance method from a class method that way, to do it you should instantiate the class you're working in (self) and access that method, like:
def self.find(id)
self.new.initialize_parameters(id)
end
But as you're defining initialize_parameters as a private method, then the way to access to it is by using send, to reach that method and pass the id argument:
def self.find(id)
self.new.send(:initialize_parameters, id)
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
Or just by updating initialize_parameters as a class method, and removing the private keyword, that wouldn't be needed anymore.
This:
class Document
attr_accessor :a, :b
def self.find(id)
initialize_parameters(id)
end
end
Is not trying to "access class method from instance method" as your title states. It is trying to access a (non-existent) class method from a class method.
Everything Sebastian said is spot on.
However, I guess I would ask: 'What are you really trying to do?' Why do you have initialize_parameters when ruby already gives you initialize that you can override to your heart's content? IMO, it should look something more like:
class Document
attr_accessor :a, :b, :id
class << self
def find(id)
new(id).find
end
end
def initialize(id)
#a = 1
#b = 2
#id = id
end
def find
# if you want you can:
call_a_private_method
end
private
def call_a_private_method
puts id
end
end

Associated (dynamic) Scopes not working until I made any change in register file

I have two models, restaurant and cuisine with a many to many association. And I have this in my app/admin/restaurant.rb
ActiveAdmin.register Restaurant do
scope("All"){|scope| scope.order("created_at desc")}
Cuisine.all.each do |c|
scope(c.name) { |scope| scope.joins(:cuisines).where("cuisines.id=?",c.id)}
end
end
The problem is whenever I delete or add a new cuisine the scopes do not change until I make a change to my admin/restaurant.rb file. How can I fix this issue?
I was able to fix this by adding in my admin/restaurant.rb
controller do
before_filter :update_scopes, :only => :index
def update_scopes
resource = active_admin_config
Cuisine.order("created_at ASC").each do |m|
next if resource.scopes.any? { |scope| scope.name == m.name}
resource.scopes << (ActiveAdmin::Scope.new m.name do |restaurants|
restaurants.joins(:cuisines).where("cuisines.id=?", m.id)
end)
end
resource.scopes.delete_if do |scope|
!(Cuisine.all.any? { |m| scope.name == m.name })
end
resource.scopes.unshift(ActiveAdmin::Scope.new "All" do |restaurants| restaurants end)
end
found the solution here
I'm not sure of a way to defines scopes dynamically, at least using the scope method.
The alternative to the scope method is defining a class method, which accomplishes the same thing so far as I know.
In other words,
scope("All"){|scope| scope.order("created_at desc")}
is the same as
# in a Class
class << self
def All
order("created_at desc")
end
end
You can dynamically create class methods using this method (taken from ruby-defining-class-methods:
class Object
def meta_def name, &blk
(class << self; self; end).instance_eval { define_method name.to_s, &blk }
end
end
I'll use the following to remove the generated class methods:
class Object
def meta_undef name
(class << self; self; end).class_eval { remove_method name.to_sym }
end
end
These methods can be called from the save and destroy hooks on your models, i.e.:
# in a Model
def save(*args)
self.class.meta_def(name) do
joins(:cuisines).where("cuisines.id=?",c.id)
end
super(*args)
end
def destroy(*args)
self.class.meta_undef(name)
super(*args)
end
Then whenever a record is created or removed, the scopes will be updated. There are pros and cons of this approach. Clearly it's nice to define methods on the fly, but this is vulnerable to remote code execution.
Personally I'd probably hold off from dynamically defining class methods (i.e. scopes) and just make one that accepts an argument. Example:
# This is with standard ActiveRecord, not sure about ActiveAdmin
class Restaurant < ActiveRecord::Base
def self.All
order("created_at desc")
end
end
class Cuisine < ActiveRecord::Base
def self.by_name(name)
Restaurant.all.joins(:cuisines).where("cuisines.name=?", name)
end
end
Cuisine.by_name("something")
Restaurant.all.All
Restaurant.All
edit in response to your comment:
load(file) will re-load the source. So you could try the following:
# in a model
def save(*args)
load(Rails.root.join("app", "models", "THIS_MODEL_FILE.rb")
super
end
def destroy(*args)
load(Rails.root.join("app", "models", "THIS_MODEL_FILE.rb")
super
end
Under the hood, save is called for both create and update. So overriding it and destroy covers all the CRUD operations.
The reason I didn't initially recommend this approach is that I haven't personally used it. I'd be curious to know how it works.

Is there are way that model methods can work with variable with out them being explicitly passed?

I know this sounds like a ridiculous question but I trying to solve a chalange given by an potential employer. I have a schema and a couple of models with their methods. Almost all the methods have no variables passed in. Meaning none of the methods look like this:
def this_is_my_method(variable)
#stuff
end
or
def this_is_my_method variable
#stuff
end
but there are methods that are clearly working with variables like this:
def build_address
if variable
# do something
end
end
Is there a RoR way that a model method will just know about certain parameters or variables in certain situations?
So if my controller was recieving params that looked like this:
?my_method[begin]=1&my_method[end]=5
would the model method "my_method" know what "begin" and "end" where?
def my_method
if self.begin == self.end
# do something
else
# do something else
end
end
Remember that a model method has access to all the attributes (and other methods) of that model instance.
So (for example) this would be a valid model method.
class User < ActiveRecord::Base
def full_name
[first_name, last_name].join(' ')
end
end
This is taking an attribute user.first_name and an attribute user.last_name and combining them to create a new method user.full_name
EDIT as #Sanket has suggested you can pass values into a model if you make them attribute accessor...
def SomeController < ApplicationController
def some_controller_method
#user = User.find(params[:id])
#user.begin = params[:begin]
#user.end = params[:end]
#user.some_model_method
end
end
def User < ActiveRecord::Base
attr_accessor :begin, :end
def some_model_method
if self.begin == self.end
# do something
else
# do something else
end
end
end
Although to be frank I'd rather just pass the values in as method arguments.

how to set an instance variable from a class method?

I am trying to do a custom active record macro. But it right now seems impossible set an instance variable from within it's block.. here is what i am trying to do.
module ActiveRecord
class Base
def self.included(base)
base.class.send(:define_method, :my_macro) do |args|
# instance_variable_set for the model instance that has called this
# macro using args
end
end
end
end
i have tried class_eval, instance_eval.. but nothing seems to work or i don't how to use them.
Thanks in advance.
Edit: Let me try to explain better. I have a class method. An instance of the class calls this method. Now, this class method should instruct the instance to set an instance variable for itself.
Edit- this is how i want o use the macro
class MyModel < ActiveRecord::Base
my_macro(*args)
def after_initialize
# use the value set in the macro as #instance variable
end
end
Is this what you are thinking of:
class DynamicAdd
def add_method
self.class_eval do
attr_accessor :some_method
end
end
end
You can then do the following:
k = DynamicAdd.new
k.some_method = "hi"
should result in an undefined method error.
But,
k = DynamicAdd.new
k.add_method
k.some_method = "hi"
should work.
You can use this same format to define other types of methods besides attr_accessors as well:
class DynamicAdd
def add_method
self.class_eval do
def some_method
return "hi"
end
end
end
end
Hm.. Isn't included() a Module method? I don't think you can use that in a class like you have written. If you want to create a class method you can do
class Base
def self.my_method
end
or
class Base
class << self
def my_method
end
end
If all you want to do is to add an instance variable to an existing object, then you can use #instance_variable_set
class Base
class << self
def my_method(instance_of_base, value)
instance_of_base.instance_variable_set "#x", value
end
end
end
a = Base.new
a.class.send(:my_method, *[a,4])

Resources