RSpec assigns not working as expected - ruby-on-rails

I have the following spec for the controller in simple ActiveRecord search feature:
Spec:
it "returns the records that match the given due date" do
create(:task, due_date: '2013-01-01')
create(:task, due_date: '2014-01-01')
get :search, 'filter' => { due_date: '2013-01-01' }
expect(assigns(:tasks)).to \
eq Task.search('filter' => { due_date: '2013-01-01' })
end
The model and controller are simple:
Model:
def self.search(params)
result = self.all #I know this is a bad idea, but starting simple.
params.each do |field, criteria|
if field.match(/due_date|completed_date/) && criteria != nil
result = result.where("DATE(#{field}) = ?", criteria)
end
end
result
end
Controller action:
def search
#tasks = Task.search(params['filter'])
#output from when the spec runs below
#puts params -> {"filter"=>{"due_date"=>"2013-01-01"}, \
# "controller"=>"tasks", \
# "action"=>"search"}
#puts params['filter] -> {"due_date"=>"2013-01-01"}
#puts #tasks.inspect -> just the one record
render 'index'
end
The spec fails, but it appears that it fails because the controller is returning both objects, while Task.search(...) is returning only the object with the specified value for due_date, as expected.
Here is the error message (edited for length):
2) TasksController GET #search returns the records that
match the given due date
Failure/Error: expect(assigns(:tasks)).to
eq Task.search('filter' => { due_date: '2013-01-01' })
expected: #<ActiveRecord::Relation
[#<Task id: 1,
due_date: "2013-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">,
#<Task id: 2, due_date: "2014-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">]>
got: #<ActiveRecord::Relation
[#<Task id: 1,
due_date: "2013-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">]>
You would assume that since the model apparently works (as evidenced by this result and a separate model spec that passes) that there is something wrong with the controller, but the controller is dead simple. I also have a feature spec incorporating the same controller that submits a form, triggers the search action and looks at the output, and the output only includes the one, correct record.
Am I missing something about how assigns works, making a dumb mistake or other?

It was option B, dumb mistake.
The model method takes the value of the filter element of the params hash as an argument, not the fake params hash I need to send to GET #searchin the line above the expectation. Or more clearly maybe, replace:
expect(assigns(:tasks)).to eq Task.search('filter' => { due_date: '2013-01-01' })
with
expect(assigns(:tasks)).to eq Task.search(due_date: '2013-01-01')'

Related

Sending json nested within json to a Rails API

In a Rails 4.1.6 app that functions only as a JSON API,
I have a field "json_data" of type text in a database table called 'cards'.
To my understanding, putting the following code in the Card model...
serialize :json_data, JSON
...makes strings stored in this json_data field output properly as JSON (and not a string with escaped quotes) whenever I return the json_data to some requesting client, like when I do
json.cards #cards do |card|
json.id card.id
json.title card.title
json.json_data card.json_data
end
in a index.json.jbuilder view file, for example.
What is confusing is that I am sending POST and PUT requests of this format to create or update cards:
{
"card":{"title":"card 124","json_data":{"text": "hi"}}
}
But json_data is empty each time. A validation for presence of json_data fails!
If, instead, I send the following:
{
"card":{"title":"card 124","json_data":"{\"text\": \"hi\"}"}
}
json_data is interpreted correctly, but the data is stored in the database as json_data: "{\"text\":\"hi\"}", with the backslashes, which I think is undesirable.
When looking around the database with rails console, shouldn't json_data be presented like this?
#<Card id: 1, title: "Card 1", json_data: {"text"=>"Lorem ipsum figaro ci yei."}, beacon_owner_id: 1, template_id: 1, created_at: "2015-03-04 19:56:36", updated_at: "2015-03-04 19:56:36">, #<Card id: 2, title: "Card 2", json_data: {"text"=>"Pre va ipsum figaro ci yei.", "image_link"=>"http://i.imgur.com/sNRv0Jq.png"}, beacon_owner_id: 1, template_id: 2, created_at: "2015-03-04 19:56:36", updated_at: "2015-03-04 19:56:36">
And not like this?
#<Card id: 4, title: "new card", json_data: "{\"text\": \"hi\"}", beacon_owner_id: 1, template_id: 1, created_at: "2015-03-23 02:51:33", updated_at: "2015-03-23 02:51:33">
What is the best way to accept POST/PUT requests where parameters involve JSON for a specific text column? Does it have to be an escaped string (because as described above, if it's directly just more nested JSON, then Rails' validation for the presence of the field fails)?
==EDIT==
Added the controller:
class API::V1::CardsController < API::BaseController
before_filter :authenticate_user_from_token!
before_action :set_card, only: [:show, :update, :destroy]
def show
respond_with(#card)
end
def index
#cards = current_beacon_owner.cards
respond_with(#cards)
end
def create
#card = Card.new(card_params)
#card.beacon_owner_id = current_beacon_owner.id
#card.save
respond_with(#card)
end
def update
#card.update(card_params)
respond_with(#card)
end
def destroy
#card.destroy
respond_with(#card)
end
private
def set_card
#card = Card.find(params[:id])
end
def card_params
puts '-----------------------------------------'
puts "params: #{params}"
puts '-----------------------------------------'
params.require(:card).permit(:title, :json_data, :template_id)
end
end
The knowledge I was missing was about Rails' strong parameters - we're supposed to whitelist nested parameters: https://github.com/rails/strong_parameters#nested-parameters
My controller's card_params was not allowing the nested parameters, hence the validates-presence-of validation failing. Fixing this lets me store data in the database without all the escaping, which is the right way.
Side note: another solution, if using Rails 4.x and postgres, is to use the :json column type for the field.

Rails console is not showing attribute when called

>> Reply.first
=> #< Reply id: 1, body: "line1\r\n\r\nline2\r\n" >
But when I do
>> Reply.first.body
=> "line1"
Its breaking a few of my tests where they are looking for :
assert_difference 'Reply.where(:body => "line1\r\n\r\nline2").count' do
How can my tests be reassured there are line breaks?
Seems like you have a custom getter, something like:
class Reply < ActiveRecord::Base
def body
"foo"
end
end
reply = Reply.new(body: "bar")
#=> #<Reply id:nil, body: "bar" created_at: nil, updated_at: nil>
reply.body
#=> "foo"
In that case, you can fetch the raw attribute using Model[:attribute_name]:
reply[:body]
#=> "bar"
Change the snytax a little bit when you have backslash's
assert_difference 'Reply.where("body = 'line1\r\n\r\nline2\r\n'").count' do

Sort Ruby array using the Post objects it contains

I have a Ruby array which contains Post objects (a Mongoid model) with a created_at method returning a Time object. What I want is to sort that array using that method (which is present on every element on the array). I tried #posts.sort_by {|post| post.created_at} but that didn't work. How could I achieve this? Thanks!
EDIT: Example of the array:
[#<Post _id: 4ffd5184e47512e60b000003, _type: nil, created_at: 2012-07-11 10:12:20 UTC, title: "TestSecond", description: "TestSecond", slug: "assa", user_id: 4ffc4fd8e47512aa14000003>, #<Post _id: 4ffd518ee47512e60b000004, _type: nil, created_at: 2012-07-11 10:12:30 UTC, title: "TestFirst", description: "TestFirst", slug: "assadd", user_id: 4ffc4fd8e47512aa14000003>].
EDIT #2: I'm getting that #posts arra with:
#posts = Array.new
#user.following.each do |user|
user.posts.each do |p|
#posts << p
end
end
I'm not sure why you have an array here. Normally, with Mongoid querying, you should have a Mongoid::Criteria instance, which you can desc(:created_by) or asc(:created_by) on, in order to sort.
Still, can't think of any reason sort_by doesn't work for properly for you, works great on my app (just tried at console).
UPD:
#user.following.each do |user|
user.posts.each do |p|
#posts << p
end
end
Well, to still have a mongoid collection there, something like this might be done:
#posts = #user.following.map(&:posts).inject { |posts, post| posts.concat post }
Now, you can #posts.desc(:created_at).
UPD2:
Also, for purpose of having a cleaner code, you could define a posts_from_following on User:
class User
def posts_from_following
following.map(&:posts).inject { |posts, post| posts.concat post }
end
end
and then, just do
#posts = #user.posts_from_following.desc(:created_at)
in your controller.
#posts.sort_by &:created_at
#posts.sort {|a, b| a.created_at <=> b.created_at }

Rails: #inspect not displaying attribute

I am defining #foo as a class instance attribute, and using the after_initialize callback to set the value of this when a record is created/loaded:
class Blog < ActiveRecord::Base
#foo = nil
after_initialize :assign_value
def assign_value
#foo = 'bar'
end
end
However, when I inspect a Blog object, I am not seeing the #foo attribute:
> Blog.first.inspect
=> "#<Blog id: 1, title: 'Test', created_at: nil, updated_at: nil>"
What do I need to do to get inspect to include this? Or conversely, how does inspect determine what to output?
Thanks.
Active record determines which attributes to show in inspect based on the columns in the database table:
def inspect
attributes_as_nice_string = self.class.column_names.collect { |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
end
}.compact.join(", ")
"#<#{self.class} #{attributes_as_nice_string}>"
end
Lifted from base.rb on github
To change the output of inspect you'll have to overwrite it with your own method e.g.
def inspect
"#{super}, #foo = #{#foo}"
end
Which should output:
> Blog.first.inspect
=> "#<Blog id: 1, title: 'Test', created_at: nil, updated_at: nil>, #foo = 'bar'"

Ruby on Rails bizarre behavior with ActiveRecord error handling

Can anyone explain why this happens?
mybox:$ ruby script/console
Loading development environment (Rails 2.3.5)
>> foo = Foo.new
=> #<Foo id: nil, customer_id: nil, created_at: nil, updated_at: nil>
>> bar = Bar.new
=> #<Bar id: nil, bundle_id: nil, alias: nil, real: nil, active: true, list_type: 0, body_record_active: false, created_at: nil, updated_at: nil>
>> bar.save
=> false
>> bar.errors.each_full { |msg| puts msg }
Real can't be blank
Real You must supply a valid email
=> ["Real can't be blank", "Real You must supply a valid email"]
So far that is perfect, that is what i want the error message to read. Now for more:
>> foo.bars << bar
=> [#<Bar id: nil, bundle_id: nil, alias: nil, real: nil, active: true, list_type: 0, body_record_active: false, created_at: nil, updated_at: nil>]
>> foo.save
=> false
>> foo.errors.to_xml
=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error>Bars is invalid</error>\n</errors>\n"
That is what I can't figure out. Why am I getting Bars is invalid versus the error messages displayed above, ["Real can't be blank", "Real you must supply a valid email"] etc.
My controller simply has a respond_to method with the following in it:
format.xml { render :xml => #foo.errors, :status => :unprocessable_entity }
How do I have this output the real error messages so the user has some insight into what they did wrong? How do I write my render method in my controller to show all of the appropriate error messages?
I think you are using
validates_associated :bar in your foo.rb MODEL
so it only giving "Bars is invalid"
to check the error messages for bars either you have to do following in your
VIEW
<%= error_messages_for :foo, :bar %>
Controller
foo.bar.errors.to_xml
& to skip "bar is invalid" message put following method in foo.rb
def after_validation
# Skip errors that won't be useful to the end user
filtered_errors = self.errors.reject{ |err| %w{ bar }.include?(err.first) }
self.errors.clear
filtered_errors.each { |err| self.errors.add(*err) }
end
It's because the errors for bar are stored in the bar object. To get these errors you have to do something like this:
foo.bar.each do |bar|
bar.errors.each_full { |msg| puts msg }
end
It's all convoluted to me, but I haven't figured out the best way to get all the errors in one list (besides handling it my self). I understand the reasoning behind it (as each object should only know about it's own errors). What I usually do is extent ActiveRecord::Errors and create a new each_full_with_associations function that returns them all.
It all makes sense when you see it on a form with nested fields. In that case the errors are shown properly and all is good.
We used to overwrite an errors method in particular model, if we needed errors of child objects too, smth like that
class Foo < ActiveRecord::Base
alias :errors_without_children :errors
def errors
self.bars.each do |i|
i.errors.each_full do |msg|
errors_without_children.add_to_base msg
end
end
errors_without_children
end
end
You can still optimise it more. But this one already adds all bars objects' error messages to foo.

Resources