Unit Testing updating a mongoid field using rr - ruby

I am trying to unit test the following code:
require 'mongoid'
class Seller
include Mongoid::Document
field :updated_at, type: Time
def update_updated_at
updated_at = Time.now
save
end
end
Here is my attempt
describe Seller do
describe 'update_updated_at' do
it 'sets updated_at to Time.now' do
now = Time.new(2013,10,14)
seller = Seller.new
mock(Time).now { now }
mock(seller).updated_at= now
mock(seller).save
seller.update_updated_at
end
end
end
I am getting the following failure
updated_at=(2013-10-14 00:00:00 -0600)
Called 0 times.
Expected 1 times.

Here is the native method which is updates record updated_at attribute to current time
For example
seller = Seller.first
seller.touch # updated_at set to current time
To mock the Time class there is a gem timecop which is super easy to use and dead simple Time, DateTime mocking e.g
it 'sets updated_at to Time.now' do
now = Time.new(2013,10,14)
Timecop.freeze(now) do
seller = Seller.new
seller.save # 2013-10-14
seller.touch # same time 2013-10-14
end
end

There is no need to mock this. Think about what you're doing here: you're setting updated_at and persisting it to the database. It's generally a code smell to stub/mock #save -- you can trust that it's doing what it should, which is persist attributes to the database. So, call the method, reload the record, and then assert that updated_at is set to the current time (as of when you called the method).
Or, just use touch ;)
By the way this test failed because you used updated_at = Time.now; it would have passed if you'd used self.updated_at = Time.now.

Related

How to ensure attribute is updated only once in Rails 3 Active Record?

Wondering what could be the best way to handle situations where I need to update particular column and once updated it should not allow user to update again.
I tried using changed? method which checks for record that has been changed and not yet saved. But this would not check a particular attribute in that row.
Any suggestions?
Thanks in advance.
rails g migration add_value_changed_to_YOUR_TABLE_NAME value_changed:boolean
set in the generated file
default: false
and run the migration.
And now, once the value is changed, update this value_changed to true:
before_update :update_value_changed, :check_value_changed
private
def update_value_changed
update(value_changed: true) unless value_changed
end
def check_value_changed
if value_changed
do_something_like_raise_error_or_do_not_save_changes
else
something_else
end
end
Let's assume that you have user class:
class User < ActiveRecord::Base
before_save :check_name_not_changed
private
def check_name_not_changed
if self.name.present? && self.name_changed?
errors.add(:name, "can not be updated")
end
end
end
I am assuming that the name attribute was some how set before.

DataMapper does not see a property assigned with #

I'm getting started with Ruby and DataMapper and I stumbled upon a problem which I think does not make any sense. Let's say I have the following model :
class Foo
include DataMapper::Resource
property :id, Serial
property :date, Date, required: true
def initialize
#date = Date.today
end
end
I open up IRB to test my models, set up the database connection, and try to save a new foo:
> foo = Foo.new
> foo.date
=> #<Date: 2013-03-28 ((2456380j,0s,0n),+0s,2299161j)>
> foo.save
Then I get the following exception :
DataObjects::IntegrityError: foos.date may not be NULL
And that perfectly makes sense, because I marked the date as required. But the date is there! I assigned it in the constructor of the class! And if I don't initialize it with today's date and I try to save, I only get a validation error. No exception.
What I don't understand (and this is what I want you to answer to) is that if I replace
#date = Date.today
with
self.date = Date.today
it works! foo is saved correctly. Why is that? Is it a bug inside DataMapper?
After bringing up the issue with DataMapper's people, I've been told that this is by design. For dirty tracking to work, you must absolutely use the attribute writer and not set the instance variable directly.

creating stub for after_create hook in rspec

my model code is:
class User < ActiveRecord::Base
after_create :create_node_for_user
def create_node_for_user
UserGraph.create(user_id: self.id)
end
end
and test for User model:
it "create node in graph database on user creation" do
userr = FactoryGirl.build(:user)
UserGraph.should_receive(:create).with(user_id: userr.id)
userr.save
end
but my test is failing with message
Failure/Error: userr.save
<UserGraph (class)> received :create with unexpected arguments
expected: ({:user_id=>nil})
got: ({:user_id=>94})
what might be wrong?
The explanation given by Yves is correct: the user id is nil until the record is saved because it is autogenerated by the DB. Here's an alternate approach:
it "create node in graph database on user creation" do
userr = FactoryGirl.build(:user)
create_args = nil
UserGraph.should_receive(:create) { |*args| create_args = args }
userr.save
expect(create_args).to eq(:user_id => userr.id)
end
Essentially, this moves the expectation about what the arguments should be so that it comes after the record has been saved, when the record has an id.
The Problem is that the userr you build with FactoryGirl does not have an ID. Thats why the expectation tells you that you expected :user_id=>nil. The ID will be generated when AR saves the record, so there is no way that you can guess the generated ID ahead of time. You could use a less restrictive assertion on the mock:
UserGraph.should_receive(:create).with(hash_including(:user_id))
This will verify that a hash is passed with a :user_id key. You can find more about hash_including here: http://rubydoc.info/gems/rspec-mocks/RSpec/Mocks/ArgumentMatchers:hash_including
Another thing you can try (not sure if it works) is to match against the kind_of matcher of rspec. This would make sure that a number was passed with :user_id
UserGraph.should_receive(:create).with(:user_id => kind_of(Numeric))

How to get a (Ruby) DataMapper custom type to work?

I have a SchoolDay class that represents a school day: it can tell you the date, the semester, the term, the week, and the day. It can generate a string like "Sem1 13A Fri". To store these objects in the database, I want them serialized as a string.
Here is my DataMapper custom type code. I've sort of scraped ideas from the code in dm-types because (disappointingly) there is no real documentation for creating custom types. Sorry it's long.
module DataMapper
class Property
class SchoolDay < DataMapper::Property::String
#load_as ::SchoolRecord::DomainObjects::SchoolDay
# Commented out: the 'load_as' method is not found
def load(value)
# Take a string from the database and load it. We need a calendar!
val = case value
when ::String then calendar.schoolday(value)
when ::SR::DO::SchoolDay then value
else
# fail
end
end
def dump(value)
# Store a SchoolDay value into the database as a string.
case value
when SR::DO::SchoolDay
sd = value
"Sem#{sd.semester} #{sd.weekstr} #{sd.day}"
when ::String
value
else
# fail
end
end
def typecast(value)
# I don't know what this is supposed to do -- that is, when and why it
# is called -- but I am aping the behaviour of the Regexp custom type,
# which, like this one, stores as a String and loads as something else.
load(value)
end
# private methods calendar() and error_message() omitted
end
end
end
This code works for reading from the (SQLite) database, but not for creating new rows. The error message is:
Schoolday must be of type String
The code that defines the DataMapper resource and tries to create the record is:
class LessonDescription
include DataMapper::Resource
property :id, Serial
property :schoolday, SchoolDay # "Sem1 3A Fri"
property :class_label, String # "10"
property :period, Integer # (0..6), 0 being before school
property :description, Text # "Completed yesterday's worksheet. hw:(4-07)"
end
# ...
ld = LessonDescription.create(
schoolday: #schoolday,
class_label: #class_label,
period: #period,
description: description
)
Here is the code for the Regexp datamapper type in the dm-types library. It's so simple!
module DataMapper
class Property
class Regexp < String
load_as ::Regexp # NOTE THIS LINE
def load(value)
::Regexp.new(value) unless value.nil?
end
def dump(value)
value.source unless value.nil?
end
def typecast(value)
load(value)
end
end
end
end
For some reason, I cannot use the load_as line in my code.
To summarise: I am trying to create a custom type that translates between a SchoolDay (domain object) and a String (database representation). The translation is easy, and I've copied the code structure primarily from the DataMapper Regexp type. But when I try to save a SchoolDay, it complains that I'm not giving it a string. Frustratingly, I can't use the "load_as" method that the built-in and custom types all use, even though I have the latest gem. I can't find the "load_as" method defined anywhere in the source code for DataMapper, either. But it's called!
Sorry for the ridiculous length. Any help would be greatly appreciated, as would a pointer to a guide for creating these things that I have somehow missed.
It seems that the current code of dm-types at github hasn't made it to any official release -- that's why load_as doesn't work in your example. But try to add this method:
module DataMapper
class Property
class SchoolDay < DataMapper::Property::String
def custom?
true
end
end
end
end
That's working here.

ruby activerecord how to use alternate updated_at column

I am trying to use Rails3 with an existing db schema. The timestamp fields updated_at and created_at need to be mapped to created and modified on the db.
Is there a way to tell activerecord to use alternate column names?
Thanks in advance, Chris
EDIT
Could it be done by having this in a file in the model directory:
module ActiveRecord
module Timestamp
def timestamp_attributes_for_update #:nodoc:
[:modified, :updated_on]
end
def timestamp_attributes_for_create #:nodoc:
[:created, :created_on]
end
end
end
I don't think there is a way to do that without overriding timestamp_attributes_for_update in ActiveRecord:: Timestamp.
A simple workaround would be to use a before_update callback in the model.
before_update :set_modified_time
private
def set_modified_time
self.modified = Time.now
end

Resources