Pass a value of the factory to the associated one - factory-bot

In the FactoryBot GETTING STARTED page there is the following snippet which explains how to create an association.
factory :post do
# ...
association :author, factory: :user, last_name: "Writely"
end
In my case, I need to pass a value of the factory to the associated one.
factory :post do
category: ['a','b','c'].sample
association :author, factory: :user, expertise: ??CATEGORY??
end
I have tried several things (mainly putting curly braces) but nothing seems to work: Trait not registered: category.
Is there a way to pass the category selected for the post to its author?
Thanks

You could use an after(:build) callback:
factory :post do
category ['a','b','c'].sample
association :author, factory: :user
after(:build) do |post|
post.author.expertise = post.category
end
end

Related

Self referencing association in Phoenix using Ecto

So I've just started fiddeling around with Phoenix, and Elixir. So I have reached the point were I am trying to get a working rest-api endpoint to work with a prerequisite JSON.
So I have this module:
defmodule MyApp.Housing.Part do
use Ecto.Schema
import Ecto.Changeset
#primary_key {:id, :integer, []}
schema "parts" do
field :level, :integer
field :title, :string
belongs_to :parent, MyApp.Housing.Part
has_many :children, MyApp.Housing.Part, foreign_key: :parent_id
timestamps()
end
def changeset(part, params \\ %{}) do
part
|> cast(params, [:title, :level, :id, :parent_id])
|> put_assoc(:children, required: false)
|> put_assoc(:parent, required: false)
|> validate_required([:title, :level, :id])
end
end
And the module in which the table is created
defmodule MyApp.Repo.Migrations.CreateParts do
use Ecto.Migration
def change do
create table(:parts, primary_key: false) do
add :id, :integer, primary_key: true
add :title, :string
add :level, :integer
add :parent_id, references(:parts)
add :children, references(:parts)
timestamps()
end
create index(:parts, [:children])
create index(:parts, [:parent_id])
end
end
The inteded functionallity is for a part to be able to have multiple children but only one parent. And these are defined in a JSON like this:
{"id": 10,
"title": "Matt",
"level": 0,
"children": [],
"parent_id": null}
So my problem is the following:
"changeset" requires that the incoming object looks like {"part":{}}otherwise the ActionClauseError gets thrown.
When defining the object as above I get the error children":["is invalid"]. And I can't figure out how to get a valid one, if I did I probably could figure out the problem.
I might take the wrong approach here but would gladly accept any help.
As mentioned by #steve-pallen, it's not necessary to store any references to children in the database. Determining whether or not a Part is a parent or child, as well as which Parts are its children or which Part is its parent can be determined fully by the parent_id field.
You described in your question that each Part "can only have one parent, but multiple children". It's not explicit in your question how many levels the relationship allows: i.e. can a Part be both a parent and a child? In which case, there would be potentially infinite levels of nesting:
part1
|- part2
|- part3
|- part4
In this case, part1 is the parent of part2, part2 is itself the parent of part3, etc. I'm going to assume for my answer that there is no limit to the amount of nesting.
Given this case, your schema definition is 100% correct:
belongs_to :parent, MyApp.Housing.Part
has_many :children, MyApp.Housing.Part, foreign_key: :parent_id
I think the primary issue is with your changeset function. Remember that with put_assoc/3, it's expected that all the models referenced by parent and children already exist in the DB (see the docs for cast_assoc/3). For simplicity I suggest that you don't use put_assoc or cast_assoc and instead manage each model in isolation. If you change your changeset function to this (I've removed id since it's not necessary):
def changeset(part, params \\ %{}) do
part
|> cast(params, [:title, :level, :parent_id])
|> validate_required([:title, :level])
end
Then you can build the nested relationship I showed above by doing 4 inserts in isolation (much easier to reason about, and probably more in line with how you'd handle DB updates from a form or script):
part1 =
MyApp.Housing.Part.changeset(%MyApp.Housing.Part{}, %{title: "part1", level: 0, parent_id: nil})
|> Repo.insert!()
part2 =
MyApp.Housing.Part.changeset(%MyApp.Housing.Part{}, %{title: "part2", level: 0, parent_id: part1.id})
|> Repo.insert!()
part3 =
MyApp.Housing.Part.changeset(%MyApp.Housing.Part{}, %{title: "part3", level: 0, parent_id: part2.id})
|> Repo.insert!()
part4 =
MyApp.Housing.Part.changeset(%MyApp.Housing.Part{}, %{title: "part4", level: 0, parent_id: part3.id})
|> Repo.insert!()
Assuming we want to get part2, we can load it along with its parent and children like this:
part2 = Repo.preload(part2, [:parent, :children])
# part2.parent == %MyApp.Housing.Part{title: "part1", ...}
# part2.children == [%MyApp.Housing.Part{title: "part3", ...}]
Hope this helps!
The first thing you need to fix is the migration. You don't want the children field since that is a has_many relationship and is handled by the parent_id field in the children. It should look like this:
defmodule MyApp.Repo.Migrations.CreateParts do
use Ecto.Migration
def change do
create table(:parts, primary_key: false) do
add :id, :integer, primary_key: true
add :title, :string
add :level, :integer
add :parent_id, references(:parts)
timestamps()
end
create index(:parts, [:parent_id])
end
end
Handling the children in the changeset depends on a couple things.
What does the incoming payload look like when there are children?
Will there be new children in the children list or just existing children?

Mongoid/MongoDB: Order a query by the value of an embedded document?

I am attempting to order the results of a query by the value of a specific embedded document, but even with what seems to be a valid set of options and using the $elemMatch operator, my results are coming back in natural order.
My model is composed of Cards, which embeds_many :card_attributes, which in turn reference a specific CardAttributeField and contain an Integer value. I would like to be able to order a collection of Cards by that value.
I am able to isolate a collection of Cards which have a CardAttribute referencing a specific CardAttributeField like this:
cards = Card.where(:card_attributes.elem_match => {
:card_attribute_field_id => card_attribute_field.id
})
If I knew the order in which the card_attributes were set, I could use MongoDB array notation, like this:
cards.order_by(['card_attributes.0.value', :asc])
This does deliver my expected results in test scenarios, but it won't work in the real world.
After much messing around, I found a syntax which I thought would allow me to match a field without using array notation:
cards.asc(:'card_attributes.value'.elem_match => {
:card_attribute_field_id => card_attribute_field.id
})
This produced a set of options on the resulting Mongoid::Criteria which looked like:
{:sort=>{"{#<Origin::Key:0x2b897548 #expanded=nil, #operator=\"$elemMatch\", #name=:\"card_attributes.value\", #strategy=:__override__, #block=nil>=>{:card_attribute_field_id=>\"54c6c6fe2617f55611000068\"}}"=>1}}
However, the results here came back in the same order regardless or whether I called asc() or desc().
Is there any way to do what I'm after? Am I taking the wrong approach, or do I have a mistake in my implementation? Thanks.
Simplified, my model is:
class Card
include Mongoid::Document
# various other fields
has_many :card_attribute_fields
embeds_many :card_attributes do
def for_attribute_field card_attribute_field
where(:card_attribute_field_id => card_attribute_field.id)
end
end
end
class CardAttributeField
include Mongoid::Document
belongs_to :card
field :name, type: String
field :default_value, type: String
field :description, type: String
end
class CardAttribute
include Mongoid::Document
embedded_in :card
field :card_attribute_field_id, type: Moped::BSON::ObjectId
field :value, type: Integer
end

Associations in Factory Girl 4.0.0

I just opening up Factory_Girl and I'm trying to figure out how to build dependent factories, most of the questions seem outdated due to the new release.
I'm using associations but other than creating the associated object, that object doesn't seem to be associated or related to the main object in anyway
Basically here's what i have
factory :computer do
serial_no "12345"
end
factory :allocation do
association :computer_id, factory: :computer
end
allocation belongs_to computer and computer has_many allocations
basically an allocation is a record of any time a computer gets moved or whatnot.
I'm not sure what I'm doing wrong but every time I run this, the computer_id of allocation is '1', but the ID of computer is something random (usually a number between 0-20), and then my test fails because it can't find the proper computer object.
Edit:
As if it weren't confusing enough, the actual class name is Assignment, i was attempting to simply. Here's the actual code thats involved, the actual code has no issues because computer_id and user_id are passed to the create method as params during creation.
describe "GET index" do
it "assigns assignments as #assignment" do
Assignment.any_instance.stubs(:valid?).returns(true)
assignment = FactoryGirl.create :associated_assignment
get :index, {}, valid_session
assigns(:assignments).should eq([assignment])
end
end
The Factories involved are
factory :user do
fname "John"
lname "Smith"
uname "jsmith"
position "placeholder"
end
factory :computer do
asset_tag "12345"
computer_name "comp1"
make "dell"
model "E6400"
serial_no "abc123"
end
factory :associated_assignment, class: Assignment do
association :user_id, factory: :user
association :computer_id, factory: :computer
assign_date '11-11-2008'
end
And the controller is:
def index
#assignments = []
#computers = Computer.all
Computer.all.each do |asset|
#assignments << Assignment.where(:computer_id => asset.id).order("assign_date ASC").last
end
respond_to do |format|
format.html # index.html.erb
format.json { render json: #assignments }
format.xls { send_data #assignments.to_xls }
end
At the moment i am running this alternative test to check my ids:
describe "GET index" do
it "assigns assignments as #assignment" do
Assignment.any_instance.stubs(:valid?).returns(true)
assignment = FactoryGirl.create :associated_assignment
get :index, {}, valid_session
assigns(:computers).should eq([assignment])
end
end
Which returns something to the effect of the following, where the ID of computer is random but computer_id of assignment is always 1.
Failure/Error: assigns(:computers).should eq([assignment])
expected: [#<Assignment id: 12, user_id: 1, computer_id: 1, assign_date: "2008-11-11", created_at: "2012-09-10 23:59:48", updated_at: "2012-09-10 23:59:48">]
got: [#<Computer id: 14, serial_no: "abc123", asset_tag: 12345, computer_name: "comp1", make: "dell", model: "E6400", created_at: "2012-09-10 23:59:48", updated_at: "2012-09-10 23:59:48">]
Factories don't guarantee what ids anything will have. But you can find the proper computer object via:
allocation = FactoryGirl.create(:allocation)
computer = allocation.computer
I think the problem is with :computer_id vs. :computer. Here's one way to do it that uses FactoryGirl's ability to infer factories and associations:
factory :computer do
serial_no "12345"
end
factory :allocation do
computer
end
Further, if you want each computer to have a unique serial number in your specs, use:
sequence :serial_no do |n|
"1234#{n}"
end
factory :computer do
serial_no
end
factory :allocation do
computer
end
Factory Girl is aware of your model and its associations, so it can pick them up and infer how to create related objects.
Thus:
allocation = FactoryGirl.create :allocation
creates an Allocation object and an associated Computer with a serial number of 12340. The id of the Computer object will already be in the Allocation's computer_id field so the relation is completely set up. allocation.computer will work, and allocation.computer_id will be the same as allocation.computer.id.
This uses some syntax sugar of FactoryGirl. You can be more explicit by supplying association (field) names and factory names:
factory :computer do
serial_no "12345"
end
factory :allocation do
association :computer, factory: :computer
end

Get all keys in MongoMapper Model including its association

Suppose the following model :
class Product
include MongoMapper::Document
key :name, String
key :product_category_id, ObjectId
belongs_to :product_category
end
class ProductCategory
include MongoMapper::Document
key :name, String, :required => true, :unique => true
timestamps!
userstamps!
end
I want to implement an advanced search that will inspect all value inside my Model including its all association like :
I have :
Product A data named "aLaptop" belongs_to:ProductCategory named "Notebook".
Product B data named "aGreatNotebook" belongs_to:ProductCategory named "Notebook".
When I search with keyword named "Notebook", I want to search it to Product.name fields and also its associations which mean ProductCategory.name also. So it will returned both of that items, because Product A have ProductCategory.name "Notebook" & Product B have Product.name "aGreatNotebook" and ProductCategory "Notebook"..
How can I do this?? I've already searching for 2 days and not success until now:(.. When is in MySQL, I used join table.. But hows in MongoMapper??
Please help.. Thank you..
You can't do joins in MongoDB. So the basic idea is to get the ObjectId associated with the "Notebook" category and then to query the products where product_category is equal to notebook_id. This generally involves two queries. So that'd be something like this:
notebook_id = ProductCategory.first(:name => "Notebook")
if notebook_id
Product.where({:product_category_id => notebook_id['_id']})
end
The question is confusing, but the title of the question is clear.
So in case someone comes here hoping to see how to get all of the:
keys
associations
Read on...
To get the keys in a model:
ConfinedSpace.keys.keys
=> ["_id", "photo_ids", "include_in_qap", "position", "created_at", "updated_at",
"structure_id", "identifier", "name", "description", "notes", "entry_info",
"anchor_points", "nature", "special_equipment", "rescue_overview"]
And to get the associations:
ConfinedSpace.associations.each{|name,assoc| puts name}
photos
attachments
activities
structure
videos
And the class (edited for brevity):
class ConfinedSpace
include MongoMapper::EmbeddedDocument
include Shared::HasPhotos
include Shared::HasAttachments
include HasActivities
TAG = "ConfinedSpace"
belongs_to :structure
many :videos, :as => :attachable
key :identifier, String
key :name, String
key :description, String
key :notes, String
key :entry_info, String
key :anchor_points, String
key :nature, String
key :special_equipment, String
key :rescue_overview, String
validates :identifier, presence: true
end

has_one vs. defining as a Key for Embedded documents for MongoMapper and MongoDB

The Source code is
class RealTimeDetail
include MongoMapper::EmbeddedDocument
key :url, String
key :method, String
end
class TargetFeed
include MongoMapper::Document
key :name, String, :null => false
key :feed_type, String, :null => false
has_one :real_time_detail
end
When I do target_feed.real_time_detail = RealTimeDetail.new(:url => "http://example.com", :method => "get")
I get errored out.
Instead i've changed the TargetFeed to
class TargetFeed
include MongoMapper::Document
key :name, String, :null => false
key :feed_type, String, :null => false
key :real_time_detail, RealTimeDetail
end
This works but was wondering if this is the best way to go about it.
Your classes no longer inherit from ActiveRecord, and has_one belongs to ActiveRecord, so it's not available for use. Using an explicit key seems like it would work.
MongoMapper uses different syntax for implementing associations between data models. Details here: http://mongomapper.com/documentation/plugins/associations.html
the short of it is, instead of has_one, use one
class TargetFeed
include MongoMapper::Document
key :name, String, :null => false
key :feed_type, String, :null => false
one :real_time_detail
end
I believe that since you've defined RealTimeDetail as an embedded document you don't need to declare an association in the definition of RealTimeDetail. If RealTimeDetail included Document (instead of EmbeddedDocument) you would use belongs_to from it's end:
class RealTimeDetail
include MongoMapper::Document
key :url, String
key :method, String
belongs_to :target_feed
end