ActiveRecord::RecordInvalid: Validation failed: Team must exist - postgresql

I am getting error ActiveRecord::RecordInvalid: Validation failed: Team must exist when I run rake db:seed.
I am trying to set an association between Players and Teams. I have data I want to seed into my database, but I am unsure if the error is being caused with how I have set up my association or how I have structured my data.
Team being the parent and Player being the child.
Models
# player.rb
class Player < ApplicationRecord
belongs_to :team, class_name: "Player"
validates :team, presence: true, allow_nil: true
end
# team.rb
class Team < ApplicationRecord
has_many :players
end
Routes
# routes.rb
Rails.application.routes.draw do
get 'welcome/index'
resources :players
resources :teams do
resources :players
end
root 'welcome#index'
end
Migrations
# create_players.rb
class CreatePlayers < ActiveRecord::Migration[5.1]
def change
create_table :players do |t|
t.belongs_to :team, index: true
t.string :name
t.string :shoots
t.string :catches
t.string :position
t.string :pos
t.integer :number
t.integer :gp
t.integer :goals
t.integer :assists
t.integer :points
t.integer :pim
t.integer :plusMinus
t.decimal :gaa
t.integer :svs
t.integer :team_id
t.references :teams
t.timestamps
end
end
end
# create_teams.rb
class CreateTeams < ActiveRecord::Migration[5.1]
def change
create_table :teams do |t|
t.string :team_name
t.string :abr
t.string :sm_logo
t.string :lg_logo
t.integer :player_id
t.timestamps
end
end
end
Data example of data attempting to seed
# Player data
players = Player.create!({
"name": "Some Guy",
"shoots": "Right",
"position": "Forward",
"pos": "F",
"number": 8,
"gp": 15,
"goals": 12,
"assists": 6,
"points": 18,
"pim": 12,
"plusMinus": 7,
"team_id": 1
})
# Team data
teams = Team.create!({
"team_name": "A Team",
"abr": "ATM"
})
Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 5.1.0'
gem 'pg', '~> 0.18'
gem 'puma', '~> 3.7'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
#Bootstrap 4 and Tether gems
gem 'bootstrap', '~> 4.0.0.alpha6'
gem 'rails-assets-tether', '>= 1.3.3', source: 'https://rails-assets.org'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'
gem 'rails-ujs', '~> 0.1.0'
gem 'jquery-rails'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

First you need to create the team:
team = Team.create!({ team_name: "A Team", abr: "ATM" })
Notice that I pass a hash to the create! method, not an array with a hash, like you had in your example.
Then you can create the player and add them to the team:
player = Player.create!({
"name": "Some Guy",
"shoots": "Right",
"position": "Forward",
"pos": "F",
"number": 8,
"gp": 15,
"goals": 12,
"assists": 6,
"points": 18,
"pim": 12,
"plusMinus": 7,
"team": team
})
Notice that I am able to pass the team object to the Player.create! method. This will associate the player with the team. Otherwise you can just use the id.
player = Player.create!({
...
team_id: team.id
})
OR
player = Player.create!({
...
team_id: 1
})
The ellipsis (3 dots) should be replaced by the actual attributes as shown above.
Update
Remove the following lines from the create_players.rb migration:
t.integer :teams_id
t.references :teams
Remove the following line from the create_teams.rb migration:
t.integer :player__id
Writing t.belongs_to :player produces the same results as writing t.integer :player_id. It's just Rails syntactic sugar. Writing t.belongs_to :player or t.references :player is just a little more clear than writing t.integer :player_id
I'm assuming that you want one player to belong to one team (a one-to-many relationship). If you think that a player could belong to multiple teams (not usually the case, but I don't know what you're developing) than you would need to do a Google search for a Rails many-to-many relationship.

#Greg Answer thank you for your help. I have now found a working solution.
To fix I reverted back to a previous commit and decided to follow the following article:
https://www.learneroo.com/modules/137/nodes/767
My finished working code looks as follows:
Models
# Team model
class Team < ApplicationRecord
has_many :players
end
# Player model
class Player < ApplicationRecord
belongs_to :team
end
Migrations
# create_teams.rb
class CreateTeams < ActiveRecord::Migration[5.1]
def change
create_table :teams do |t|
t.string :team_name
t.string :abr
t.timestamps
end
end
end
# create_players.rb
class CreatePlayers < ActiveRecord::Migration[5.1]
def change
create_table :players do |t|
t.string :name
t.string :shoots
t.string :catches
t.string :position
t.string :posAbr
t.string :abr
t.integer :number
t.integer :gp
t.integer :goals
t.integer :assists
t.integer :points
t.integer :pim
t.integer :plusMinus
t.decimal :gaa
t.integer :svs
t.timestamps
end
end
end
# add_team_id_to_players.rb
class AddTeamIdToPlayers < ActiveRecord::Migration[5.1]
def change
add_column :players, :team_id, :integer
end
end
Seed data examples
# Team data
teams = Team.create([{
"team_name": "A Team",
"abr": "ATM"
}])
# Player data
players = Player.create!([{
"name": "Some Guy",
"shoots": "Right",
"position": "Forward",
"pos": "F",
"number": 8,
"gp": 15,
"goals": 12,
"assists": 6,
"points": 18,
"pim": 12,
"plusMinus": 7,
"team_id": 1
}])

Your association in the class Player is wrong, see when you must use:
If the name of the other model cannot be derived from the association name, you can use the :class_name option to supply the model name. For example, if a book belongs to an author, but the actual name of the model containing authors is Patron, you'd set things up this way:
class Book < ApplicationRecord
belongs_to :author, class_name: "Patron"
end
See all options to use this association: Rails Guide

Related

Ecto will not insert a :date field into the database - why?

I am facing an issue while learning Elixir & Ecto. The idea is to build a standard posts/comments page to understand how the basics work. I am at a point where I have schemas defined, a migration written and encounter an error when trying to insert data into the database (PostgreSQL) via the Repo. I have done a fair deal of web searching and documentation reading, which leads me to believe it's a scenario that should just work and I am making a stupid mistake somewhere, which I just can't see.
They are defined as follows:
lib/hello/schemas.ex
defmodule Hello.PostAuthor do
use Ecto.Schema
schema "post_authors" do
field :name, :string
end
end
defmodule Hello.CommentAuthor do
use Ecto.Schema
schema "comment_authors" do
field :name, :string
end
end
defmodule Hello.Comment do
use Ecto.Schema
schema "comments" do
has_one :author, Hello.CommentAuthor
field :date, :date
field :body, :string
end
end
defmodule Hello.Post do
use Ecto.Schema
schema "posts" do
has_one :author, Hello.PostAuthor
field :date, :date
field :body, :string
has_many :comments, Hello.Comment
end
end
as you can see, I have two fields with :date type - on post and comment schemas. The corresponding migration is as follows:
defmodule Hello.Repo.Migrations.CreatePosts do
use Ecto.Migration
def change do
create table(:post_authors) do
add :name, :string
end
create table(:comment_authors) do
add :name, :string
end
create table(:comments) do
add :author, references(:comment_authors)
add :date, :date
add :body, :string
end
create table(:posts) do
add :author, references(:post_authors), null: false
add :date, :date
add :body, :string
add :comments, references(:comments)
timestamps()
end
end
end
Now, when I start iex -S mix I can successfully create all structs:
iex(1)> post_author = %Hello.PostAuthor{name: "John"}
%Hello.PostAuthor{
__meta__: #Ecto.Schema.Metadata<:built, "post_authors">,
id: nil,
name: "John"
}
iex(2)> comment_author = %Hello.PostAuthor{name: "Adam"}
%Hello.PostAuthor{
__meta__: #Ecto.Schema.Metadata<:built, "post_authors">,
id: nil,
name: "Adam"
}
iex(3)> comment = %Hello.Comment{author: comment_author, date: ~D[2019-01-01], body: "this is a comment"}
%Hello.Comment{
__meta__: #Ecto.Schema.Metadata<:built, "comments">,
author: %Hello.PostAuthor{
__meta__: #Ecto.Schema.Metadata<:built, "post_authors">,
id: nil,
name: "Adam"
},
body: "this is a comment",
date: ~D[2019-01-01],
id: nil
}
iex(4)> post = %Hello.Post{author: post_author, date: ~D[2019-01-01], body: "this is a post", comments: [comment]}
%Hello.Post{
__meta__: #Ecto.Schema.Metadata<:built, "posts">,
author: %Hello.PostAuthor{
__meta__: #Ecto.Schema.Metadata<:built, "post_authors">,
id: nil,
name: "John"
},
body: "this is a post",
comments: [%Hello.Comment{
__meta__: #Ecto.Schema.Metadata<:built, "comments">,
author: %Hello.PostAuthor{
__meta__: #Ecto.Schema.Metadata<:built, "post_authors">,
id: nil,
name: "Adam"
},
body: "this is a comment",
date: ~D[2019-01-01],
id: nil
}],
date: ~D[2019-01-01],
id: nil
}
The problem arises when I call Hello.Repo.insert(post) (where post is the struct representing the Hello.Post schema). I receive what looks like serialization error:
iex(8)> Hello.Repo.insert(post) [debug] QUERY OK db=0.1ms
begin []
[debug] QUERY ERROR db=1.6ms
INSERT INTO "posts" ("body","date") VALUES ($1,$2) RETURNING "id" ["this is a post", ~D[2019-01-01]]
[debug] QUERY OK db=0.1ms
rollback []
** (DBConnection.EncodeError) Postgrex expected a binary, got ~D[2019-01-01]. Please make sure the value you are passing matches the definition in your table or in your query or convert the value accordingly.
(postgrex) lib/postgrex/type_module.ex:897: Postgrex.DefaultTypes.encode_params/3
(postgrex) lib/postgrex/query.ex:75: DBConnection.Query.Postgrex.Query.encode/3
(db_connection) lib/db_connection.ex:1148: DBConnection.encode/5
(db_connection) lib/db_connection.ex:1246: DBConnection.run_prepare_execute/5
(db_connection) lib/db_connection.ex:540: DBConnection.parsed_prepare_execute/5
(db_connection) lib/db_connection.ex:533: DBConnection.prepare_execute/4
(postgrex) lib/postgrex.ex:198: Postgrex.query/4
(ecto_sql) lib/ecto/adapters/sql.ex:666: Ecto.Adapters.SQL.struct/10
(ecto) lib/ecto/repo/schema.ex:651: Ecto.Repo.Schema.apply/4
(ecto) lib/ecto/repo/schema.ex:262: anonymous fn/15 in Ecto.Repo.Schema.do_insert/4
(ecto) lib/ecto/repo/schema.ex:916: anonymous fn/3 in Ecto.Repo.Schema.wrap_in_transaction/6
(ecto_sql) lib/ecto/adapters/sql.ex:898: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
(db_connection) lib/db_connection.ex:1415: DBConnection.run_transaction/4
This is where I am lost. Both the schema and the migration are expecting a :date . I believe that ~D[2019-01-01] is a date. PostgreSQL defines date as a 4 byte binary value. I am expecting Ecto.Adapters.Postgres to translate elixir date struct into the Postgres binary value. This is not happening. Why?
Struct itself is just raw data. You should go through Ecto.Changeset as shown in the documentation, specifically to all types to be cast to the respective DB types with Ecto.Changeset.cast/4.
The conversion will be done automagically, but you need to explicitly call cast/4 (hence the Changeset,) otherwise the adapter has no idea of how to convert your ecto types.

rails PG undefined column does not exist for index creation

I'm trying to add an index for a for column but I'm getting the error:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "contact" does not exist
: CREATE UNIQUE INDEX "index_users_on_contact" ON "users" ("contact")
which is strange because as you an see I'm creating the column before I try to index it:
class AddContactToUser < ActiveRecord::Migration[5.1]
def change
add_reference :users, :contact, foreign_key: true
add_index :users, :contact, unique: true
end
end
Why am I getting this error?
In case your wondering why I'm doing a separate contact model, it's because all users will have a contact but not all contacts will have a user.
add_reference :users, :contact, foreign_key: true
Creates a column named contact_id. So you're index needs to be
add_index :users, :contact_id, unique: true

What's the proper way to add an index to a table in a Rails migration?

I'm using Rails 5 with PostGres 9.5 . I have the following migration
class CreateCryptoIndexCurrencies < ActiveRecord::Migration[5.0]
def change
create_table :crypto_index_currencies do |t|
t.references :crypto_currency, foreign_key: true
t.date :join_date, :null => false, :default => Time.now
t.timestamps
end
add_index :crypto_index_currencies, :crypto_currency, unique: true
end
end
Upon running the migration, it is dying with this error
PG::UndefinedColumn: ERROR: column "crypto_currency" does not exist
What is the proper way to add the index? The table name that I want to reference is called "crypto_currencies".
add_index 'crypto_index_currencies', ['crypto_currency'], name: "index_crypto_index_currencies_on_crypto_currency", unique: true, using: :btree
using: :btree its optional.
This is the syntax to add it inside the create_table block
class CreateCryptoIndexCurrencies < ActiveRecord::Migration[5.0]
def change
create_table :crypto_index_currencies do |t|
t.references :crypto_currency, foreign_key: true
t.date :join_date, :null => false, :default => Time.now
t.index :crypto_currency, unique: true
end
end
end

Vagrant not working properly

I have a vagrant file that uses chef to help install things:
Vagrant.configure(2) do |config|
config.vm.box = 'opscode-ubuntu-12.04_chef-11.4.0'
config.vm.box_url = 'https://opscode-vm-bento.s3.amazonaws.com/vagrant/opscode_ubuntu-12.04_chef-11.4.0.box'
config.vm.network :forwarded_port, guest: 3000, host: 3000
config.vm.provider(:virtualbox) do |vb|
vb.customize [
"modifyvm", :id,
"--memory", "1024",
"--cpus", "4"
]
end
config.vm.provision :shell, inline: %Q{
sudo apt-get install -y postgresql-client
}
config.vm.provision :chef_solo do |chef|
chef.cookbooks_path = ["cookbooks"]
chef.add_recipe :apt
chef.add_recipe 'postgresql::server'
chef.add_recipe 'build-essential'
chef.add_recipe 'rvm::vagrant'
chef.add_recipe 'rvm::system'
chef.add_recipe 'git'
chef.json = {
:postgresql => {
:version => '9.3'
},
"postgresql" => {
"password" => {
"postgres" => "kshgfi3ret3hihjfbkivtbo3ity835"
}
},
"database" => {
"create" => ["aisisplatform"]
},
:git => {
:prefix => "/usr/local"
},
:rvm => {
'rubies' => [ 'ruby-2.1.0' ],
'default_ruby' => 'ruby-2.1.0',
'vagrant' => {
:system_chef_solo => '/usr/bin/chef-solo'
}
},
}
end
end
There are a few issues with this:
Through out the vagrant up start up I get warnings like this:
/tmp/vagrant-chef-1/chef-solo-1/cookbooks/rvm/libraries/rvm_chef_user_environment.rb:32: warning: class variable access from toplevel
The next issue is that things don't work properly, some times the vm sees PostgreSQL, through the psql command, sometimes it doesn't know what it is and states it's not installed. when it does see it, it states that psql: FATAL: role "vagrant" does not exist
The final issue is that when the vm boots up there is well over 200 updates of both regular and security. I would like this to be taken care of when the vm is set up the first time, via vagrant up. I tried doing:
config.vm.provision :shell, inline: %Q{
sudo apt-get update
sudo apt-get upgrade -y
}
But when the script runs I get TON of errors about stdn and so on and so forth. So, what do I do to fix these? what's wrong with my vagrant file?
I adapted the vagrant file to use the omnibus and berkshelf plugins. The former will ensure chef is at the desired version and the latter keeps the cookbooks up-to-date.
I also noted the "class variable access" warnings, whose root cause is presumably buried in the rvm cookbook. I didn't look deeper because my Vagrant run completed without error.
Example
$ tree
.
├── Berksfile
└── Vagrantfile
Berksfile
site :opscode
cookbook "apt"
cookbook "postgresql"
cookbook "build-essential"
cookbook "rvm", :github => "fnichol/chef-rvm"
cookbook "git"
Vagrantfile
Vagrant.require_plugin "vagrant-omnibus"
Vagrant.require_plugin "vagrant-berkshelf"
Vagrant.configure(2) do |config|
# Box config
config.vm.box = 'precise64'
config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
# Plugin config
config.omnibus.chef_version = :latest
config.berkshelf.enabled = true
# Network config
config.vm.network :forwarded_port, guest: 3000, host: 3000
# Virtual config
config.vm.provider(:virtualbox) do |vb|
vb.customize [
"modifyvm", :id,
"--memory", "1024",
"--cpus", "4"
]
end
# Provisioner config
config.vm.provision :chef_solo do |chef|
chef.add_recipe 'apt'
chef.add_recipe 'postgresql::client'
chef.add_recipe 'postgresql::server'
chef.add_recipe 'build-essential'
chef.add_recipe 'rvm::system'
chef.add_recipe 'git'
chef.json = {
:postgresql => {
:version => '9.3'
},
"postgresql" => {
"password" => {
"postgres" => "kshgfi3ret3hihjfbkivtbo3ity835"
}
},
"database" => {
"create" => ["aisisplatform"]
},
:git => {
:prefix => "/usr/local"
},
:rvm => {
'rubies' => [ 'ruby-2.1.0' ],
'default_ruby' => 'ruby-2.1.0',
'vagrant' => {
:system_chef_solo => '/usr/bin/chef-solo'
}
},
}
end
end
Notes:
A standard Ubuntu image can be used. The omnibus plugin will install Chef 11.10 automatically
The "rvm::vagrant" recipe was removed as unncessary. Chef is installed using the omnibus installer and will therefore have it's own embedded ruby version
Used the "postgresql::client" recipe instead of a shell provisioner.

Rails 4, Rspec, FactoryGirl

I am having trouble having an id column in a database that is not rails automagic. Basically the situation is the users table has to be between 5000 and 7000 and there are other external stipulations that don't allow this to auto-increment Here is the migration (which works magically in the console):
class CreateUsers < ActiveRecord::Migration
def change
create_table :users, id: false do |t|
t.integer :id, null: false
t.string :firstname, null: false
t.string :lastname, null: false
t.string :username, null: false
t.boolean :active, null: false, default: true
t.date :hiredate, null: false
t.date :termdate
t.timestamps
end
add_index :users, [:id], :unique => true
execute "ALTER TABLE users ADD PRIMARY KEY (id);"
end
end
The problem I'm having is with testing in order to create a user with the factory I have to have a begin rescue block. The first time it fails and the second time it works perfectly. I have no idea why this happens. Here is the first part of users_spec.rb:
describe User do
before(:all)do
begin
#user = FactoryGirl.create(:user)
rescue
#user = FactoryGirl.create(:user)
end
end
end
I'm contemplating just having the id column perform as it should in a rails app and making a new extensions column, but I would appreciate someone helping me with this issue. Thank you for the help!