I try to run a scenario with Cucumber, through Capybara, in a Rails 3.2.3 app supported by Mongoid. The aim is to have current user add a book to his collection.
Everything goes ok, but the final step definition, where I check that the amount of books is now one, fails.
But if I check on the app controller, the size actually increased. And actually, when I send reload to the user in the step definition, it passes:
user.reload.books(true).size.should == 1
I'm afraid this behavior could harm my app once in production. Any advice how to make sure all tests and app behaviors are consistent?
UPDATE
I checked the test.log to see what's going on.
Calling reload I get this query to MongoDB:
find({"count"=>"books",
"query"=>{:_id=>{"$in"=>[BSON::ObjectId('4f889b473dffd63235000004')]}},
"fields"=>nil}).limit(-1)
while without the reload I get this:
find({"count"=>"books", "query"=>{:_id=>{"$in"=>[]}}, "fields"=>nil}).limit(-1)
It practically doesn't query against the user if I don't reload the model, which doesn't make much sense to me.
The following works for me (updated with actual Cucumber example)
I built a Rails project to test out your issue, rails 3.2.3, mongoid 2.4.8, mongo 1.6.2, mongodb 2.0.4, cucumber 1.1.9.
The following (association generated methods) work as expected, without need for refresh:
user.books << book
book.users << user
Then I tried to bypass the association, which was what I thought that you were doing.
user.push(:book_ids, book.id)
book.push(:user_ids, user.id)
These DO bypass the association, resulting in incomplete (one-way instead of two-way) references, but the memory and db state is consistent. So my guess in my previous answer about what you were experiencing was wrong, there's no refresh needed and you are probably doing something else. Note that you/we do not want the incomplete references, please do not push directly to the internals for Mongoid referenced relations.
Are you using the association append "<<" for adding a user or a book? My current conclusion is that Mongoid referenced relations work as advertized for my test of your issue. There's no need for refresh.
Here's the model:
class User
include Mongoid::Document
field :first_name, type: String
field :last_name, type: String
has_and_belongs_to_many :books
end
class Book
include Mongoid::Document
field :title, type: String
field :author, type: String
has_and_belongs_to_many :users
end
Cucumber feature
Feature: UserAndBook
Test adding a book to a user_s books
Scenario: add_book_to_user
Given starting with no users and no books
And a new user
And that the new user has no books
And a new book
And add book to user
Then I can check that the user has a book
Cucumber steps
require 'test/unit/assertions'
require File.expand_path('../../../test/test_helper', __FILE__)
World(Test::Unit::Assertions)
Given 'starting with no users and no books' do
User.delete_all
Book.delete_all
assert_equal(0, User.count)
assert_equal(0, Book.count)
end
Given 'a new user' do
#user = User.create(first_name: 'Gary', last_name: 'Murakami')
end
Given 'that the new user has no books' do
assert_equal(0, #user.books.count)
end
Given 'a new book' do
#book = Book.create(title: 'A Tale of Two Cities', author: 'Charles Dickens')
end
Given 'add book to user' do
#user.books << #book
end
Then 'I can check that the user has a book' do
assert_equal(1, #user.books.count)
end
I'm open to further exchange of info to help to address your issue.
Blessings,
-Gary
P.S. Looking at the log, it is interesting to see that user.books.length does an actual db "find count $in" query rather than a local array length.
Previous answer
You've pretty much answered your own question. In Rails, you need to use the reload method whenever data for your model has changed in the database, otherwise you will just be looking at the previously loaded/instantiated/cached state of your model. With update of just an attribute, things look pretty consistent, but associations are more complicated and the inconsistency becomes more obvious.
Related
Quick one.
I am using Searchkick gem for searching in my Rails app. I notice if I try search some text in the Post model it works fine.
Everything is set up correctly however, say I wish to search for author of the Post. It's example relation is Post.user.name
When I search "users name" I get no results. I suspect it is to do with the data being in the User model and not the Post model but in my Post views I can see this as a string.
Is this something simple I am missing? I thought addding
searchkick inheritance: true
to my Post model and reindexing the Post and User model then it would work but nothings changed.
Any ideas? Thanks.
Searchkick is backed by ElasticSearch. All of the searching happens there - not your database. You need to get the data you want to search on in to ElasticSearch somehow. Searchkick provides this via indexing your data with the search_data method (https://github.com/ankane/searchkick#indexing). So if you had an association that had a field you wanted to search on, you can follow something similar to the example set forth on Searchkick's Github page, and do (for your case):
class Post
belongs_to :author, class_name: 'User' # or whatever your association is
def search_data
{
title: title,
author_name: author.name
}
end
end
Don't forget to reindex afterwards, so you can see the results.
Post.reindex
And don't forget that if your association's data changes, like if your Author's name goes from Bob to Robert, the data is not automatically synced. You need to manually call Post.reindex, or set up an after_commit hook on Author that calls out to reindex Post when an author changes. See the docs for more information about that.
I've been struggling the last couple days trying to create an import process for users to upload CSV/Excel files. I have been trying to imitate the process done in this Railscast episode. However, my requirements are a bit more complex so I have been trying to modify what has been done there but I have hit a wall when it comes to enforcing model validation to ensure against any bad data.
I am trying to use Roo to allow my users the ability to upload CSV or Excel files. I also would like to save the OrderImport model with the uploaded spreadsheet and some other information for historical reference and what not.
Models
property.rb
has_many :owners
has_many :orders
owner.rb
belongs_to :property
order.rb
belongs_to :property
has_many :owners, through: :property
order_import.rb
#no assications
#uses paperclip to save the attached file
Order Import process in order_import.rb
def load_imported_orders(file, cid, uid) #loads file from controller along with client_id and user_id for use in order creation
spreadsheet = Roo::Spreadsheet.open(file.path)
header = spreadsheet.row(1)
order_imports = (2..spreadsheet.last_row).map do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
property = row["address"]
propparse = import_address_parse(property) #to parse submitted address
newprop = if Property.exists?(propparse)
Property.find_by(propparse)
else
Property.new(propparse)
end
owner = row["owner"]
newowner = Owner.find_or_initialize_by(name: owner)
newprop.owners << newowner #creates new property/owner association
neworder = Order.new(property_id: newprop.id,
client_id: cid,
task: "Property Report",
submitted_by: uid)
neworder
newprop
newowner
end
#imported_orders = order_imports
end
def import_address_parse(property)
address = StreetAddress::US.parse(property)
if address.blank?
new_blank_prop = { address1: nil,
city: nil,
state: nil,
zipcode: nil }
else
new_prop = { address1: address.to_s(:line1),
city: address.city,
state: address.state_name,
zipcode: address.postal_code}
end
end
def save
if #imported_orders.map(&:valid?).all?
#imported_orders.each(&:save!)
true
else
#imported_orders.each_with_index do |order, index|
if order.instance_of?(Property) #needed to reduce the amount of errors shown on the form
errors.add :base, "Row #{index+2}: Please re-check the Address of your Property."
else
order.errors.full_messages.each do |message| #shows any errors not related to property
errors.add :base, "Row #{index+2}: #{message} | #{order}"
end
end
end
false
end
end
order_import controller
def create
#order_import = OrderImport.new(params[:order_import_params])
#order_import.load_imported_orders(params[:order_import][:document], params[:order_import][:client_id], params[:order_import][:submitted_by])
if #order_import.save
redirect_to external_client_orders_path(current_user.client_id), notice: "Imported Orders successfully."
else
render :new
end
end
So as you can see, the load_imported_orders() method gets called from the controller and parses the spreadsheet. Then a property & owner are found (if they happen to exist already) or initialized and then an order is initialized from the items in that row. (I've tried using something like a :before_create filter in my OrderImport model but I have no idea how these different guides are opening files passed through OrderImport.new(params[:order_import_params]) without the model being saved first - and of course I don't want to save unless everything imports correctly).
The purpose for the import_address_parse method is because the address is submitted as one line (555 test rd, testington, tt, 55555) but is comprised of address1, city, state, zipcode in my database. The StreetAddress gem will return nil if it cannot parse the entire address string so I have put that catch in there to return a nil object in hopes to fail the property model validations when Property.new is initialized with all nil values in those fields.
The problems
For some reason the Property validation does not fail on its own, I am only alerted when owners go to be saved and associated to the new property in the save method. Property.exists?() with all nil attributes still loads a property for some reason instead of initializing Property.new and I can't for the life of me figure out why.
The order validation fails because a no property.id exists since the new property hasn't been saved yet, which I understand, but I'm not sure how create the Order association/creation from the save method (i.e. outside of the load_import_orders method which is parsing the import data).
I think my understanding of the entire validation side of importing bulk records is fundamentally wrong, especially since I cannot get the OrderImport record to save correctly even when adding create_or_update (Rails API docs) to my save method, which overwrites the default rails save method for the model.
This may be easier than I'm making it out to be but I would consider myself to still be an amateur Rails developer (having taught myself everything through tutorials, SO posts, guides/articles) and am diving into what I think is complex methodology so if someone could take the time to assist me with refining this process so that I can achieve what my goal is here and attain a deeper understanding of rails in general.
Added caveat, if I change all the find_or_initialize_by / new calls to create! and ONLY use data I know would pass all validations then this process works as intended but we all know thats not realistic in a real-world environment. I think since that part works its throwing me off from re-engineering this a bit (it's not the end of the world if using built in rails CSV.parse instead of Roo is the way to go but I'd like to provide the option of being able to import from excel but its not a necessary one).
Thank you to anyone who takes the time to break this down and help me out.
So after posting this question with no responses, I decided to brush up on my Ruby IRB skills to work my way through the process and analyze the data every step of the way.
After much trial and error, I've finally completed this task!
To provide a brief overview of my final process:
Following the methodology of the answer in this SO question, Ruby on Rails: Validate CSV file, I decided to
Break out the validations into its own Ruby class.
Using Roo's Parse method, I passed each row as a Hash to OrderImportValidator.new()
Pass the row[:property] pair through my import_address_parse method.
Instantiate both row[:property] && row[:owner] using .find_or_initialize_by method to their respective models.
Pair up each :property and :owner objects into a hash within a OrderImportValidator instance variable (#order_objects) and allow access to it from my OrderImport model via a method (very similar to how the #errors is returned in the linked SO CSV question).
Verify validation of all values (instantiated objects) in the returned hash.
If all were good, then I called save on each, create the correct associations, and appropriate order for each pair.
Added a custom validate :import_error_check, on: :create to my OrderImport model.
Run through each.with_index, check if errors exist, clear the errors on the object and add my custom message to errors[:base] of Order Import with their appropriate index and display them on the page.
Pretty pumped I figured it out and have furthered my knowledge of data manipulation, objects, and ruby syntax. Maybe the outline of my process will help someone else one day. Cheers.
I have two interdependent models, account and user. An account is always created by a user, whose id is thus stored in the account's creator_id attribute, and a user necessarily belongs to an account (but there's no limit on the number of users belonging to an account), this information being stored in user's account_id attribute. The same user can have created different accounts.
I expressed these rules this way :
User model :
belongs_to :account, inverse_of: :users
has_many :created_accounts, class_name: "Account", :foreign_key => "creator_id"
Account model :
belongs_to :creator, class_name: 'User', optional: true
has_many :users, inverse_of: :account
As they are interdependent, I used a dirty workaround to be able to instantiate them : I create first the account, and following that action I force the user to create their profile, and their user_id is added to the account as creator_id in an update.
That's why I have, in Account model :
validate :require_actual_creator_id, on: :update
------------------------------------------------
def require_actual_creator_id
User.find(creator_id)
end
I was working on an authentication system only involving the user model, so I had these lines commented out until yesterday when I uncommented them.
I ran db:migrate:reset, db:seed and db:migrate RAILS_ENV=test without any problem, both models have a normal behavior in the console, but when it comes to fixtures (e.g. testing or db:fixtures:load), I got the following error :
NoMethodError: undefined method `id' for nil:NilClass
/home/vincent/workspace/bam-rails/test/fixtures/users.yml:16:in `get_binding'
Here is one typical fixture causing the problem, the line 16 being the commented one :
michael:
id: 1
handle: Michael Example
email: michael#example.com
encrypted_password: <%= User.generate_encrypted_token('password') %>
role_id: <%= User::ADMIN %>
is_activated: true
activated_at: <%= DateTime.now %>
#account_id: <%#= Account.first.id %>
When I comment this last line, there's no problem anymore. However, I'd like to load proper fixtures for my tests, because for example the user created here is not valid.
I read in this post that the fixtures load in the alphabetical order. If this is right, I can't get why I have to comment this line, because accounts is supposed to be loaded before users.
I found that solution to work but it's not from the official documentation and it's quite old, dated back to 2007. I am afraid this would stop working from one day to the next.
Does anyone know how to properly load the fixtures in a custom order in Rails 5, or has another solution to my problem ?
Thank you in advance.
The problem you have is entirely stemming from how you organized your code. That work around where you create the first account is where you are having an issue. So the fact that by the time your user is instantiated your account does not exist first because the fixtures are not loaded yet; of this I am sure you are aware. Fixtures are notoriously brittle this is why people often move away from them the more complex their code gets. In this case though the are helping you expose a code smell, anytime the order or running you test or basic non test case specific set up causes issues that means you have an issue with your code. I would suggest you find a way around using this "dirty work around".
Now if for some reason you are married to the way your code is currently organized I suggest maybe switching to factory girl, it will give you a little more flexibility to control the point at which your mock objects are instantiated that way you wont run into this issue. I will however say this will just enable you to continue you down this path that will more than likely just lead to more issues down the road, your best bet is to reimplement the feature.
I have the following model:
class Kueue < ActiveRecord::Base
attr_accessible :name, :user_id
belongs_to :user
has_and_belongs_to_many :photos
scope :empty, includes(:photos).where{ photos.id.eq(nil) }
scope :with_photos, includes(:photos).where{ photos.id.not_eq(nil) }
end
I want to write specs for the scopes, to make sure they're reliably working. My problem is how to deal with the Photo model. Photos have a lot of validations on them, for instance they must belong_to a User. So, I can write a working spec for these queries like so:
describe "queries" do
before :each do
#empty = Fabricate(:kueue)
#full = Kueue.new :name => "Full Queue"
#full.photos << Fabricate(:photo)
#full.save
end
it "should find empty queues" do
Kueue.empty.should_not include(#full)
end
it "should find queues with photos" do
Kueue.with_photos.should_not include(#empty)
end
end
However, as you can imagine this is sloooow. This makes a bunch of calls to the database. (1) to make two kueues, (2) to make a photo, (3) to make a user who owns the photo... and so on, down the line.
This seems like a simple enough problem, all I need is one join record between a photo and a kueue and I can test this really fast. So how would you go about mocking this interaction so you can test it faster and in better isolation?
PS: I'm using Rails 3.2.8, Squeel (hence the query syntax) and my model is called Kueue because Queue is a reserved word in Rails :)
i think that it would not make sense to mock in the context of scopes.
scopes are basically database-queries and you want to make sure they work with, you know, the database.
so if your problem is test-performance, then i would suggest:
try out fixtures in this case
OR
skip model validation, to reduce the models needed
I am using Cucumber for BDD development in my Ruby on Rails project and I'm running into some confusion on how the path.rb handles paths used in rails applications.
Given I have:
class Parent < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :parent
end
and I have the following Cucumber feature:
Scenario: A test feature
Given I am on the parent page
When I follow "Link to Children"
Then I should be on the children list page
with the path defined as:
def path_to(page_name)
case page_name
when /the children list page/
'/parents/:id/children'
end
The problem I come across is the following error when running the feature:
Spec::Expectations::ExpectationNotMetError: expected: "/parents/:id/children",
got: "/parents/1726/children" (using ==)
I don't really care what the :id is. What should I do instead? Is this even possible with the default web steps? Am I thinking about the problem in the wrong way?
The way I do it, which may not be the best way is as follows:
when /the children list page for "(.+)"/
p = Parent.find_by_name($1)
parent_children_path(p)
In our app, we always wanted a new record in the database whenever a user clicked the "new" button. Thus, our controller's new action automatically calls create and then redirects to the edit action.
We faced a similar problem in testing, when we didn't care so much about what the ID was -- just that it got to the edit page for the app.
Here's what I came up with.
(NOTE: The step definition is written using capybara, but it shouldn't be too different from webrat)
Then /^(?:|I )should now be editing the (.*)$/ do |model|
id = find_by_id("#{model}_id").value
Then "I should be on the edit #{model} page for \"#{id}\""
end
The basic premise is that when you're on a Rails edit page, there will be a form for the model you're editing. That form always contains a hidden field with the ID of the specific record you're editing.
The step finds the hidden field, extracts the ID from it, and then looks for a web_step to resolve the path for that model.
Just make sure you have a path that matches for the model you're looking up.
when /the edit person page for "([^\"]*)"/
edit_person_path($1)