rspec expect child to receive method when parent method is called - ruby-on-rails-5

class Post
has_many :comments
after_update :update_comments
def update_comments(user)
comments.where(user: user).each do |comment|
# binding.pry
comment.do_something
end
end
end
class Comment
belongs_to :post
def do_something
'Ok, I will'
# binding.pry
end
end
This is my issue:
RSpec.describe Post do
describe '#update_comments' do
let(:post) { create :post }
let(:comment) { create :comment, post: post }
it 'triggers comment.do_something'
comment = post.comments.first
expect(comment).to receive(:do_something)
post.update(title: 'new title')
end
end
end
I get this error:
(#<Comment id: 1, ..., created_at: "2018-06-15 01:31:33", updated_at: "2018-05-16 02:51:39">).api_update(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
But if I use binding.pry in either (or both) def(s), I get the console, so I know it's actually being called.
I compared my comment variable in RSpec with self in the Comment class and they match. I tried using post.reload and comment.reload in RSpec to make sure the association was solid. Not sure what else I can do.
The fact that it triggers pry inside the method I'm saying should be received is so baffling to me.
What am I missing?

After a lot more digging, the issue is that my comment variable isn't actually exactly the same instance as the variable that RSpec sees in the loop. They differ by the Rails object ID that is assigned on the fly.
I needed to use a spy or stub the method to avoid this issue.

Related

How to get the id of newly created record in rails & associate the id with another model - Rails

I'm building a rails app connected to a react app. The rails app has four models. For arguments sake here, let's pretend it has two models: a sender & address. The sender belongs_to an address, and an address has_many senders. Therefore a sender has a foreign_key of address_id. When the sender registers for a new account, he creates an address as well. I'm trying to build something in my rails controller whereby when a new address is created it is associated with address_id column of the sender. I tried building my own unique solution based on the solutions of this rails thread. but I didn't have much success. I implemented the following in my address controller:
class AddressesController < ActionController::API
def show
#address = Address.find(params[:id])
end
def create
success = false
address = Address.new(address_params)
sender = #sender
if address.save
address_id = address.sender.build({address_id: address.id })
success = true if !address_id.empty?
else
render json: { errors: address.errors.full_messages }, status: :bad_request
end
if success = true
render json: {status: 'address created successfully'}, status: :created
else
render('edit')
end
end
def address_params
params.require(:address).permit(:streetname, :zipcode, :city, :country)
end
end
And I'm getting this error in the terminal:
NoMethodError (undefined method `sender' for #<Address:0x00007f93b2576050>
Did you mean? senders
senders=
send):
app/controllers/addresses_controller.rb:21:in `create'
So I placed an attr_accessor in my senders controller:
class SendersController < ApplicationController
attr_accessor :sender
def show
#sender = Sender.find(params[:id])
end
def create
#sender = Sender.new(sender_params)
if #sender.save
render json: {status: 'sender created successfully'}, status: :created
else
render json: { errors: sender.errors.full_messages }, status: :bad_request
end
end
def sender_params
params.require(:sender).permit(:userid, :name, :email, :photourl, :address_id)
end
end
I feel I'm over complicating things over here. So I really need help with this one. Thanks in Advance!
NoMethodError (undefined method `sender' for)
First, the error triggered because you have has_many relation with sender, so it should be senders not sender
address.senders.build
When the sender registers for a new account, he creates an address as
well. I'm trying to build something in my rails controller whereby
when a new address is created it is associated with address_id column
of the sender.
Second, your approach is wrong. You can make use of available methods for belongs_to association. For example, in your situation, you can call #sender.build_address
address = #sender.build_address(address_params)
The build_association method returns a new object of the associated
type. This object will be instantiated from the passed attributes, and
the link through this object's foreign key will be set, but the
associated object will not yet be saved.
So, finally the create would look like below(after removing unnecessary code)
def create
address = #sender.build_address(address_params)
if address.save
render json: {status: 'address created successfully'}, status: :created
else
render json: { errors: address.errors.full_messages }, status: :bad_request
end
end
You don't need to manually set the id if you're creating a record via an association:
address.sender.build({address_id: address.id })
Can just be
address.sender.build
And rails will assign the id for you.

Fetching last incomplete record or new in ActiveRecord

I have a Rails 3.2.21 app where I'm adding some basic timeclock functionality. I need to build a scope called current_clock_event that will look for the last record for a user where clock_out: nil so in essence, the record that will be fetched is the last ClockEvent record for the user that does not have a value in clock_out. This is what I want to pass to my controller/view.
class ClockEvent < ActiveRecord::Base
attr_accessible :clock_in, :clock_out, :total_hours, :user_id
scope :current_clock_event, where(clock_out: NIL).last
end
As you can see I wrote a very simple scope to pull a record where the clock_out: NIL so in theory that should pull the last incomplete record. I think this is working ok but I need to figure out how to access this in the controller and have some sort of conditional to either pull the current_clock_event or instantiate a new clock event if the last record is completed (both clock_in and clock_out are populated)
So I'm stubbing out my controller but am hitting a wall as to how to do this.
class ClockEventsController < ApplicationController
def index
#clock_event = current_user.current_clock_event # need to figure out this part to fetch the record or if the record is complete instantiate a ClockEvent.new for the user.
respond_to do |format|
format.html # index.html.erb
format.js
end
end
end
I wrote code 2 years ago that did all of this but lost the repo by accident so I have nothing to reference and am sort of brain-fogging on how to pull this off.
Any help would be appreciated. If you need more examples or further explanation, please let me know.
You might want to try something like this:
class ClockEvent
belongs_to :user
# you might want to add an order here...
scope :last_clock_event, -> { where("clock_out NULL").last }
def completed?
clock_in.present? && clock_out.present?
end
end
class User
has_many :clock_events
def current_clock_event
ce = clock_events.last_clock_event
ce.completed? ? ClockEvent.new : ce
end
end
class ClockEventsController < ApplicationController
def index
#clock_event = current_user.current_clock_event
render :index
end
end
The completed? method defined on the ClockEvent instance allows you to tell if your instance is considered completed or not.
The current_clock_event method defined at the User level allows you to define the logic to return either the last clock event record or a new one if completed.
The index method is pretty straight forward.
I played around with some code and was able to get some refactoring help to make things cleaner. In the User model I refactored current_clock_event to just clock_event and seem to have been able to make the code a bit cleaner, although it's not tested, just stubbed out for now. Let me know what you think.
class ClockEvent
belongs_to :user
scope :incomplete, -> { where(clock_out: nil) }
scope :complete, -> { where.not(clock_out: nil) }
def completed?
clock_in.present? && clock_out.present?
end
end
class User
has_many :clock_events
def clock_event
#clock_event ||= clock_events.incomplete.last || clock_events.new
end
end
class ClockEventsController < ApplicationController
def index
#clock_event = current_user.clock_event
render :index
end
end

Rspec. Issuing a save on an existing, but modified, activerecord does not run before_update callback methods that are in included modules

I'm starting to attempt to incorporate more testing into my code, but I've hit a wall.
My model looks something like this
class Image < ActiveRecord:Base
before_create :do_something_general
before_update :do_something_on_update, :do_something_general
belongs_to :captureable, polymorphic: true
mount_uploader :image, SomeUploader
...
end
My rspec looks something like
describe SomeModel do
before :each do
#image = FactoryGirl.create(:image)
end
...
describe "moving image" do
context "change the parent of the image" do
it "moves" do
new_parent = FactoryGirl.create(:parent)
current_file_path = #image.image.file.path
#image.captureable = new_parent
#image.save!
#image.image.file.path.should_not == current_file_path
end
end
end
end
When I first create an Image, it will get stored in a file tree structure that depends on its parents. When a parent changes, the Image should be moved, and this is done with the before_update callback :do_something_on_update. My test should verify that when the Image has had its parent changed, it is located in a new location.
The problem is, when #image.save.should be_valid an except is returned because :do_something_general is run before :do_something_on_update (the order is important). It seems that the rspec thinks I'm creating a new object (using debugger I've checked that the object id doesn't change when modifying it), and thus runs before_create instead of before_update.
Edit: it seems that before_update is working, but only on callback methods that are in the class, but not in the module. In this case, :do_something_on_update is located in an included module.
End Edit
When I try this in the console in development mode, it works as expected.
Other things to note: I'm using Carrierwave for uploading (the image column is a carrierwave uploader) and when the :image factory is called, it also creates several parents and grandparent objects. Using Rspec 2.10, Rails 3.2.8, Ruby 1.9.3
Looking forward to your responses.
Thanks.
I would expect image.save.should be_valid to fail, because it's going to invoke image.save, which returns true or false, then it's going to invoke #valid? on that boolean result, which should likely fail.
You might consider writing your test like so:
describe SomeModel do
let(:image) { FactoryGirl.create(:image) }
context "when changing the parent of the image" do
let(:parent_change) { lambda {
image.captureable = FactoryGirl.create(:parent)
image.save!
} }
it "updates the image's path" do
expect parent_change.to change { image.image.file.path }
end
end
end
This ensures that you only have one assertion in the test (that the file path is changing), and that if the save fails, it will instead raise an exception.

using build method on association in rails 3.2 creating object in memory

I have 2 models like the following
Class Post
has_many :comments, :dependent => :destroy
end
Class Comment
validates_presence_of :post
validates_presence_of :comment
belongs_to :post
end
In Comments controller,
def create
comment = #post.comments.build(params[:comment])
if comment.save
// some code
else
// some code
end
end
When the comment is invalid as per the validation, the comment is not saved. But when the #post object is accessed in the view, it contains a comment object with nil id. This did not happen in Rails 2.3.11. We are upgraded to Rails 3.1 and then now to Rails 3.2. This comment object with nil id disappears when I do #post.reload. We are using REE.
I tried to interchange build and new methods. It had the same result as build. Similar behavior is found across our application. Is it the expected behavior or am I doing something wrong?
This seems like expected behaviour to me.
via http://guides.rubyonrails.org/association_basics.html#belongs_to-association-reference
4.1.1.3 build_association(attributes = {})
The build_association method returns a new object of the associated
type. This object will be instantiated from the passed attributes, and
the link through this object’s foreign key will be set, but the
associated object will not yet be saved.
When you call #post.comments.build(...), Rails:
Creates a new Comment object
sets comment.post_id to #post.id.
Inserts it into the comments array (in memory).
When the validation fails, it doesn't delete the comment and the comment persists in the in-memory comments array. When #post gets to your view, #post.comments still includes that badly validated comment.
As for how to deal with it, I'm not sure. Maybe you could do something like (in your controller)... (Feels pretty ugly though.)
def create
comment = #post.comments.build(params[:comment])
if comment.save
// some code
else
#bad_comment = #post.comments.pop
end
end
I had a similar problem while using rails 3.2
Firstly, you need to create two separate methods in your controller. They will be as follows:
The 'new' method that is used to build your comments using 'build_association'
def new
#post = Post.new
comment = #post.build_comments
end
The 'create' method to actually create your comments using 'create_association'
def create
#post = Post.new(params[:post])
comment = #post.create_comments(params[:post][:comment_attributes])
if comment.save
// some code
else
#bad_comment = #post.comments.pop
end
end
Note: I suggest passing 'comment' attribute as a nested attribute of 'post' through your form using 'fields_for'.
Please refer:
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Rails, creating a callback

I want to use an ActiveModel callback to be called after an object has been voted on, the issue is that the gem I'm using (voteable_mongo) to make the model votable doesnt provide like a vote model or callback in my app, so how can I create a callback for it?
set_callback(:vote, :before) do |object|
object.do_something
end
Obviously that vote action I made up, but the gem I'm using has this method, how would you properly extend this method to trigger a callback?
Taking the plugin example as source here's what you could do:
class Post
include Mongoid::Document
include Mongo::Voteable
extend ActiveModel::Callbacks
define_model_callbacks :vote
# set points for each vote
voteable self, :up => +1, :down => -1
def vote(options, value = nil)
_run_vote_callbacks do
super( options, value )
end
end
end
I did not run this code so I am not sure if this is going to work correctly or not, but in the worst case you could alias the vote method using alias_method_chain or just copy and paste the source to inside the _run_vote_callbacks block (really, really ugly, but it's a solution anyway).
EDIT
This could also be done using alias_method_chain, if the code above does not work:
class Post
include Mongoid::Document
include Mongo::Voteable
extend ActiveModel::Callbacks
define_model_callbacks :vote
# set points for each vote
voteable self, :up => +1, :down => -1
alias_method_chain :vote, :callback
def vote_with_callback(options, value = nil)
_run_vote_callbacks do
vote_without_callbacks( options, value )
end
end
end