How to load fixtures in a specific order in Rails 5 - ruby-on-rails

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.

Related

Rails Design Pattern for Multiple Devise Log-Ins Managing One Set of Data

EDIT: What's the design pattern for an app that does the following: A business sets up an account with the app. The business then creates "employees" in the app that can log in separately and CRUD the business's data, except for what their employer marks as off limits?
I've run into a problem with the hostel app. Here we go:
My app, a hostel management SaaS, has many users. These are hostels. The owner of the hostel signs up for my app via Devise. current_user is always the hostel itself.
Hostels have_many guests and each guest belongs_to a user(the hostel). These are people who call/email and want to spend a night in the hostel. Thanks a ton to everyone for helping me with the availability calendar.
All is fine and dandy now. Normally, the hostel owner or manager logs into my app, books rooms, emails guests, sends invoices, upload financials, you name it. However, many have been requesting the ability to add employees that can log in separately and create reservations, send emails, etc, but NOT view financial info. Enter CanCan.
Here's where I'm stuck. It's easy enough to delegate abilities and authorizations. Devise also gives me the ability to set up multiple devise models. However, I'm stuck with how I can give the employee, once they log in, access to their employer's data. The current_user.id is going to be different than their employer's ID(the business that signed up), so how can I tell Devise to use the ID of their user?
class User
has_many :employees
end
#should I do something like this?
class Employee
belongs_to :user
has_many :guests, :through => users
has_many :reservations, :through => users
has_many :rooms, :through => users
end
I thought about doing something like this below:
current_user.id = current_employee.user.id
The only problem is, it smells. There must be a better way. Once the employee logs in, everything is going to look the exact same as when their boss logs in(show all reservations, all guests, all emails, etc), the employee will just be restricted from certain areas.
The same current_user on multiple models in devise is mostly workarounds and hacks. https://leanpub.com/multi-tenancy-rails seemed kind of in the right direction, but it's a bit too much for what I need. There must be a specific design pattern for this situation, but I can't seem to find it or even get Googling in the right direction.
Basically, how do Rails apps give their users the ability to create sub-users, and let those sub-users view the user's data with restrictions?
Any help getting my thoughts straightened out is much appreciated.
Is there anything wrong with a simple override of the current_user method?
class ApplicationController
def current_user
super || current_employee&.user # safe operator for ruby 2.3+, otherwise `current_employee.try(:user)`
end
end

Rails validates_uniqueness_of doesn't seem to be working for me

I'm having a hard time trying to avoid duplicates in my database and really curious to know if I'm actually doing this correctly. In the example below, my technical reports actually work perfectly fine. So there's no duplicate nodes in the technical report, even if I try to create a duplicate manually.
However, I can't say the same for the nodes' vulns. When I import the exact same file (containing data) twice, I get duplicate Vulns for the same IP when I believe it's not supposed to happen.
So here are two models that I have:
#app/models/node.rb
class Node < ActiveRecord::Base
validates_uniqueness_of :technical_report_id, :scope => :ip
has_many :vulns, dependent: :destroy
end
.
#app/models/vuln.rb
class Vuln < ActiveRecord::Base
validates_uniqueness_of :node_id, :scope => [:master_finding_id, :vuln_finding_id, :additional_output, :port]
belongs_to :node
belongs_to :master_finding
belongs_to :vuln_finding
end
However, when I go to importing data, I still find myself with duplicates in the Vuln table. I've used rails c to validate this as well.
irb(main):012:0> Vuln.where(node_id: 12).pluck(:master_finding_id, :additional_output, :vuln_finding_id).length
(0.4ms) SELECT `vulns`.`master_finding_id`, `vulns`.`additional_output`, `vulns`.`vuln_finding_id` FROM `vulns` WHERE `vulns`.`node_id` = 12
=> 2
When I go to call .uniq it shows that there's only one entry.
irb(main):013:0> Vuln.where(node_id: 12).pluck(:master_finding_id, :additional_output, :vuln_finding_id).uniq.length
(0.5ms) SELECT `vulns`.`master_finding_id`, `vulns`.`additional_output`, `vulns`.`vuln_finding_id` FROM `vulns` WHERE `vulns`.`node_id` = 12
=> 1
Does anyone know what I'm doing wrong here? I'm not sure why this works for one model and not the other one. If I try to create two of the exact same Vuln records from rails c CLI, it rolls back like it should have, but not when it's created otherwise.
EDIT
Looks like my issue is with activerecord-import instead. I'm not sure why but it imports nodes just fine, but it calls the "Class Create Many Without Validations Or Callbacks" when it goes to importing vulns. Guess because there's a lot more data or something. I think I have this sort of figured out for now. Perhaps I just need to write a quick method to validate the uniqueness manually since I don't want to get rid of the mass importing gem.
validates_uniqueness_of can not ensure uniqueness in every case. Within the docs you can see an example for the race condition within this kind of validation. If it is realy needed you should also use an uniq constraint on database level.

Access Attribute of Rails Test Fixture in Associated FixtureSet

Background
My application defines a one-to-many relationship between Employee and Company models. I've assigned the Employee fixture a company using the label of the Company fixture (37_signals). However, I also need to assign the company_uuid, which is generated by SecureRandom.uuid.
Example
app/models/
employee.rb
class Employee
belongs_to :company
end
company.rb
class Company
has_many :employees
end
test/fixtures/
employees.yml
employee:
name: dhh
company: 37_signals
company_uuid: <%= "Access the 37_signals company fixture's uuid here!" %>
companies.yml
37_signals:
name: $LABEL
company_uuid: <%= SecureRandom.uuid %>
Question
How can I access the attribute of a Fixture in another FixtureSet?
Attempted
I've attempted to use the following lines as solutions:
company_uuid: <%= ActiveRecord::FixtureSet.identify(:37_signals).company_uuid %>
The above finds the company's primary key id value, then calls the company_uuid method, which is undefined for the integer. This is invalid.
company_uuid: <%= companies(:37_signals).company_uuid %>
The above finds reports undefined method 'companies' for main:Object
Is there a conventional way to solve this problem?
Hmmm, the first option should be to DRY out the data, so it only exists in one place. You could do this with a delegate so that employee.company_uuid will always be answered by employee.company.company_uuid.
If you really need it in the Employee model, the next best choice would be to use a callback (like before_validate or after_save depending on your use-case), that copies the value from the Company to the Employee object. You want to eliminate chances for the data value to diverge from what it's true source should be.
Finally, you could extract all the UUIDs into a hash accessible to both fixtures at the time of creation, and set both values like:
company_uuid: <%= UUIDs['37_signals'] %>
...or similar
This is the best solution I've been able to devise:
company_uuid: <%= Company.find(ActiveRecord::FixtureSet.identify(:publish_and_export_album)).company_uuid %>
However, this solution does not seem conventional. Also, I believe that this succeeds with some luck since fixtures are loaded alphabetically. If Company was named Organization, loading after Employee, then I think this would not work as intended. Actually, through trial and error I determined that this method works in the opposite direction, so the alpha-order has no detrimental effect.
This isn't an issue if you use the fixture_builder gem. It allows you to build a graph of model objects and give them fixture names, and saves them to .yml fixture files for you.

Fabrication has_many though with validation

I'm just getting into testing and I have many models that utilize a has_many though relationship. In each case, one model requires that the other be present at the time of save. I've run into a wall with every testing system I've tried (FactoryGirl, Fixtures, and now Fabrication) where I can not figure out how to set up the tests correctly to replicate this behavior.
I followed this GIST as an example but changed the after_build to before_save as the models are requiring the "though" model at that time. Am I approaching this the wrong way? How does one test this relationship/functionality?
I've created a GIST that is hopefully easier to use/read.
I changed this
Fabricator(:brand) do
title "Coca Cola"
before_save do |brand|
styles Fabricate(:brand_style, :brand => brand, :style => Fabricate(:style))
end
end
to this
Fabricator(:brand) do
title "Coca Cola"
styles(count: 3) { Fabricate(:style) }
end
and now the test is passing. However, I'm not sure if this is the correct way of setting this up, so if anyone has any additional insight it would be appreciated.

Mongoid + Cucumber

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.

Resources