Call a block on a Model - ruby-on-rails

Instead of defining a scope in a class like this:
scope :first_user, -> { first }
And calling it like this: User.first_user
I would like to define a block in another class, that can be called on the user class and works like a Scope:
This code is not working but it should signalize what behaviour I want to achieve:
class Manage
def get_first_user
User.&first_added
end
def first_added
Proc.new { first }
end
end
When I run this code:
a = Manage.new
a.get_first_user
it says me, & undefined method for User. How can I execute the Block defined in first_added on the User model?
How can I in general call a block on a class? Thanks

If I understand your question correctly, you can use class_eval:
>> foo = Proc.new { count }
=> #<Proc:0x007f1aa7cacfd8#(pry):30>
>> Buyer.class_eval(&foo)
(30.6ms) SELECT COUNT(*) FROM "buyers"
=> 1234
Or with your example:
class Manage
def get_first_user
User.class_eval(&first_added)
end
def first_added
Proc.new { first }
end
end

It is not what you want but maybe this will be helpful.
I am not sure if one can call proc on something. I think one can only call proc with something, i.t. in your case passing User as parameter.
def get_first_user
wrap User, &first_added
end
def first_added
Proc.new { |model| model.where(...) }
end
private
def wrap(model, &block)
yield model
end

Here's three ways to call first on User from Manager using:
Object#send:
http://ruby-doc.org/core-2.3.1/Object.html#method-i-send
Module#class_eval:
http://ruby-doc.org/core-2.3.1/Module.html#method-i-class_eval
File manage.rb:
class User
def self.first
puts 'record would probably go here'
end
def self.yielder
print "via yielder => "
self.send(yield) if block_given?
end
def self.blocker(&block)
print "via blocker => "
self.send(block.call) if block_given?
end
def self.evaller(&block)
print "via evaller => "
self.class_eval(block.call) if block_given?
end
end
class Manage
def get_first_user
User.yielder(&first_added)
User.blocker(&first_added)
User.evaller(&first_added)
end
def first_added
Proc.new {"first"}
end
end
a = Manage.new
a.get_first_user
Output:
$ ruby manage.rb
via yielder => record would probably go here
via blocker => record would probably go here
via evaller => record would probably go here

Related

Creating a hash of lambda's referencing instance methods in Ruby

I have a class with a hash of lambda's pointing to methods in the class.
Thing.new.call
=> undefined local variable or method `do_foo' for Thing:Class
I was expecting
Thing.new.call
=> foo
=> bar
The lambda's seem to be calling do_foo and do_bar as class methods instead of instance methods. Can I fix this? I don't want to use send(command) as the commands can be updated by a user and can be a hacking risk.
Link to a replit
class Thing
def initialize
#commands = [:foo, :bar]
end
SAFE_COMMANDS = {
foo: -> { do_foo },
bar: -> { do_bar }
}
def call
#commands.each { |command| SAFE_COMMANDS[command].call }
end
private
def do_foo
puts 'foo'
end
def do_bar
puts 'bar'
end
end
The problem here is your SAFE_COMMANDS array and the lambdas in it are being defined in class context, therefore it is trying to call class methods.
You can pass the instance to the lambdas so that you can call its instance methods.
class Thing
def initialize
#commands = [:foo, :bar]
end
SAFE_COMMANDS = {
foo: -> (obj){ obj.do_foo },
bar: -> (obj){ obj.do_bar }
}
def call
#commands.each { |command| SAFE_COMMANDS[command].call(self) }
end
private
def do_foo
puts 'foo'
end
def do_bar
puts 'bar'
end
end
However you'll get the following error:
private method `do_foo' called for #<Thing:0x00007fdc018399f8 #commands=[:foo, :bar]> (NoMethodError)
This is because those methods are private and therefore inaccessible by the lambdas.
So here's what I'd recommend instead. Use #send but filter the commands first and raise an exception (or something similar) if an illegal command is issued.
class Thing
def initialize
#commands = [:do_foo, :do_bar, :do_bad_stuff, :do_really_bad_stuff]
end
SAFE_COMMANDS = %i{
do_foo
do_bar
}
def call
illegal_commands = #commands - SAFE_COMMANDS
raise("illegal commands: #{illegal_commands.join(', ')}") unless illegal_commands.empty?
#commands.each { |command| self.send(command) }
end
private
def do_foo
puts 'foo'
end
def do_bar
puts 'bar'
end
end
This creates and array of any commands not in your SAFE_COMMANDS array. If that array is empty then we've only been passed legal commands and we proceed. Otherwise we raise an exception, and nicely format those illegal commands to tell the user they 'done screwed up.'
illegal commands: do_bad_stuff, do_really_bad_stuff (RuntimeError)
But I'd actually recommend checking this at initialization time. I assume you are going to eventually pass these commands to the instance somehow so I made that change.
class Thing
def initialize( *commands )
commands = commands.map(&:to_sym)
illegal_commands = commands - SAFE_COMMANDS
raise("illegal commands: #{illegal_commands.join(', ')}") unless illegal_commands.empty?
#commands = commands
end
SAFE_COMMANDS = %i{
do_foo
do_bar
}
def call
#commands.each { |command| self.send(command) }
end
private
def do_foo
puts 'foo'
end
def do_bar
puts 'bar'
end
end
# the exception is raised here
thing = Thing.new(:do_foo, :do_bar, :do_bad_stuff, :do_really_bad_stuff)
# rather than here
thing.call
I think filtering the command list rather than shenanigans with lambdas is way safer security wise, and way less complicated.

How do I allow mock a locally scoped variable in a controller to receive a message?

So, I've only been doing Ruby for a couple of days. Any tips would be appreciated it.
variable.rb
class Variable < ApplicationRecord
def some_attribute=(value)
#do something with the vlue
end
end
X_Controller.rb
class XController < ApplicationController
def do_something
variable = Variable.instance_with_id(params[:id])
variable.some_attribute = some_new_value
redirect_to(some_url)
end
end
x_controller_spec.rb
describe '#do_something' do
before do
allow(Variable).to receive(:instance_with_id) # Works fine
allow_any_instance_of(Variable).to receive(:some_attribute)
post :do_something, :params => { id: 'uuid' }, :format => :json
end
it {
expect(variable).to have_received(:some_attribute)
}
end
You probably want this:
let(:variable) { instance_double("Variable") }
before do
allow(Variable).to receive(:instance_with_id).and_return(variable)
allow(variable).to receive(:some_attribute=)
# ...
end
Because instance_with_id should return something. And then you want to allow calling some_attribute=(note the =) on that instance.

Rails - how to enforce one method call per method, like controllers do with `render`

So in Rails, you get an error if you try to call render multiple times within a controller action.
I have another Ruby class that I'm writing, and I'd like to try to do something similar (make sure that my own respond_with method is only called once.
So for example, this would be fine:
def my_method
if (my_value == true)
...
respond_with(:a, :b, :c)
else
...
respond_with(:x, :y, :z)
end
end
But this would raise an error if my_value == 4
def my_method
if (my_value >= 4)
...
respond_with(:a, :b, :c)
end
if (my_value <= 4)
...
respond_with(:d, :e, :f)
else
...
respond_with(:x, :y, :z)
end
end
Any thoughts on how to best accomplish that?
class MyBaseClass
def respond_with(arguments)
if #rendered
raise DoubleRenderError #or whatever
end
#rendered = true
#whatever the respond_with function should do
end
end
Here's one way I could think of - in your Ruby class, define a #responded attribute by doing attr_accessor :responded. In your respond_with method, add the following lines:
def respond_with
raise DoubleRenderError if self.responded
# do stuff
self.responded = true
end
The above code should raise an error if you call respond_with twice on the same object.

How do I make module methods available to a constant Proc variable? (Ruby)

This is in my application helper:
def call_me
"blah"
end
P = Proc.new { call_me }
def test_me
P.call
end
If I then do this in my view:
<%= test_me %>
I get an error saying that call_me is an undefined method.
How do I get the Proc to be able to call call_me? require doesn't do it. Prefixing with ApplicationHelper::call_me doesn't either. :(
This works, but I really don't like it since test_me will be called lots of times and in reality there are many many more Procs:
def test_me
p = Proc.new { call_me }
p.call
end
It should work as is in Ruby 1.9.2, but in earlier versions you can pass your helper as an argument to the Proc:
P = Proc.new { |helper| helper.call_me }
def test_me
P.call self
end
Since call_me is an instance method on your helper, you need to invoke it with an instance.
I think you were looking for this?
def call_me
Proc.new
end
proc = call_me { "blah" }
proc.call #=> "blah"
you have to pass call_me in. gets kind of convoluted...
p = Proc.new { |m| m.call }
def test_me
p.call(call_me)
end
I'm not entirely sure what your aim here is in the larger sense so this is more or less a stab in the dark...
The most pragmatic way to do this is to make the module methods static, and then simply using them wherever you want:
module Sample::SillyModule
def self.say_hello(my_cool_var)
puts "Hello #{my_cool_var}!"
end
end
proc do
Sample::SillyModule.say_hello("stackoverflow")
end

refactor a method with a block that contains multiple blocks itself

I'm using Ruby 1.9.2
I have a class method called search that takes a block
e.g.
class MyClass
def self.search do
if criteria1
keywords "abcde", fields: :c1 do
minimum_match(1)
end
end
if criteria2
keywords "defghi", fields: :c2 do
minimum_match(1)
end
end
end
end
What I'd like to do is refactor the MyClass.search method and have a simple one-line method for each if/end statement
e.g. it would look something like this:
class MyClass
def self.search do
c1_method
c2_method
end
def self.c1_method
if criteria1
return keywords "abcde", fields: :c1 do
minimum_match(1)
end
end
end
def self.c2_method
if criteria2
return keywords "defghi", fields: :c2 do
minimum_match(1)
end
end
end
end
But the refactoring that I show above doesn't quite work. It looks like the "blocks" that I'm returning in c1_method and c2_method aren't really being returned and evaluated in the search method, but I'm not sure how to do that.
Well, you can use the method(sym) call in order to get at the body of a method.
>> def foo(bar); bar * 2; end
=> nil
>> def baz(bleep); method(:foo).call(bleep); end
=> nil
>> baz(6)
=> 12

Resources