Using
delayed_job_active_record 4.1
rails 6.1.1
a method within a cartitems controller correctly calls and executes a method
set_qr_code(#cartitem.cart)
But being resource intensive, delayed_job should allow to complete the parent method and run the longer method on its own. However, if the method is run with the delay verb
#cartitem.cart.delay.set_qr_code(#cartitem.cart)
The log show the complaint:
undefined method `set_qr_code' for #<Cart id: 64, ...
So although the method is cast in the application_controller.rb it is not being invoked.
The documentation has no explicit indications as to where such methods should be set. Where should one define them?
#AbM is correct. You are calling the set_qr_code method on an instance of your Cart class so it needs to go in your Cart model, not the Carts Controller.
You can call #cartitem.cart.delay.set_qr_code without the #cartitem.cart argument from your controller action, then reference the instance as self in your model:
def set_qr_code
self.do_something_here
end
The extra call to set_qr_code(#cartitem.cart) in your CartItems controller isn't strictly necessary unless you there are additional steps you want to perform there before triggering the delayed job call.
Related
I have a private model method in my rails app that is so vital to quality of life as we know it that I want to test it using rspec so that it squawks if future tampering changes how it works.
class MyModel < ApplicationRecord
belongs_to something
has_many somethings
after_create :my_vital_method
validates :stuff
private
def my_vital_method
#do stuff
end
end
When I try calling the method in an rspec test I get the following error message:
NoMethodError: undefined method `my_vital_method' for #< Class:......>
Question: How do I call a private model method in rspec?
By definition, you are not allowed to call private methods from outside of the class because they are private.
Fortunately for you, you can do what you want if you use object.send(:my_vital_method) which skips the testing for method call restrictions.
Now, the bigger issue is that you really are making your object more brittle, because calling from outside like that may need implementation details that will get out of sync with the class over time. You are effectively increasing the Object API.
Finally, if you are trying to prevent tampering, then you are tilting at windmills -- you can't in Ruby because I can just trivially redefine your method and ensure that if my new method is called from your anti-tamper checking code, I call the original version, else I do my nefarious stuff.
Good luck.
If your private method needs testing it probably should be a public method of another class. If you find the name of this class define its purpose and then create the method signature you want you would end up with something like
def my_vital_method
MyVitalClass.new.do_stuff
end
and now you can write a spec for do_stuff.
Within a model I have the following method:
def some_method
some_obj.new(view_c: view_context).create_some_links
end
An exception is thrown with the following message:
undefined local variable or method `view_context'
I am fully aware that it is not good practice to call view-related methods from your model, but nonetheless: Is it possible to access view_context from the model so that I can pass it along to a Plain Old Ruby Object (PORO) which creates some links?
Update: From the code I have above, one suggestion might be to simply create and call the PORO directly in the view. However: pretend that the code requires it to go through the model in order to create the right PORO.
view_context doc
Unless you pass the view_context to the method it is impossible:
model layer has nothing to do with view layer.
model itself can not possibly know anything about view context.
model has no access to view context.
Here's how you'd pass a view to the method (while being in the view):
#model_instance.some_method(self) # self is the view itself
Now, slight method change does the trick:
def some_method(view_context)
some_obj.new(view_c: view_context).create_some_links
end
When we navigate through pages in a rails app, inturn we call one of the functions defined in the controller class. Lets say we access localhost:3000/articles/new then new action (method) of the ArticlesController class is called/invoked.It's simple.
But what i can't figure out is that since ArticlesController class is a pure Ruby class with some methods and we need an instance of this class to call one of it's methods. But we never explicitly do that.
Then how the function call of any controllerclass is made possible ?
The controller is initialized automatically by rails. Specifically, this calls the action method on the controller class, which does the actual initialization.
The RouteSet generates instances of any controller on demand based on the needs of the ActionDispatch routing system. See here for how this is done.
So unless you are testing a controller directly, you can rely on the router to supply you with a controller instance. And if you are testing one directly, you should be using an ActiveController::TestCase to do this work for you.
I want to create matcher that test whether a model is watched by observer.
I decided to dynamically add method after_create (if necessary), save instance of model and check is it true that observer instance received an after_create call. Simplified version (full version) :
RSpec::Matchers.define :be_observed_by do |observer_name|
match do |obj|
...
observer.class_eval do
define_method(:after_create) {}
end
observer.instance.should_receive(:after_create)
obj.save(validate: false)
...
begin
RSpec::Mocks::verify # run mock verifications
true
rescue RSpec::Mocks::MockExpectationError => e
# here one can use #{e} to construct an error message
false
end
end
end
It wasn't work. No instance of observer is received after_create call.
But If I modify actual code of Observer in app/models/user_observer.rb like this
class UserObserver
...
def after_create end
...
end
It works as expected.
What should I do to add after_create method dynamically to force trigger observer after create?
In short, this behavior is due to the fact that Rails hooks up UserObserver callbacks to User events at the initialization time. If the after_create callback is not defined for UserObserver at that time, it will not be called, even if later added.
If you are interested in more details on how that observer initialization and hook-up to the wobserved class works, at the end I posted a brief walk-through through the Observer implementation. But before we get to that, here is a way to make your tests work. Now, I'm not sure if you want to use that, and not sure why you decided to test the observer behavior in the first place in your application, but for the sake of completeness...
After you do define_method(:after_create) for observer in your matcher insert the explicit call to define_callbacks (a protected method; see walkthrough through the Observer implementatiin below on what it does) on observer instance. Here is the code:
observer.class_eval do
define_method(:after_create) { |user| }
end
observer.instance.instance_eval do # this is the added code
define_callbacks(obj.class) # - || -
end # - || -
A brief walk-through through the Observer implementation.
Note: I'm using the "rails-observers" gem sources (in Rails 4 observers were moved to an optional gem, which by default is not installed). In your case, if you are on Rails 3.x, the details of implementation may be different, but I believe the idea will be the same.
First, this is where the observers' instantiation is launched: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/railtie.rb#L24. Basically, call ActiveRecord::Base.instantiate_observers in ActiveSupport.on_load(:active_record), i.e. when the ActiveRecord library is loaded.
In the same file you can see how it takes the config.active_record.observers parameter normally provided in the config/application.rb and passes it to the observers= defined here: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L38
But back to ActiveRecord::Base.instantiate_observers. It just cycles through all defined observers and calls instantiate_observer for each of them. Here is where the instantiate_observer is implemented: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L180. Basically, it makes a call to Observer.instance (as a Singleton, an observer has a single instance), which will initialize that instance if that was not done yet.
This is how Observer initialization looks like: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L340. I.e. a call to add_observer!.
You can see add_observer!, together with and define_callbacks that it calls, here: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/activerecord/observer.rb#L95.
This define_callbacks method goes through all the callbacks defined in your observer class (UserObserver) at that time and creates "_notify_#{observer_name}_for_#{callback}" methods for the observed class (User), and register them to be called on that event in the observed class (User, again).
In your case, it should have been _notify_user_observer_for_after_create method added as after_create callback to User. Inside, that _notify_user_observer_for_after_create would call update on the UserObserver class, which in turn would call after_create on UserObserver, and all would work from there.
But, in your case after_create doesn't exist in UserObserver during Rails initialization, so no method is created and registered for User.after_create callback. Thus, no luck after that with catching it in your tests. That little mystery is solved.
I'm doing a Rails application where people can take quizzes. I have a model BrowserGame that's taking care of the controller logic (sessions, redirecting etc.). Currently, this is my #initialize method:
class BrowserGame
def initialize(controller)
#controller = controller
end
end
And in the controller I have a method
class GamesController < ApplicationController
# actions
private
def browser_game
BrowserGame.new(self)
end
end
As you can see, I'm passing the whole controller to BrowserGame#initialize (so I can manipulate with sessions and others). Is this a good idea? Are there any side effects, since the controller instance is a large object?
Yes, it is fine to pass large objects as method parameters. You're not placing the object on the stack, just a pointer to it. As far as side-effects -- anything you do to #controller from within BrowserGame is seen through any other reference to the controller, but that's probably what you already expect.
There is no problem with passing a large object.
As Darshan says, it is only a pointer.
It would be better to only pass serializable objects if you are forking the process/thread or otherwise trying to create a delayed job to run in the background.