How to manually add a column referencing a model in FactoryGirl? - ruby-on-rails

I have the current modeling:
A House belongs_to User
A User has_one House
Which means that the House model has a reference to User. My Factory for User, looks like this:
FactoryGirl.define do
factory :user do
house
end
end
What this does is basically creating a User and a House that references that User.
Now, I have introduced a column in User called house_id and I want to be able to allow the Factory to work as it is, but also, fill the house_id with the House that was created. The reason why I am doing this is because I want to change the reference direction incrementally.
I have done it like this:
FactoryGirl.define do
factory :user do
house
house_id { House.find_by(user_id: id).id }
end
end
But I suspect there might be a better way to do this. Any thoughts?

First of all you have the wrong idea of how has_one and belongs_to relations work.
belongs_to sets up a 1-1 or 1-to-many relationship and places the foreign key column on this models table. In the case its houses.user_id which references users.id:
class User < ActiveRecord::Base
has_one :house
end
class House < ActiveRecord::Base
belongs_to :user
end
Secondly specs should not hinge on records being created sequentially. It will create brittle specs and ordering issues. Instead let factory girl do its job in creating objects for you.
FactoryGirl.define do
factory :user do
house
end
end
FactoryGirl.define do
factory :house do
user
end
end
And don't assume that records have a given id:
# bad
get :show, id: 1
# good
get :show, id: user.to_param
If you need to setup a record which is explicitly associated somewhere you can just pass a user or house option:
let(:user) { create(:user) }
let(:house) { create(:house, user: user) }
it 'should be associated with the correct user' do
expect(house.user).to eq user
end

Related

FactoryGirl: how to handle belongs_to presence validations behind the scenes?

I had a factory like the following:
FactoryGirl.define do
factory :product do
name 'John'
end
end
I have a lot of specs using this factory like this:
product = create :product
I now added a Product belongs_to :user association and validate it on presence:
class Product < ApplicationRecord
validates :user, presence: true
end
I would have to refactor all my factory calls like this now (or something similar):
product = create :product, user: create(:user)
This seems like a hassle to me. I'd like FactoryGirl to do this for me automatically. It should be something like this:
Whenever creating a product, FactoryGirl automatically adds the user:
If there's already a user in the DB, it assigns this user
If there's no user in the DB, one is created
Is there a good way to do this? I want to avoid querying the DB for the existence of a user whenever a product is created.
Also, it could be dangerous, as an existing user could be destroyed during a test and then would be missing to any subsequent created product.
Are there better patterns for this?
I've seen stuff like validating the ID instead of the object:
validates :user_id, presence: true
And then simply assigning a non-existent ID when creating the object:
product = create :product, user_id: 123456
Is this a good pattern?
All my deeply nested/associated resources should have a belongs_to :user association soon. I want to avoid having to write user: create(:user) (or user: #user) everywhere in my specs.
I'm sure there is a well-proven pattern for this...?
It's possible to set up associations within factories. If the factory
name is the same as the association name, the factory name can be left
out.
http://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#Associations
FactoryGirl.define do
factory :product do
name 'John'
user
end
end

Active Record querying relation to get other relation

I want to be able to return an AR relation by calling a method on another relation of different model objects. In other words, given a User model which belongs_to a House model (which either has_one or has_many Users) I want to be able to take a relation of users and do users.houses, which should return the relation of house objects to which those users belong.
NOTE -- I'm not trying to create a user.houses method (singular user), but rather a users.houses method, which grabs all houses whose ids are among the list of all house_ids in the list of users. The idea is that this then allows me to call class/relation methods on that relation of houses: users.houses.house_class_method.
I've tried doing this "manually":
class User
belongs_to :house
# Is there some AR Relationship I can declare that would
# write this method (correctly) for me?
def self.houses
House.where(id: pluck(:house_id))
end
end
House model:
class House
has_many :users
def self.addresses
map(&:address)
end
def address
"#{street_address}, #{city}"
end
end
Two issues, though:
This feels (maybe incorrectly) like something that should be
declarable via a AR relationship.
It isn't working correctly. The trouble (as outlined in this
separate question) is that users.houses works fine, but when I
do users.houses.addresses,
the method is called on the House class, rather than the relation of
houses that users.houses returns (!?!). As a consequence, I get an undefined method 'map' for <Class:0x1232132131> error.
Is there a correct way to do this in Rails -- to essentially say that a relation belongs to another relation, or that a model belongs to another, on a relation level as well?
Thanks!
Implementing has_and_belongs_to_many relationship between User and House models might make it easier for accomplishing what you want.
class User
has_and_belongs_to_many :houses
...
end
class House
has_and_belongs_to_many :users
...
end
> user = User.find(user_id)
> user.houses # All houses the user belongs to
> house = House.find(house_id)
> house.users # All users belonging to the house
You don't have to implement self.houses; Rails does this for you.
In your User model (user.rb), do the following
def User
has_many :houses
...
end
and in your House model
def House
belongs_to :user
...
end
from there on, some_user.houses returns all the houses that belong to that user. Just make sure there is a user_id (integer) column in your Houses table.
Hmm, users would be an Array or ActiveRelation, not of type User
I would recommend a static method on User:
def self.houses_for_users(users)
..
end

FactoryGirl has_many association with validation

I have a standard has_many relationship (Booking has many Orders) with validation that a Booking does not get saved without at least one Order. I'm trying to replicate this with my FactoryGirl factories but the validation is preventing me from doing so.
class Booking < ActiveRecord::Base
has_many :orders
validates :orders, presence: true
end
class Order < ActiveRecord::Base
belongs_to :booking
end
Here are my FactoyGirl factory specifications for each model as followed from FactoryGirl's GitHub wiki page.
FactoryGirl.define do
factory :booking do
factory :booking_with_orders do
ignore do
orders_count 1
end
before(:create) do |booking, evaluator|
FactoryGirl.create_list(:order, evaluator.orders_count, booking: booking)
end
end
end
factory :order do
booking
end
end
When I try to run FactoryGirl.create(:booking_with_orders) from my spec, I get:
Failure/Error: #booking = FactoryGirl.create(:booking_with_orders)
ActiveRecord::RecordInvalid:
Validation failed: Orders can't be blank
It seems like the check for the validation is running even before before(:create) [...] which would theoretically create the Orders for the Booking.
This post recommends not adding has_many relationships to your factories but I would like to solve this anyways if there is a good way to do it.
Thanks in advance.
Wat? Impossible? Not at all.
Just change your code to something like this:
after :build do |booking, evaluator|
booking.orders << FactoryGirl.build_list(:order, evaluator.orders_count, booking: nil)
end
Taking off from #jassa's answer, if you just need to add a single (required) associated record with a specific attribute, this pattern worked for me:
factory :booking do
ignore do
order_name "name"
end
after :build do |factory, evaluator|
factory.orders << FactoryGirl.build(:order, name: evaluator.order_name, booking: nil)
end
end
This seems like an overly simplistic observation but what you're trying to do is in effect make sure that the Order exists before the Booking, which is impossible, as the Order cannot exist without its booking_id (meaning that the Booking needs to be created first).
There's nothing wrong with a has_many relationship in your factories, it's your validation that is a problem. Does this currently work in your application? How do you save your records in that case? What is the flow for creating Orders and Bookings?
Even the infamous accepts_nested_attributes_for won't help you here.
My advice is to rethink your record saving and validation strategy so that it is a little more sane.

How do you test a model that is dependent on another?

I have User, Account, and Role models. A User can exist by itself. An Account must be tied to a User through a Role. An Account must have at least one Role record of type "Owner". I'm not sure how to test this in RSpec and FactoryGirl.
# user_id, account_id, role
Role < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
User < ActiveRecord::Base
has_many :roles
has_many :accounts, through: roles
accepts_nested_properties_for :accounts
end
Account < ActiveRecord::Base
has_many :roles
has_many :users, through: roles
end
If a user is not signed in, the Accounts.new will display the User and Account forms. If a user is signed in, only the Account form will be displayed. The trouble is, I'm unsure what Role and Account will look like in RSpec when trying to test the associations.
This test fails (the array comes back empty):
describe Account do
let(:user) { FactoryGirl.create(:user) }
before { #account = user.accounts.build(title: "ACME Corporation", subdomain: "acme") }
subject { #account }
it { should respond_to(:users) }
its(:users) { should include(user) }
Then there is the added complication of testing when users are signed in, and when they are not. Any reference sample code I can see for a similar use case? I'm also not sure what to test for in roles_spec, and what belongs to accounts_spec/user_spec.
Factories:
FactoryGirl.define do
factory :user do
name "Mickey Mouse"
email "mickey#disney.com"
password "m1ckey"
password_confirmation "m1ckey"
end
factory :account do
title "ACME Corporation"
subdomain "acme"
end
factory :role do
user
account
end
end
You test objects that depend on other objects by using a mocking framework to "mock" the other objects. There are a number of mock frameworks that allow you to do this.

How to implement "business rules" in Rails?

What is the way to implement "business rules" in Rails?
Let us say I have a car and want to sell it:
car = Cars.find(24)
car.sell
car.sell method will check a few things:
does current_user own the car?
check: car.user_id == current_user.id
is the car listed for sale in the sales catalog?
check: car.catalogs.ids.include? car.id
if all o.k. then car is marked as sold.
I was thinking of creating a class called Rules:
class Rules
def initialize(user,car)
#user = user
#car = car
end
def can_sell_car?
#car.user_id == #user.id && #car.catalogs.ids.include? #car.id
end
end
And using it like this:
def Car
def sell
if Rules.new(current_user,self).can_sell_car
..sell the car...
else
#error_message = "Cannot sell this car"
nil
end
end
end
As for getting the current_user, I was thinking of storing it in a global variable?
I think that whenever a controller action is called, it's always a "fresh" call right? If so then storing the current user as a global variable should not introduce any risks..(like some other user being able to access another user's details)
Any insights are appreciated!
UPDATE
So, the global variable route is out! Thanks to PeterWong for pointing out that global variables persist!
I've now thinking of using this way:
class Rules
def self.can_sell_car?(current_user, car)
......checks....
end
end
And then calling Rules.can_sell_car?(current_user,#car) from the controller action.
Any thoughts on this new way?
I'd use the following tables:
For buyers and sellers:
people(id:int,name:string)
class Person << ActiveRecord::Base
has_many :cars, :as => :owner
has_many :sales, :as => :seller, :class_name => 'Transfer'
has_many :purchases, :as => :buyer, :class_name => 'Transfer'
end
cars(id:int,owner_id:int, vin:string, year:int,make:string,model:string,listed_at:datetime)
listed_at is the flag to see if a Car is for sale or not
class Car << ActiveRecord::Base
belongs_to :owner, :class_name => 'Person'
has_many :transfers
def for_sale?
not listed_at.nil?
end
end
transfers(id:int,car_id:int,seller_id:int,buyer_id:int)
class Transfer << ActiveRecord::Base
belongs_to :car
belongs_to :seller, :class_name => 'Person'
belongs_to :buyer, :class_name => 'Person'
validates_with Transfer::Validator
def car_owned_by_seller?
seller_id == car.owner_id
end
end
Then you can use this custom validator to setup your rules.
class Transfer::Validator << ActiveModel::Validator
def validate(transfer)
transfer.errors[:base] = "Seller doesn't own car" unless transfer.car_owned_by_seller?
transfer.errors[:base] = "Car isn't for sale" unless transfer.car.for_sale?
end
end
First, the standard rails practice is to keep all business logic in the models, not the controllers. It looks like you're heading that direction, so that's good -- BUT: be aware, there isn't a good clean way to get to the current_user from the model.
I wouldn't make a new Rules model (although you can if you really want to do it that way), I would just involve the user model and the car. So, for instance:
class User < ActiveRecord::Base
...
def sell_car( car )
if( car.user_id == self.id && car.for_sale? )
# sell car
end
end
...
end
class Car < ActiveRecord::Base
...
def for_sale?
!catalog_id.nil?
end
...
end
Obviously I'm making assumptions about how your Catalog works, but if cars that are for_sale belong_to a catalog, then that method would work - otherwise just adjust the method as necessary to check if the car is listed in a catalog or not. Honestly it would probably be a good idea to set a boolean value on the Car model itself, this way users could simply toggle the car being for sale or not for sale whenever you want them to ( either by marking the car for sale, or by adding the car to a catalog, etc. ).
I hope this gives you some direction! Please feel free to ask questions.
EDIT: Another way to do this would be to have methods in your models like:
user.buy_car( car )
car.transfer_to( user )
There are many ways to do it putting the logic in the object its interacting with.
I would think this would a prime candidate for using a database, and then you could use Ruby to query the different tables.
You might take a look at the declarative authorization gem - https://github.com/stffn/declarative_authorization
While it's pre-configured for CRUD actions, you can easily add your own actions (buy, sell) and put their business logic in the authorization_rules.rb config file. Then, in your controllers, views, and even models!, you can easily ask permitted_to? :buy, #car
I'm doing something similar with users and what they can do with photo galleries. I'm using devise for users and authentication, and then I set up several methods in the user model that determine if the user has various permissions (users have many galleries through permissions) to act on that gallery. I think it looks like the biggest problem you are having is with determining your current user, which can be handled quite easily with Devise, and then you can add a method to the user model and check current_user.can_sell? to authorized a sale.

Resources