FactoryBot: Multi-Level associations? - ruby-on-rails

So im relatively new to factory bot, and im pulling in some modals on some older php code into rails. And I seem to be running into a problem with one specific factory creation.
Right now here is my basic factories.rb file:
FactoryBot.define do
factory :"Core/User" do
username {"jDoe"}
end
factory :"Core/Sex" do
title {"Unspecified"}
abbreviation {"U"}
end
factory :"Core/Contact" do
first_name {"John"}
last_name {"Doe"}
display_phone_mobile {false}
internal {false}
archive {false}
"Core/Sex"
end
factory :"Core/Employee" do
"Core/User"
"Core/Contact"
username {"jDoe"}
end
end
Pretty basic right now, as the schema is sort of a tangled mess. Anyways, for whatever reason everything works until I get to trying to create an "Employee" (Sidenote: I had to add Core:: to everything and had to scour SO to find out how to add that to the symbols, since they are namespaced I guess? (I know that I need to use Core::<Model> to access the models in rails fwiw)
Anyways the models are relatively complex, but the important parts are here:
Contact.rb:
class Core::Contact < Core::BaseModel
self.primary_key = 'id'
has_one :employee
belongs_to :sex
User.rb:
class Core::User < Core::BaseModel
extend Core::ActiveDirectory
self.primary_key = 'id'
has_one :employee
Employee.rb:
class Core::Employee < Core::BaseModel
include ActionView::Helpers::DateHelper
self.primary_key = 'id'
belongs_to :contact
There are tons of other dependencies to tackle...but for whatever reason the associations don't seem to pull in the contact_id when making an employee. As in it specifically complains about TinyTds::Error: Cannot insert the value NULL into column 'contact_id'
Thing is, ALL the others work just fine. IE: if I make a Contact it pulls in the "Core/Sex" fine and I manually create the "Contact" factory and specifically pull in the ID like so:
#contact = create(:"Core/Contact")
puts #contact.attributes
#employee = create(:"Core/Employee", contact_id: #contact.id)
It works!, but I dont know why the other associations get pulled in just fine? Any ideas?

you don't need that; You just refer to the factories as a one-word thing than then the Factory constructor has a class: option on it
just move all of your factories over to standard names
factory :employee, class: "Core::Employee" do
user { create(:user) }
contact { create (:contact) }
username {"jDoe"}
end
when you refer to them as factories just use the short names with symbols and let the class: option do the rest.

Related

Rails validate polymorphic associated model attribute

In my Rails 5.2 app, I have a polymorphic model Vehicle of types Car, Bike, Jeep etc. which has belongs_to association vehicle_type. I would like to validate associated record attribute display_name. The following code snippet does the job but I would like to know a better way to do this.
class Car < Vehicle
validates :vehicle_type,
:inclusion => {
:in => [VehicleType.find_by(display_name: 'four wheeler')],
:message => "A Car can only be of vehicle_type 'four wheeler'",
}
}
You should put the validation on the id rather than the display name since you would have to refactor your code if you ever decide to change the display name.
class VehiculeType
FOUR_WHEELER = 1 (id of the four_wheeler type)
end
class Car < Vehicule
validate :validate_vehicule_type
private
def validate_vehicule_type
errors.add(:vehicule, "A Car can only be of vehicle_type 'four wheeler'") unless vehicule_type_id == VehiculeType::FOUR_WHEELER
end
end
I don't know what is the best way, but I'll share what i have done in one of my projects:
I decided to extend ActiveModel::Validator and create my own validation for my polymorphic associations
In you case
class CarValidator < ActiveModel::Validator
def validate_vehicle_type(record)
# where did you save the veicle type associatuon?
unless VehicleType.find_by(display_name: record.veicle_type).exists?
record.errors.add :veicle_type, "This veicle type does not exist"
end
end
then validates with CarValidator
I agree with Mathieu Larouche. One small thing I would add to this discussion is that this is not really a polymorphic association, as polymorphic associations are about how "a model can belong to more than one other model, on a single association". This is done via a combination of type and id fields (imageable_id and imageable_type, for example). See docs here.
It doesn't really affect the response your question, but I just wanted to mention it because polymorphic associations took me forever to wrap my head around, and I thought calling out the distinction could be helpful.

Scoping a class method to current_user

I'm working on implementing a tagging system and I'm having problem querying for tagged objects with a scope.
For example, I would like to find all the user's items with a certain tag. With a class method I can currently find all the objects:
def self.tagged_with(name)
Tag.find_by_name(name).items
end
However, this has a problem. If I were to do something like: current_user.items.tagged_with(name) won't this existing method return ALL the items and not just items owned by the current_user? I suppose this is a simply querying issue but I can't figure out how to change a class method into something called on a collection. I have tried going the opposite way, to get a the collection through the tags, something like... tag.items.where(:user_id => current_user.id) but in this case, it's a many-to-many relationship and I haven't been able to get on thumb on this either.
What's the proper way to restrict a query like this?
Create an association on your User class that points to your Tag class.
class User < ActiveRecord::Base
has_many :tags
end
Then you can do:
current_user.tags.where(...)
If you don't already have an association in place, you'll need to create a migration to have the tags table reference your users table with a foreign key.
I think this will help you:
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
So, basically you can define your method tagged_with directly into the association!
This example is took from the documentations ActiveRecord::Associations

How can I create a FactoryGirl factory for a Product that belongs_to a User?

I have read the FactoryGirl documentation, but I can't seem to figure this out. I know that it must be simple.
Here is my setup:
Rails 3.2.6
User has_many Products
FactoryGirl.define do
factory :product do
sequence(:identifier, 1000) {|n| "ABC#{n}" }
end
end
This fails because there must be a user_id associated with the Product. In my tests, I am creating a user during login. So my tests have access to the user object. I just don't know how to pass the user object in to the Product factory using FactoryGirl.
Assuming you already have a User factory (i.e factory :user do) you can add the following line in your :product factory:
association :user
That will automatically create a user factory (as defined) and assign it to your product.
When you create the object in your test though, you can still do something like:
FactoryGirl.create(:produce, user: some_user)
in order to assign a different user variable, because the Factory sees the association from the line you just added.
Hope this helps.

Rails/ActiveRecord: has_one, belongs_to and before_create

I'm having trouble figuring out how best to model my data. I have the following two models in my Rails application:
class Foo < ActiveRecord::Base
belongs_to :active_bar, :class_name => 'Bar'
accepts_nested_attributes_for :active_bar
before_create do |f|
f.active_bar.foo = f
# Causes stack overflow!
f.active_bar.save!
end
end
class Bar < ActiveRecord::Base
belongs_to :foo
end
test 'create with nested attributes' do
f = Foo.create!(:name => 'foo-name', :active_bar_attributes => {:name => 'bar-name'})
assert_equal 'foo-name', f.name
assert_equal 'bar-name', f.active_bar.name
assert_equal f, f.active_bar.foo
f_id = f.to_param
retrieved_f = Foo.find_by_id!(f_id)
assert_equal retrieved_f, retrieved_f.active_bar.foo
end
What you probably think is strange is the reflexive belongs_to relationship I'm attempting to model. My plan is that, eventually, Foo will have many instances of Bar while one instance will be considered "active". Thus I'm using active_bar to refer to this active instance. The problem with this code is that I need to set the foo property in Bar back to the parent Foo instance and I can't figure out the best place to do it (the save! call in before_create ends up being recursive and overflowing the stack) or even if this is the cleanest way to model this type of relationship.
Essentially I'm attempting to model a user (equivalent to Foo) who has multiple e-mail addresses (equivalent to Bar) with one of the e-mail addresses marked as the user's primary address.
Any advice?
I'm just going to respond in terms of User and EmailAddress if that's okay with you ;)
In your User model should really be has_many :email_addresses, has_one :active_email, :class_name => 'EmailAddress' and, as you correctly identified, accepts_nested_attributes_for :email_addresses
The EmailAddress model should then, of course, have belongs_to :User.
Aside from these, I think you are over-thinking things. In the form to create a user, then, allow them to enter as many email addresses as they want and either have them put their "active" email first, or have some sort of toggle to denote which email address is their primary address.
Edit: As far as the before_create statement, I think it only needs to be a simple validation that a primary email address has been given/marked (if it is necessary that they specify an email address in the first place).
If this doesn't fulfull what functionality you need, please comment. I'll try and help more.

How to handle this type of model validation in Ruby on Rails

I have a controller/model hypothetically named Pets. Pets has the following declarations:
belongs_to :owner
has_many :dogs
has_many :cats
Not the best example, but again, it demonstrates what I'm trying to solve. Now when a request comes in as an HTTP POST to http://127.0.0.1/pets, I want to create an instance of Pets. The restriction here is, if the user doesn't submit at least one dog or one cat, it should fail validation. It can have both, but it can't be missing both.
How does one handle this in Ruby on Rails? Dogs don't care if cats exists and the inverse is also true. Can anyone show some example code of what the Pets model would look like to ensure that one or the other exists, or fail otherwise? Remember that dogs and cats are not attributes of the Pets model. I'm not sure how to avoid Pets from being created if its children resources are not available though.
errors.add also takes an attribute, in this case, there is no particular attribute that's failing. It's almost a 'virtual' combination that's missing. Parameters could come in the form of cat_name="bob" and dog_name="stew", based on the attribute, I should be able to create a new cat or dog, but I need to know at least one of them exists.
You're looking for errors.add_to_base. This should do the trick:
class Pet < ActiveRecord::Base
belongs_to :owner
has_many :dogs
has_many :cats
validate :has_cats_or_dogs
def has_cats_or_dogs
if dogs.empty? and cats.empty?
errors.add_to_base("At least one dog or cat required")
end
end
end
If you want to pass cat_name or dog_name to the controller action, it may look like this:
class PetsController < ApplicationController
# ...
def create
#pet = Pet.new(params[:pet])
#pet.cats.build(:name => params[:cat_name]) if params[:cat_name]
#pet.dogs.build(:name => params[:dog_name]) if params[:dog_name]
if #pet.save
# success
else
# (validation) failure
end
end
end
Alternatively, for some more flexibility you can use nested attributes to create new cats and dogs in your controller.

Resources