Factory Girl with multiple associations with the same parent - ruby-on-rails

How do I create a Factory that has multiple associations that rely on the same parent?
The Parent model:
class Parent < ActiveRecord::Base
has_many :codes
has_many :parent_filters
validates :parent, :presence => true, :uniqueness => true
end
The Fitler model:
class Filter < ActiveRecord::base
has_many :parent_filters
validates :filter, :presence => true, :uniqueness => true
end
The ParentFilter join model:
class ParentFilter < ActiveRecord
belongs_to :parent
belongs_to :filter
validates :filter, :presence => true
validates :parent, :filter, :presence => true, :uniqueness => [ :scope => filter ]
end
The AdhocAttribute model:
class AdhocAttribute < ActiveRecord::Base
has_many :adhoc_mappings
has_many :codes, :through => :adhoc_mappings
has_many :parent_filters, :through => adhoc_mappings
validates :adhoc_attribute, :presence => true, :uniqueness => true
end
The code model:
class Code < ActiveRecord::Base
belongs_to :parent
has_many :adhoc_mappings
has_many :adhoc_attributes, :through => :adhoc_mappings
validates :code, :parent, presence: true
validates :code, uniqueness: {case_sensitive: false}
end
And last but not least, an ad-hoc mapping model. This model allows for each code to be assigned one adhoc attribute per ParentFilter, which is the factory that I'm trying to create. This factory should require that the Parent for both the ParentFilter and the Code be the same (I'll add a custom validation to enforce that once I get a functional factory).
class AdHocMapping < ActiveRecord::Base
belongs_to :code
belongs_to :parent_filter
belongs_to :adhoc_attribute
has_one :company, :through => :parent_filter
validates :code, :parent_filter, presence: true
validates :code, :uniqueness => { :scope => :parent_filter }
end
If I were to create an AdhocMapping using straight ActiveRecord, I would build it up something like this:
p = Parent.where(:parent => "Papa").first_or_create
aa = AdhocAttribute(:adhoc_attribute => "Doodle").first_or_create
f = Filter.where(:filter => "Z..W").first_or_create
c = Code.where(:code => "ZYXW", :parent => p).first_or_create
pf = ParentFilter.where(:parent => p, :filter => f).first_or_create
am = AdhocMapping(:adhoc_attribute => aa, :parent_filter => pf, :code => :c).first_or_create
so that the Code and ParentFilter that is assigned to the AdhocMapping have the same Parent. I've tried a number of different methods to try to get this to work in FactoryGirl, but I can't seem to get it to create the object.
Here is the factory I thought was the closest to what I want:
require 'faker'
FactoryGirl.define do
factory :adhoc_mapping do |f|
transient do
p Parent.where(:parent => Faker::Lorem.word).first_or_create
end
association :parent_filter, :parent => p
association :code, :parent => p
association :adhoc_attribute
end
end
It gives an error of Trait not registered: p

For associations I usually use after(:build) in FactoryGirl, then I can exactly tell what should happen. (association xxx often did not work like I wanted it to, shame on me.)
So in your case I think you can solve it with this:
require 'faker'
FactoryGirl.define do
factory :adhoc_mapping do |f|
transient do
default_parent { Parent.where(parent: Faker::Lorem.word).first_or_create }
parent_filter { FactoryGirl.build(:parent_filter, parent: default_parent) }
code { FactoryGild.build(:code, parent: default_parent) }
adhoc_attribute { FactoryGirl.build(:adhoc_attribute) }
end
after(:build) do |adhoc_mapping, evaluator|
adhoc_mapping.parent_filter = evaluator.parent_filter
adhoc_mapping.code = evaluator.code
adhoc_mapping.adhoc_attribute = evaluator.adhoc_attribute
end
end
end
By using after(:build) it works with either FactoryGirl.create or FactoryGirl.build. In addition with the transient default_parent, you can even explicitly set the parent you want to have when you build your adhoc_mapping. And now after edit you can also pass nil to the attributes and it will not get overridden.

Related

Translating Database Models to Models on Ruby On Rails

I am beginning Ruby On Rails through a purchase/resale platform project at school. I'm having an issue with my models when I try to translate them from my relational model.
Firstly, I've modelled my database. Here is simplified the entity-relationship model :
I've then translated it in a relational model :
Finally, I've implemented it in Ruby On Rails.
I've implemented a model Client :
class Client < ApplicationRecord
attr_accessor :name
validates :name, :presence => true
has_many :purchasings, :dependent => :destroy
has_many :sellers, :through => :purchasings
has_many :articles, :through => :purchasings
end
I've implemented a model Seller :
class Seller < ApplicationRecord
attr_accessor :name
validates :name, :presence => true
has_many :purchasings, :dependent => :destroy
has_many :sellers, :through => :purchasings
has_many :articles, :through => :purchasings
end
I've implemented a model Article
class Article < ApplicationRecord
attr_accessor :quantity
validates :quantity, :presence => true
has_one :purchasing, :dependent => :destroy
has_one :client, :through => :purchasings
has_one :seller, :through => :purchasings
end
I've implemented a model Purchasing :
class Purchasing < ApplicationRecord
attr_accessor :client_id, :seller_id, :article_id
belongs_to :client, :class_name => "Client"
belongs_to :seller, :class_name => "Seller"
belongs_to :article, :class_name => "Article"
validates :client_id, :presence => true
validates :seller_id, :presence => true
validates :article_id, :presence => true
end
I've modified the Purchasing database migration :
class CreatePurchasing < ActiveRecord::Migration[5.1]
def change
[...]
add_index :purchasings, :client_id
add_index :purchasings, :seller_id
add_index :purchasings, :article_id
add_index :purchasings, [:client_id, :seller_id], :unique => true
end
def down
[...]
end
end
I know this is incorrect because when I execute the following code on the Rails console :
cl1 = Client.create(:name => "John")
cl2 = Client.create(:name => "James")
sel1 = Seller.create(:nom => "Jack")
sel2 = Seller.create(:nom => "Jil")
a1 = Article.create(:quantity => 5)
p1 = Purchasing.new(:client => cl1, :client_id => cl1.id, :seller => sel1, :seller_id => sel1.id, :article => a1, :article_id => a1.id)
p1.save
p2 = Purchasing.new(:client => cl2, :client_id => cl2.id, :seller => sel1, :seller_id => sel1.id, :article => a1, :article_id => a1.id)
p2.save
p2.save returns true whereas an article can't be sold by a same seller and bought by two clients different.
You are adding the index on incorrect columns on purchasings table. As per the requirement, the article_id and seller_id should not get repeated ideally. So you actually need is a uniqueness constraint on seller_id, and article_id column. You can do so by creating an unique index on the composition of two columns seller_id, and article id on the database layer. You should also add the application layer validation on the purchasing model.
class Purchasing < ApplicationRecord
attr_accessor :client_id, :seller_id, :article_id
belongs_to :client, :class_name => "Client"
belongs_to :seller, :class_name => "Seller"
belongs_to :article, :class_name => "Article"
validates :client_id, :presence => true
validates :seller_id, :presence => true
validates :article_id, :presence => true
validates :article_id, uniqueness: {scope: :seller_id}
end
Now you should also write a database migration to add the unique index on these two columns.
class AddUniquenessConstraintInPurshasing < ActiveRecord::Migration
def change
add_index :purchasings, [:article_id, :seller_id], :unique => true
end
end

rails dynamically generate a form

I'm pretty new to the whole rails MVC concept, and I've been tasked with creating a form which does the following:
takes a test number
shows how many sections there are per test (this is a constant, always 4)
allows a region for the user to enter in their responses for each question in a section. The number of questions in each section changes depending on the test number they get.
I have my view which looks like this:
= form_for [#answer_sheet] do |f|
=f.collection_select(:test_prep_number, Exam.find(:all),:test_prep_number, :test_prep_number, {:include_blank => 'Select your test prep number'})
=f.fields_for :answer_sections do |section_form|
=section_form.label :section
.form-inline
=f.label :A
=radio_button_tag 'answer', 'A'
=f.label :B
=radio_button_tag 'answer', 'B'
=f.label :C
=radio_button_tag 'answer', 'C'
=f.label :D
=radio_button_tag 'answer', 'D'
=f.label :E
=radio_button_tag 'answer', 'E'
My controller looks like this:
def index
#answer_sheet = AnswerSheet.build_with_answer_sections
#answer_section = AnswerSection.new
#section_count = AnswerSection.where("exam_id = ?", params[:test_prep_number).count
end
The issue I'm having right now is I can't seem to wrap my head around creating the correct number of radio buttons. So far, I've managed to generate only one question per section.
I'm assuming I'll need a for loop (which will then require a query to find how many questions per exam section).
Edit: Adding models as requested
Answer sheet model
class AnswerSheet < ActiveRecord::Base
attr_accessible :date, :raw_score, :test_prep_number, :answer_sections, answer_sections_attributes
MAX = 101
validates :test_prep_number, :presence => true
validates :raw_score, :presence => true, :numericality => { :greater_than_or_equal_to => 0, :less_than_or_equal_to => MAX}
validates :date, :timeliness => {:on_or_before => lambda { Date.current }, :type => :date}, :presence => true
belongs_to :user
has_many :answer_sections
Answer Section model
class AnswerSection < ActiveRecord::Base
MAX = 30
attr_accessible :section_score, :answers, :answer_attributes
has_many :answers, :dependent => :destroy
belongs_to :answer_sheet
accepts_nested_attributes_for :answers
validates :section_score, :presence => true, :numericality => { :greater_than_or_equal_to => 0,
:less_than_or_equal_to => MAX }
I would recommend tweaking your models to something the breaks the choice from the users answer:
class Test < ActiveRecord::Base
belongs_to :user
has_many :question
attr_accessible :date, :test_prep_number
validates :test_prep_number, :presence => true
validates :date, :timeliness => {:on_or_before => lambda { Date.current }, :type => :date}, :presence => true
end
class Question < ActiveRecord::Base
MAX = 30
attr_accessible :question_text
has_many :choices, :dependent => :destroy
has_one :answer
belongs_to :test
accepts_nested_attributes_for :answers
end
class Choice < ActiveRecord::Base
attr_accessible :question_id, :choice_text, :correct_choice
belongs_to :question
end
class Answer < ActiveRecord::Base
attr_accessible :choice_id
belongs_to :user
belongs_to :question
end

Model.create() throws errors with readonly attributes

I have the following model in rails (simplified):
class Phone < ActiveRecord::Base
include ActiveModel::Validations
belongs_to :user
belongs_to :brand
attr_readonly :user, :brand
attr_accessible :model, :phone_number
validates :user, :presence => true
validates :brand, :presence => true
validates :model, :presence => true
validates :phone_number, :presence => true
end
According to the documentation, attr_readonly should allow attributes to be set at creation, but not at update.
However, when I do this:
Phone.create(:user => <existing_user>, :brand => <existing_brand>, :model => "Galaxy", :phone_number => "555-433-5678")
I get this error:
Can't mass-assign protected attributes user, brand
What am I missing?
If you want to assign a user and a brand association like that, you must define them as being accessible attributes:
attr_accessible :user, :brand
Otherwise, you can assign them like this:
Model.create({:user => user, :brand => brand }, :without_protection => true)

Validate presence in this belongs_to

I have products and brands
products model:
class Product < ActiveRecord::Base
attr_accessible :brand_id, :title
belongs_to :brand
validates :title, :presence => true
validates :brand, :presence => {:message => 'The brand no exists'}
end
and the brands model
class Brand < ActiveRecord::Base
attr_accessible :name
validates :name, :presence => true
has_many :products, :dependent => :destroy
end
I want to validate if exist a product with a name in this brand.
I mean I could have 2 products with the same name in different brands but not in the same brand.
You could use the uniqueness validation with a scope:
validates :name, :uniqueness => { :scope => :brand_id }
Note that you have to specify :brand_id instead of :brand, because the validation can't be made on the relation.
If you don't know it, I suggest you to read the Active Record Validations and Callbacks guide.
NB: the syntax {:foo => 'bar'} is replaced (since Ruby 1.9.2) with {foo: 'bar'}.

Rails Mutually exclusive fields

I have something like:
class Event
validates :name, :prescence => true
belongs_to :parent, class => "Event", foreign_key => "parent_id"
has_many :children, class => "Event"
I want to change it so that a name is only required when an event doesn't have a parent.
validates :name, :presence => true, :if => Proc.new {|event| event.parent.blank? }
should work fine. Please read docs for further information.

Resources