I'm trying to expose some methods to a class based on the values in its order attribute, which can be something like ['top', 'bottom', 'right', 'lower-right'].
First, my class deletes from that array anything that responds blank like so:
def order
order.split(' ').delete_if do |o|
send(o).blank? if respond_to? o
end
end
After that, I want to "expose" top (for example's sake) as a method for the class. I've tried stuff like this in the initialize method:
order.each do |o|
V1_ATTRIBUTES << o.to_sym
define_method(o) do |a|
send(a).blank? ? '' : send(a)
end
end
But o isn't defined when the class isn't instantialised, a la:
<NameError: undefined local variable or method `o' for #<Class:#<APIDocument:0x007fb8123609f8>>>
Anyone have any success with a better way to get this result?
Note: I know it's not great practice allowing the user to write instance method names, but in this case security and breakages aren't a concern.
You can define singleton methods (per-object methods).
def order
orders.split(' ').map{|o| send(o)}.compact.each do |o|
singleton_class.send(:define_method, o) do
send(o).to_s
end
end
end
But as you can see, this only messes up your code. So don't abuse metaprogramming, use it wisely.
Related
Is there a better way to set values to setter methods when they are made dynamically using attr_accessor method? I need this for setting values for them from another model in rails. I'm trying to do something like below.
Model_class.all.each do |mdl|
attr_accessor(mdl.some_field)
end
Then I know that it creates a set of get and setter methods. What I want to do is, when these methods are get created, i want some value to be specified for setter method.Thanks in advance.
attr_accessor has no magic embedded. For each of params passed to it, it basically executes something like (the code is simplified and lacks necessary checks etc):
def attr_accessor(*vars)
vars.each do |var|
define_method var { instance_variable_get("##{var}") }
define_method "#{var}=" { |val| instance_variable_set("##{var}", val) }
end
end
That said, the attr_accessor :var1, :var2 DSL simply brings new 4 plain old good ruby methods. For what you are asking, one might take care about defining these methods (or some of them, or none,) themselves. For instance, for cumbersome setting with checks one might do:
attr_reader :variable # reader is reader, no magic
def variable=(val) do
raise ArgumentError, "You must be kidding" if val.nil?
#variable = val
end
The above is called as usual:
instance.variable = 42
#⇒ 42
instance.variable = nil
#⇒ ArgumentError: You must be kidding
Here is another possible implementation for this:
def attr_accessor(*args)
args.each do |attribute|
define_method(attribute.to_sym) { eval("##{attribute}") }
define_method((attribute.to_s + '=').to_sym) {|value| eval("##{attribute} = value") }
end
end
I have a question about ActiveRecord of Rails.
For example I have a Service model, and Service has name as a column.
This is app/model/service.rb
class Service < ActiveRecord::Base
def self.class_method
puts 'This is class method'
end
def instance_method
puts 'This is instance method'
end
end
Then I can do,
Service.class_method #=> 'This is class method'
Service.find(1).instance_method #=> 'This is instance method'
This is easy. But when I get ActiveRecord Instance in Array, for example
Service.where(id: [1,2,3])
and I need method like,
Service.where(id: [1,2,3]).sample_method
def sample_method
self.length
end
but how and where to define method for Active Record Array? I want to handle this object just like other Service class or instances.
Thanks.
First of all, where returns an ActiveRecord::Relation object, not an array. It behaves similar to an array, but inherits a load more methods to process data/ construct SQL for querying a database.
If you want to add additional functionality to the ActiveRecord::Relation class, you can do something like this:
class ActiveRecord::Relation
def your_method
# do something
end
end
This will need to reside somewhere meaningful, such as the lib directory or config/initializers.
This should allow you to do something like
Service.where(id: [1,2,3]).your_method
You can do something similar for any Ruby class, like the Hash, or Array class as well.
However, there's almost ALWAYS a better solution than extending/ overriding Rails/ Ruby source classes...
Your methods like get_count and get_name are a bit pointless... Why not just do:
Service.count
Service.find(1).name
Class methods like count, and instance methods like name (i.e. database column names) are all public - so you don't need to define your own getter methods.
As for your second example, you could just write the following:
Service.where(id: [1,2,3]).map{ |s| s.name }
Or equivalently:
Service.where(id: [1,2,3]).map(&:name)
But the following is actually more efficient, since it is performing the calculation in SQL rather than in ruby. (If you're confused what I mean by that, try running both versions and compare what SQL is generated, in the log):
Service.where(id: [1,2,3]).pluck(:name)
I would like to do something like:
class TestController < InheritedResources::Base
def test_method
self.var1 + self.var2
end
private
def test_params
params.require(:test).permit(:var1, :var2)
end
end
Where in the view I could call from the built in controller index:
test.test_method
I've tried adding a create method to the controller as follows:
def create
Test.create!(require(:test).permit(:var1, :var2, :test_method))
end
I've also tried updating the params directly:
private
def test_params
params.require(:test).permit(:var1, :var2, :test_method)
end
I've also tried making a helper method, but I knew that was doomed to fail because it wouldn't have access to var1 and var2.
I guess I just don't understand two things: one how to make my var1 and var2 white-listed so I can use them, and more importantly how to add a method to my model using strong parameters, because attr_accessible doesn't work in my models anymore.
EDIT:
Let me rephrase a little, maybe it will help. I can get access to individual Test objects in my view with a simple call to tests.each |test| in the view. I just want to make methods that act on my already defined active record variables for that object, hence var1 and var2. The problem is when I define a new method in my controller it is private to the object and I won't have access to it with a call from an instance of the object. Better yet, I would like to just be able to define a new variable, local to the object, that is created after it has propagated its other fields from the db.
EDIT2: I'm aware I'm probably missing the design pattern here. It can be hard to describe that I want X, when really I need Z. Thanks for the patience.
Thanks for the help.
There's no reason for white-listing parameters that you'll directly use.
White-listing with strong parameters is useful only when you call function like ActiveRecord#update that simply take every key from the dictionary, so you can control with key you want to allow and which not.
In this case, just do:
class TestController < InheritedResources::Base
def test_method
#result = params[:var1] + params[:var2]
end
end
And in your view, just print the #result variable wherever you want
<%= #result %>
This is the Rails way. You can of course call the variable as you want.
Helper methods are useful only for more complex cases.
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/
I have a module, whose purpose is to act on any given ActiveRecord instance.
For argument's sake, let's say that this method puts the string "match" if it matches certain properties with another instance of the same type.
module Foo
def check_against_other_instances
self.all.each do |instance|
if self.respond_to? :color && self.color == instance.color
puts "match"
end
end
end
end
However, I can't just simply call self.all here, because self is an instance. How do I call the class method all from here?
Ah.. found the solution almost right after I asked...
self.class.all.each do |instance|
...
if you want to extend the behavior of rails classes, then you are best of using ActiveSupport::Concern!
http://apidock.com/rails/ActiveSupport/Concern
You can pull the name of a class from an instance and then constantize it.
For example, given a class Thing:
t = Thing.new
t.class.name
# => "Thing"
t.class.name.constantize
# => Thing