Rails 4, Rspec, FactoryGirl - factory-bot

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!

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.

invalid or unknown type App.ImageFile.Type for field :filename Phoneix App

I'm attempting to use Arc to allow for uploading images to Google Cloud Storage, (following this - https://maartenvanvliet.nl/2017/08/05/arc_uploads_to_google_cloud_storage/) however, I'm getting a compile error when running mix ecto.migrate:
== Compilation error on file lib/codsonline/assets/image.ex ==
** (ArgumentError) invalid or unknown type App.ImageFile.Type for field :filename
lib/ecto/schema.ex:1886: Ecto.Schema.check_type!/3
lib/ecto/schema.ex:1507: Ecto.Schema.__field__/4
lib/codsonline/assets/image.ex:8: (module)
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in
Kernel.ParallelCompiler.spawn_compilers/1
I've changed my schema from:
schema "images" do
field :filename, :string
field :name, :string
timestamps()
end
to:
schema "images" do
field :filename, App.ImageFile.Type
field :name, :string
timestamps()
end
My uploaders/image_file.ex:
defmodule Codsonline.ImageFile do
use Arc.Definition
# Include ecto support (requires package arc_ecto installed):
use Arc.Ecto.Definition
#versions [:original]
def __storage, do: Arc.Storage.GCS
def gcs_object_headers(:original, {file, _scope}) do
[content_type: MIME.from_path(file.file_name)]
end
end
Have I got the syntax wrong somewhere?
Update
#dogbert pointed out I wasn't Using Codsonline instead of App, the below now works:
schema "images" do
field :filename, Codsonline.ImageFile.Type
field :name, :string
timestamps()
end

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

ActiveRecord::RecordInvalid: Validation failed: Team must exist

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