rspec assigns converts hash keys to string - ruby-on-rails

I have a controller action where i am assigning a hash to an instance variable. In my rspec test file, i am using assigns to test it the instance variable is assigned to the value i expect. For some reason, assigns gives me a hash with string keys. If i print the instance variable in the controller, i has symbol keys
Please find the code below. It is simplified.
class TestController < ApplicationController
def test
#test_object = {:id => 1, :value => 2, :name => "name"}
end
end
My test file:
describe TestController do
it "should assign test_object" do
get :test
assigns(:test_object).should == {:id => 1, :value => 2, :name => "name"}
end
end
The above test fails with the error message
expected: {:id=>1, :value=>2, :name=>"name"}
got: {"id"=>1, "value"=>2, "name"=>"name"}
Please help me understand why it is doing that.

RSpec borrows assigns from the regular Rails test/unit helpers and it's using with_indifferent_access to return the requested instance variable as in assigns(:my_var).
Hash#with_indifferent_access returns a key-stringified version of the hash (a deep copy), which has the side effect of stringfiying the keys of instance variables that are hashes.
If you try to match the entire hash, it will fail, but it works if you are checking the values of specific keys, whether they're a symbol or a string.
Maybe an example will help clarify:
{:a => {:b => "bravo"}}.with_indifferent_access => {"a"=>{"b"=>"bravo"}}
{:a => {:b => "bravo"}}.with_indifferent_access[:a][:b] => "bravo"

Related

Rails console compare model instances

Is there a way to compare two instances of model like
Model.compare_by_name("model1", "model2") which would list the differing column fields
You can use ActiveRecord::Diff if you want a mapping of all the fields that differ and their values.
alice = User.create(:name => 'alice', :email_address => 'alice#example.org')
bob = User.create(:name => 'bob', :email_address => 'bob#example.org')
alice.diff?(bob) # => true
alice.diff(bob) # => {:name => ['alice', 'bob'], :email_address => ['alice#example.org', 'bob#example.org']}
alice.diff({:name => 'eve'}) # => {:name => ['alice', 'eve']}
There is no standard comparator for this. The standard ActiveModel comparator:
Returns true if comparison_object is the same exact object, or comparison_object is of the same type and self has an ID and it is equal to comparison_object.id.
You can write your own by using Hash#diff from activesupport. Something like the following should hopefully get you started:
def Model.compare_by_name(model1, model2)
find_by_name(model1).attributes.diff(find_by_name(model2).attributes)
end
Without using a library or defining a custom method, you can easily get a diff between two models.
For instance,
a = Foo.first
b = Foo.second
a.attributes = b.attributes
a.changes #=> {"id" => [1,2] }

Mongoid: Added Hash to Model but can't write to it

I've got a model, Entity.
class Entity
include Mongoid::Document
field :x
field :y
field :z, type => Hash, :default => {} # new field
end
I added a new field to it, a hash. When I try to use it, I get an error. My code is:
e = Entity.first
if e.z["a"] # if there is a key of this in it?
e.z["a"] = e.z["a"] + 1
else
e.z["a"] = 1
end
But, this error with an undefined method get for hash. If I try to create an initializer for it, to set the values in an existing document, it errors with the same error. What am I doing wrong?
Initializer looks like:
e = Entity.first
e.write_attribute(:z, {})
Thanks
Sorted it.
It seems the answer is to set in Mongoid 1.9.5 the hash to:
field :hash_field, :type => Hash, :default => Hash.new
and it can access and initialize it. Not quite understanding why, but happy to have the answer !

RoR: attr_encrypted assignment doesn't get written?

I'm using attr_encrypted (v 1.2.0) in RoR 3.0.5 to encrypt credentials that I don't want appearing as plain text in my db. When I update the encrypted field, it appears that it's not getting saved into the db.
My model is essentially:
class Service << ActiveRecord::Base
attr_encrypted :credentials_aux, :key => KEY, :attribute => 'encrypted_credentials', :encode => true, :marshal => true
def credentials
credentials_aux
end
def credentials=(c)
h = {}.update(c) # coerce HashWithIndifferentAccess to a vanilla hash
credentials_aux = h
end
...
end
(Note that the 'credentials=' method exists simply to coerce a Rails-generated HashWithIndifferentAccess into a vanilla hash. It also gives me a place to interpose debugging printout to verify my data.)
But when I try updating credentials via the console, it doesn't take:
>> s = Service.find(19)
=> #<Service id: 19, encrypted_credentials: "10VfHU7IkdrFb4Q6Hj18YtY81rbRp3sIuoVUl8CHNj88cq1XFo2...",>
>> s.credentials
=> {"user_id"=>"fred.flintstone", "password"=>"supersecret"}
>> s.credentials = {"user_id" => "barney.rubble", "password" => "notsosecret"}
=> {"user_id" => "barney.rubble", "password" => "notsosecret"}
>> s.credentials
=> {"user_id"=>"fred.flintstone", "password"=>"supersecret"}
Why didn't s.credentials get updated to the new value?
I believe you're just setting a local variable called "credentials_aux" in your "credentials=" method, try explicitly using "self"
def credentials=(c)
h = {}.update(c) # coerce HashWithIndifferentAccess to a vanilla hash
self.credentials_aux = h
end
I'm actually the maintainer of that project, so if the fix above doesn't work then I'll open up a new ticket for this.

Is there find_or_create_by_ that takes a hash in Rails?

Here's some of my production code (I had to force line breaks):
task = Task.find_or_create_by_username_and_timestamp_and_des \
cription_and_driver_spec_and_driver_spec_origin(username,tim \
estamp,description,driver_spec,driver_spec_origin)
Yes, I'm trying to find or create a unique ActiveRecord::Base object. But in current form it's very ugly. Instead, I'd like to use something like this:
task = Task.SOME_METHOD :username => username, :timestamp => timestamp ...
I know about find_by_something key=>value, but it's not an option here. I need all values to be unique. Is there a method that'll do the same as find_or_create_by, but take a hash as an input? Or something else with similat semantics?
Rails 3.2 first introduced first_or_create to ActiveRecord. Not only does it have the requested functionality, but it also fits in the rest of the ActiveRecord relations:
Task.where(attributes).first_or_create
In Rails 3.0 and 3.1:
Task.where(attributes).first || Task.create(attributes)
In Rails 2.1 - 2.3:
Task.first(:conditions => attributes) || Task.create(attributes)
In the older versions, you could always write a method called find_or_create to encapsulate this if you'd like. Definitely done it myself in the past:
class Task
def self.find_or_create(attributes)
# add one of the implementations above
end
end
I also extend the #wuputah's method to take in an array of hashes, which is very useful when used inside db/seeds.rb
class ActiveRecord::Base
def self.find_or_create(attributes)
if attributes.is_a?(Array)
attributes.each do |attr|
self.find_or_create(attr)
end
else
self.first(:conditions => attributes) || self.create(attributes)
end
end
end
# Example
Country.find_or_create({:name => 'Aland Islands', :iso_code => 'AX'})
# take array of hashes
Country.find_or_create([
{:name => 'Aland Islands', :iso_code => 'AX'},
{:name => 'Albania', :iso_code => 'AL'},
{:name => 'Algeria', :iso_code => 'DZ'}
])

How can I add multiple should_receive expectations on an object using RSpec?

In my Rails controller, I'm creating multiple instances of the same model class. I want to add some RSpec expectations so I can test that it is creating the correct number with the correct parameters. So, here's what I have in my spec:
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => #user.id, :position_id => 1, :is_leader => true)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "2222", :position_id => 2)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "3333", :position_id => 3)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "4444", :position_id => 4)
This is causing problems because it seems that the Bandmate class can only have 1 "should_receive" expectation set on it. So, when I run the example, I get the following error:
Spec::Mocks::MockExpectationError in 'BandsController should create all the bandmates when created'
Mock 'Class' expected :create with ({:band_id=>1014, :user_id=>999, :position_id=>1, :is_leader=>true}) but received it with ({:band_id=>1014, :user_id=>"2222", :position_id=>"2"})
Those are the correct parameters for the second call to create, but RSpec is testing against the wrong parameters.
Does anyone know how I can set up my should_receive expectations to allow multiple different calls?
Multiple expectations are not a problem at all. What you're running into are ordering problems, given your specific args on unordered expectations. Check this page for details on ordering expectations.
The short story is that you should add .ordered to the end of each of your expectations.
Mock Receive Counts
my_mock.should_receive(:sym).once
my_mock.should_receive(:sym).twice
my_mock.should_receive(:sym).exactly(n).times
my_mock.should_receive(:sym).at_least(:once)
my_mock.should_receive(:sym).at_least(:twice)
my_mock.should_receive(:sym).at_least(n).times
my_mock.should_receive(:sym).at_most(:once)
my_mock.should_receive(:sym).at_most(:twice)
my_mock.should_receive(:sym).at_most(n).times
my_mock.should_receive(:sym).any_number_of_times
Works for rspec 2.5 too.

Resources