How to put column length or not null constraints with mix phx.gen.html - phoenix-framework

I would like to put length on DB columns with mix phx.gen.html.
How can I do it?

I found I should not put length, when I use phx.gen.html.
I should put length on migration file which is in priv/repo/migrations.
defmodule App.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :name, :string, size: 20, null: false
add :email, :string, size: 120, null: false
timestamps()
end
end
end

Related

How to spit Ecto Migrations into partials?

I have many migration files using the same column names name, address, phone, etc.
In my migrations, I want to make a partial that calls them instead of writing it over and over:
defmodule App.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:settings) do
add :blah, :string
App.Repo.Migrations.Common.stuff
defmodule App.Repo.Migrations.Common do
def stuff do
add :name, :string
add :address, :string
add :phone, :string
But I get an error (UndefinedFunctionError) function App.Repo.Migrations.Common.stuff/0 is undefined.stuff
What's the proper way to split migrations? In Rails this was done with require_relative
I can guess you put the file common.ex inside priv/repo/migration which is not included while compilation
for this to work you need 2 things:
move this file common.ex inside lib/, in order to be compiled and loaded.
add this line use Ecto.Migration above def stuff do, for the definition of add() to be imported.
You can define a function, just like timestamps/1 :
def timestamps(opts \\ []) when is_list(opts) do
opts = Keyword.merge(Runner.repo_config(:migration_timestamps, []), opts)
opts = Keyword.put_new(opts, :null, false)
{type, opts} = Keyword.pop(opts, :type, :naive_datetime)
{inserted_at, opts} = Keyword.pop(opts, :inserted_at, :inserted_at)
{updated_at, opts} = Keyword.pop(opts, :updated_at, :updated_at)
if inserted_at != false, do: add(inserted_at, type, opts)
if updated_at != false, do: add(updated_at, type, opts)
end

Insert Ecto model with already existing model as an association

I have 2 models, entries:
schema "entries" do
belongs_to :exception, Proj.Exception
field :application, :string
end
And exceptions:
schema "exceptions" do
field :name, :string
end
The migration script:
def change do
create table(:exceptions) do
add :name, :string, null: false
end
create table(:entries) do
add :exception_id, references(:exceptions), null: false
add :application, :string, null: false
end
end
My goal is to store exceptions that happen in another system. I want the project to be able to store each exception in the second table exception if they are not already there and then store the application name and the exception id in the first table entries. There will be 1000s of records in entries and a handful in exceptions.
Assuming entry_params uses this JSON format:
{
exception: "NPE",
application: "SomeApp"
}
the method that should create the entries:
def create(conn, %{"entry" => entry_params}) do
exception = Repo.get_by(Exception, name: entry_params["exception"]) ||
Repo.insert!(%Exception{name: entry_params["exception"]})
changeset =
Entry.changeset(%Entry{}, entry_params)
|> Ecto.Changeset.put_assoc(:exception, exception)
Repo.insert!(changeset)
end
This will print out:
** (ArgumentError) unknown assoc `exception` in `put_assoc`
If I change the entries model to use has_one instead of belongs_to (and I think belongs_to "feels" bad here. An entry does not belong to an exception, it just has an exception) it throws the following:
** (Postgrex.Error) ERROR (not_null_violation): null value in column "exception_id" violates not-null constraint
table: entries
column: exception_id
What I want basically to first create an Exception (if it does not exist) and than create a new Entry of a system error and put the previously begotten Exception in the entry as an association.
What is wrong here?
Typo. belongs_to :exception, Proj.Exception should be belongs_to :exceptions, Proj.Exception
Association. Based on the data model in the question, I think the put_assoc is the wrong way around because in the data schema in the question, an exception has_many entries and an entry belongs_to exceptions. Ecto.Changeset.put_assoc(entries_changeset, :exception, exception) should be Ecto.Changeset.put_assoc(exception_changeset, :entries, entries)
Attempted solution:
entries schema:
schema "entries" do
field :application, :string
belongs_to :exceptions, Proj.Exception, on_replace: :nilify
end
exceptions schema:
schema "exceptions" do
field :name, :string
has_many :entry, Proj.Entry, on_delete: :delete_all, on_replace: :delete
end
migration script:
def change do
create table(:exceptions) do
add :name, :string, null: false
end
create table(:entries) do
add :application, :string, null: false
add :exception_id, references(:exceptions)
end
end
Assuming entry_params uses this JSON format:
{
exception: "NPE",
application: "SomeApp"
}
create or update the exceptions and the associated entries:
def create(conn, %{"entry" => entry_params}) do
new_entry = Entry.changeset(%Entry{}, entry_params)
changeset =
case Repo.get_by(Exception, name: entry_params["exception"]) do
:nil ->
exception = %Exception{name: entry_params["exception"]} |> Repo.insert!
Ecto.Changeset.build_assoc(exception, :entries, [new_entry])
struct ->
changeset = Ecto.Changeset.change(struct)
data = Ecto.Changeset.preload(changeset, :entries) |> Map.get(:model) # Ecto 1.x
# data = Ecto.Changeset.preload(changeset, :entries) |> Map.get(:data) # Ecto 2.0.x
Ecto.Changeset.put_assoc(changeset, :entries, [new_entry | data.entries])
end
Repo.insert!(changeset)
end

Sequel migration - only add column if table does not have it yet

I am trying to create a Sequel migration that goes through a list of tables, and try to add a specific column if the table does not of that column yet.
For example:
Sequel.migration do
change do
table_list = [:table1, :table2, :table3]
table_list.each do |t|
if t does not have :specific_column yet
alter_table(t) do
add_column :sepcific_column, type: String
end
end
end
end
end
Is is possible to tell if the column already exist in a table, so I can do stuff accordingly?
Yes, it is possible. The method Dataset#columns returns a list of columns. This result can be used to be checked with include?
Full example:
Sequel.migration do
change do
table_list = [:table1, :table2, :table3]
table_list.each do |t|
if ! self[t].columns.include?(:specific_column)
alter_table(t) do
add_column :specific_column, type: String
end
end
end
end
end
or
Sequel.migration do
change do
table_list = [:table1, :table2, :table3]
table_list.each do |t|
alter_table(t) do
add_column :specific_column, type: String
end unless self[t].columns.include?(:specific_column)
end
end
end
end

Sequel : DRY between schema migration and model validate method

I'm wondering if I miss a way to avoid repeat validation code in my Sequel::Model#validate subclass method since I've already put all constraints into my migration file.
Here's a simple example of what I'm talking about :
Sequel.migration do
change do
create_table :users do
primary_key :id
String :name, :null => false, :unique => true
end
end
end
class User < Sequel::Model
def validate
super
validates_presence :name
validates_unique :name
validates_type String :name
end
end
It seems very painful and errors prone to have to repeat all the constraints in the validate method. Did I miss something or there's no other way to do that ?
Any advice will be appreciated, thanks
Sequel has some nice plugins and extensions.
Sequel::Model.plugin(:auto_validations)
Sequel::Model.plugin(:constraint_validations)
and
DB.extension(:constraint_validations)
auto_validations
The auto_validations plugin automatically sets up three types of
validations for your model columns:
type validations for all columns
not_null validations on NOT NULL columns (optionally, presence
validations)
unique validations on columns or sets of columns with unique indexes
See http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/AutoValidations.html
constraint_validations
The constraint_validations extension is designed to easily create
database constraints inside create_table and alter_table blocks. It
also adds relevant metadata about the constraints to a separate table,
which the constraint_validations model plugin uses to setup automatic
validations.
See http://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/constraint_validations_rb.html
and
http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/ConstraintValidations.html
Your example would look like this
Sequel::Model.plugin(:auto_validations)
Sequel::Model.plugin(:constraint_validations)
Sequel.migration do
up do
extension(:constraint_validations)
create_table :users do
primary_key :id
String :name, :null => false, :unique => true
validate do
presence :name,
name: :presence_name
end
end
end
down do
extension(:constraint_validations)
drop_table(:users)
end
end
class User < Sequel::Model
end
I think, it's normal. Don't worry.

How can I change an index defined on a table?

I have an index defined like
add_index :users, :email, :unique => true
Is there any way to change this index to drop the UNIQUE constraint, something like 'change_index'?
Or is it the only way to drop the index and add it again without the UNIQUE constraint?
There is no "change_index" in migrations so you can do:
remove_index :users, :email
add_index :users, :email, :unique => false
Different index types are typically implemented in very different ways in the database you are using. A primary index is very different from any secondary index. And unique indexes are typically different from search indexes to facilitate their primary use case: to quickly determine if a value is already present in a column vs. allowing efficient searches.
As such, (depending on your DBMS) you can't change an existing index. You safest bet in any case is to drop the index and create a new one. This can be done during live operations. There is no need to shutdown neither the database nor your rails app.
This should be done in two steps - first creating the index you need in one migration file and dropping the existing one in another.
First file:
class AddNewIndex < ActiveRecord::Migration
disable_ddl_transaction!
def change
add_index(
:users,
:email,
unique: false,
name: "index_non_unique_emails_on_users",
algorithm: :concurrently
)
end
end
Second file:
class DropOldListCIndexFromPushLogs < ActiveRecord::Migration
def change
remove_index(
:users,
name: <existing unique index name> # have a look in db/schema.rb
) if index_exists?(:users, name: "index_non_unique_emails_on_users")
end
end
Please have a look at Zero Downtime Migrations' readme if you're new to safe migrations.

Resources