FactoryGirl with has_many :through associations with capybara - ruby-on-rails

I have the following models:
class Productmainclass < ActiveRecord::Base
attr_accessible :name, :id, :maintext
has_many :producttaggings, :dependent => :destroy
has_many :products, :through => :producttaggings
has_many :productsubclasses
end
class Productsubclass < ActiveRecord::Base
attr_accessible :name, :id, :maintext
has_many :producttaggings, :dependent => :destroy
has_many :products, :through => :producttaggings
belongs_to :productmainclass
end
class Product < ActiveRecord::Base
attr_accessible :name, :productimage, :size, :description, :price
has_many :producttaggings, :dependent => :destroy
has_many :productsubclasses, :through => :meteoritetaggings
has_many :productmainclasses, :through => :meteoritetaggings
mount_uploader :productimage, ProductimageUploader
end
class Producttagging < ActiveRecord::Base
belongs_to :product
belongs_to :productsubclass
belongs_to :productmainclass
attr_accessible :product_id, :productsubclass_id, :productmainclass_id
end
I now want to create a Product with FactoryGirl and Capybara. In the spec I simply have:
product = FactoryGirl.create(:product)
In my factories.rb I have:
factory :product do
name "Blue glass"
description "Description text of product"
productimage File.new(File.join(::Rails.root.to_s, "spec/factories/", "testimage.jpg"), 'rb')
productsubclass
productmainclass
end
factory :productsubclass do
name "Colored glasses"
productmainclass
end
factory :productmainclass do
name "Glasses"
end
Running the test I get:
Failure/Error: product = FactoryGirl.create(:product)
NoMethodError:
undefined method `productsubclass=' for #<Product:0xcd42090>

I think the way you have it setup would work if you were dealing with a situation where :product belonged to productsubclass, then the product and the productsubclass would be created with the product.productsubclass_id nicely inserted and all would be fine, but that's clearly not your structure, so we'd have to use another way. I think the link that #depa noted is the right way to go, specifically the 'Basic has many associations' section in this document: http://robots.thoughtbot.com/aint-no-calla-back-girl although you have the added complexity of a has_many through. But essentially your looking at a situation where you create an object and then after that you trigger another create to make the many's. Hope this makes sense :)
** Update **
Here's another approach which might be a little limited but you could just create the records from the other direction. So, if you just want one record in each object/table how about this:
FactoryGirl.define do
factory :producttagging do
product
productsubclass
productmainclass
end
end

Related

Rails Factory Girl: How to setup has_many/through association with other model being created from nested attributes

I've been struggling with setting up a has_many/through relationship when one model object is being created through nested attributes using Factory Girl.
class CommercialInvoice < ActiveRecord::Base
has_many :shipment_commercial_invoices, :dependent => :destroy
has_many :shipments, :through => :shipment_commercial_invoices
end
class Shipment < ActiveRecord::Base
has_many :shipment_commercial_invoices, :dependent => :destroy
has_many :commercial_invoices, :through => :shipment_commercial_invoices
end
class ShipmentCommercialInvoices < ActiveRecord::Base
attr_accessible :job_id, :detail_id
belongs_to :shipment
belongs_to :commercial_invoice
end
The Shipment model is created using nested attributes inside another model supplier_invoice
class SupplierInvoice < ActiveRecord::Base
has_many :shipments, :dependent => :destroy
accepts_nested_attributes_for :shipments, :allow_destroy => true
end
class Shipment < ActiveRecord::Base
belongs_to :supplier_invoice
end
My Factories
factory :supplier_invoice do do
shipments_attributes do
shipments_attributes = []
2.times do # 2 shipments per supplier invoice
shipments_attributes << attributes_with_foreign_keys(:shipment)
end
shipments_attributes
end
end
factory :commercial_invoice do
after(:create) do |commercial_invoice, evaluator|
commercial_invoice.shipments << FactoryGirl.create(:supplier_invoice).shipments
end
end
factory :shipments_commercial_invoice do
shipment
commercial_invoice
comm_invoiced true # A boolean attribute that i want to be true
end
Now the problem in whenever i create a new commercial invoice, the associated shipment_commercial_invoice's attribute comm_invoiced is always false. I am not able to figure out what am i doing wrong. Is there any problem with how i defined factories? Thanks in advance.

Multiple associations from single table rails

In an attempt to expand on this guide for creating multiple associations with the same table I came up with the following user class
class User < ActiveRecord::Base
has_secure_password
validates_uniqueness_of :email
validates_presence_of :email
validates_presence_of :name
belongs_to :role
has_many :clients, foreign_key: 'rep_id'
has_many :submitted_jobs, class_name: 'WorkOrder', through: :clients
has_many :processing_tasks, class_name: 'WorkOrder', foreign_key: 'processor_id'
def has_role?(role_sym)
role.name.underscore.to_sym == role_sym
end
end
My goal is to be able to refer to submitted jobs and processing tasks separately depending on the type of user. So far the processing tasks part works as I expected and so far I can get the rep from the workorder, but when I attempt something like rep.submitted_jobs I get the following error:
NoMethodError: undefined method `to_sym' for nil:NilClass
from /usr/local/rvm/gems/ruby-2.1.5#rails4/gems/activerecord-4.1.6/lib/active_record/reflection.rb:100:in `_reflect_on_association'
ect...
Clearly the has_many through relationship works differently than I'm expecting, but I'm not even quite sure what to call this type of relationship so I'm at something of a loss for what to look for.
Its probably also worth noting that
RSpec.describe User, type: :model do
it {should validate_uniqueness_of(:email)}
it {should validate_presence_of(:name)}
it {should belong_to(:role)}
it {should have_many(:submitted_jobs)}
it {should have_many(:processing_tasks)}
end
all pass
EDIT:
class Client < ActiveRecord::Base
has_many :contacts
has_many :addresses, through: :contacts
has_many :permits
has_many :work_orders
validates :clientnumber, format: { with: /\A\d{3}\z/ },
length: { is: 3 }
belongs_to :rep, class_name: 'User'
belongs_to :default_processor, class_name: 'User'
end
EDIT 2:
work order associations
class WorkOrder < ActiveRecord::Base
belongs_to :client
belongs_to :project_type
belongs_to :status
belongs_to :labels
belongs_to :contact
belongs_to :processor, class_name: 'User'
has_one :rep, through: :client, class_name: 'User'
has_one :presort_information
has_one :printing_instructions
has_one :production_details
........
end
Typical has_many_through_association look like
To solve this problem, you could write custom association, a method that will give you related work_orders
Inside User model
def submitted_jobs
WorkOrder.joins(:client).where('clients.rep_id = ?', self.id)
end

Dependent destroy does not destroy dependencies

I have the following models:
class Article < ActiveRecord::Base
has_many :comments, :as => :subject, :dependent => :destroy
has_many :deleted_comments, :as => :subject, :dependent => :destroy
end
class DeletedComment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
end
class Comment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
before_destroy :create_deleted_comment
def create_deleted_comment
DeletedComment.create!(....)
end
end
In my database, I have quite a few DeletedComment objects where the subject is nil. The DeletedComment (and Comment) model stores :article_id, and for the ones where the subject is nil, Article.find(deleted_comment.article_id) raises an ActiveRecord::RecordNotFound error.
Are there any cases where the :dependent => :destroy would destroy the parent record but leave the dependencies untouched?
Is it possible that in some cases when I delete an Article the deleted_comments are destroyed before comments? and when the comments are destroyed, deleted_comments are created and not destroyed (because ActiveRecord has already checked the dependent deleted_comment and tried to destroy any dependencies)?
According to official documentation:
Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order for the associations to work as expected, ensure that you store the base model for the STI models in the type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts and member posts that use the posts table for STI. In this case, there must be a type column in the posts table.
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets, as: :attachable, dependent: :destroy
end
class GuestPost < Post
end
class MemberPost < Post
end
I guess you could use examle and do something like:
class Article < ActiveRecord::Base
# for deletion only
has_many :abstract_comments, :as => :subject, :dependent => :destroy
# for 'manual' access/edition
has_many :comments, :as => :subject
has_many :deleted_comments, :as => :subject
end
class AbstractComment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
def attachable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class DeletedComment < AbstractComment
end
class Comment < AbstractComment
before_destroy :create_deleted_comment
def create_deleted_comment
DeletedComment.create!(....)
end
end

Association through another table "Unknown key: through"(Rails)

I'm creating trips in my rails app and i want to add categories to those trips. The categories are stored in the Categories table in my DB and the user may select which categories are suitable for the trip. So multiple categories a trip can be used.
Although i'm a noob i figured some things out with some help of the RoR guides about this subject. Now, i've got a 3rd table tripcategories which will have to hold the trip_id and the category_id. Right? With that i've got the following models:
trip.rb:
class Trip < ActiveRecord::Base
attr_accessible :description, :title, :user_id, :triplocations_attributes, :photo
has_many :triplocations, :dependent => :destroy
has_many :tripcategories
has_many :categories, :through => :tripcategories
accepts_nested_attributes_for :triplocations, allow_destroy: true
end
category.rb:
class Category < ActiveRecord::Base
attr_accessible :name
has_many :tripcategories
belongs_to :trip, :through => :tripcategories
end
tripcategory.rb:
class Tripcategory < ActiveRecord::Base
attr_accessible :category_id, :trip_id
belongs_to :trip
belongs_to :category
end
When i'm trying it this way and trying to call trip.categories in my trip index it says "Unknown key: through". Am i doing something horribly wrong or am i missing the bigger picture?
Thanks in advance!
class Category < ActiveRecord::Base
attr_accessible :name
has_many :tripcategories
has_many :trips, :through => :tripcategories
end

rails creating model with multiple belongs_to, with attr_accessible

My models look something like this:
class User < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Product < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Review < ActiveRecord::Base
attr_accessible: :comment
belongs_to :user
belongs_to :product
validates :user_id, :presence => true
validates :product_id, :presence => true
end
I am trying to figure out what the best way is to create a new Review, given that :user_id and :product_id are not attr_accessible. Normally, I would just create the review through the association ( #user.reviews.create ) to set the :user_id automatically, but in this case I am unsure how to also set the product_id.
My understanding is that if I do #user.reviews.create(params), all non attr_accessible params will be ignored.
You can do:
#user.reviews.create(params[:new_review])
...or similar. You can also use nested attributes:
class User < ActiveRecord::Base
has_many :reviews
accepts_nested_attributes_for :reviews
...
See "Nested Attributes Examples" on http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html.
It seems you would like to implement a many-to-many relationship between a User and Product model, with a Review model serving as a join table to connect the two with an added comment string. This can be accomplished with a has many through association in Rails. Start by reading the Rails Guides on Associations.
When setting up your Review model, add foreign keys for the User and Product:
rails generate model review user_id:integer product_id:integer
And set up your associations as follows:
class User < ActiveRecord::Base
has_many :reviews
has_many :products, through: :reviews
end
class Product < ActiveRecord::Base
has_many :reviews
has_many :users, through: :reviews
end
class Review < ActiveRecord::Base
# has comment string attribute
belongs_to :user
belongs_to :product
end
This will allow you to make calls such as:
user.products << Product.first
user.reviews.first.comment = 'My first comment!'
Here's how you would create a review:
#user = current_user
product = Product.find(params[:id])
#user.reviews.create(product: product)

Resources