I'm trying to create a contact form in a phoenix 1.3 app. I used mix phx.gen.html to create the relevant files. However, I'm getting a compilation error when trying to start the server:
== Compilation error on file lib/iotc/web/controllers/email_controller.ex ==
** (CompileError) lib/iotc/web/controllers/email_controller.ex:7: Email.__struct__/1 is undefined, cannot expand struct Email
(stdlib) lists.erl:1354: :lists.mapfoldl/3
lib/iotc/web/controllers/email_controller.ex:6: (module)
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
Looking at some other posts with a similar issue, it could be related to the alias, but I have alias Itoc.Contact in the controller, and I don't think alias Iotc.Contact.Email would be right here.
email_controller.ex
defmodule Iotc.Web.EmailController do
use Iotc.Web, :controller
alias Iotc.Contact
def index(conn, _params) do
changeset = Email.changeset(%Email{})
emails = Contact.list_emails()
render(conn, "index.html", emails: emails, changeset: changeset)
end
...
email.ex
defmodule Iotc.Contact.Email do
use Ecto.Schema
import Ecto.Changeset
alias Iotc.Contact.Email
schema "contact_emails" do
field :email, :string
field :event, :string
field :message, :string
field :name, :string
timestamps()
end
#doc false
def changeset(%Email{} = email, attrs) do
email
|> cast(attrs, [:name, :email, :message, :event])
|> validate_required([:name, :email, :message, :event])
end
end
With respect to this
OK makes sense. I've updated it to the controller to : changeset =
Contact.Email.changeset(%Contact.Email{}) But I now get: warning:
function Iotc.Contact.Email.changeset/1 is undefined or private. Did
you mean one of: * changeset/2
You only have one function changeset/2 defined in the Email module.
But you're doing Contact.Email.changeset(%Contact.Email{}) passing only one argument. Do ``Contact.Email.changeset(%Contact.Email{})` and it should work.
The /2 part of the signature tells you the arity of the function, namely how many arguments takes.
Related
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
I've added a many to many association to a couple of my models and it seems to work fine in isolation--meaning w/o the GraphQL schema declarations. Here is the code for one of my models:
use Ecto.Schema
import Ecto.Changeset
alias Trader.Collect.Card
schema "users" do
field(:email, :string)
field(:first_name, :string)
field(:last_name, :string)
field(:password, :string)
field(:username, :string)
many_to_many(:cards, Card, join_through: "user_cards")
timestamps()
end
#doc false
def changeset(user, attrs) do
user
|> cast(attrs, [:first_name, :last_name, :email, :username, :password])
|> validate_required([:first_name, :last_name, :email, :username, :password])
end
end
And here is the GraphQL type declaration:
defmodule TraderWeb.Schema.Types.User do
use Absinthe.Schema.Notation
#desc "User model representation"
object :user do
field(:id, non_null(:id))
field(:first_name, non_null(:string))
field(:last_name, non_null(:string))
field(:username, non_null(:string))
field(:email, non_null(:string))
field(:password, non_null(:string))
# field(:cards, list_of(:card), resolve: assoc(:cards))
end
end
This is the top level schema definition for the Absinthe/GraphQL part:
defmodule TraderWeb.Schema.Schema do
use Absinthe.Schema
import_types(Absinthe.Type.Custom)
# Import Types individually here
import_types(TraderWeb.Schema.Types.{
User,
Card,
CardSet
})
# import queries here
import_types(TraderWeb.Schema.Queries.{
User,
Card,
CardSet
})
query do
import_fields(:user_queries)
import_fields(:card_queries)
import_fields(:card_set_queries)
end
end
Please note the cards field is commented out in the Type. Everything works fine in this case, however, if I uncomment that cards field, I get the following error:
== Compilation error in file lib/trader_web/schema/types/user.ex ==
** (CompileError) lib/trader_web/schema/types/user.ex:12: undefined function assoc/1
(elixir) src/elixir_locals.erl:108: :elixir_locals."-ensure_no_undefined_local/3-lc$^0/1-0-"/2
(elixir) src/elixir_locals.erl:108: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(elixir) lib/kernel/parallel_compiler.ex:229: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
I've googled this issue pretty aggressively and can't find anything for this. It's unclear where that assoc function even lives--is it an ecto thing? or an absinthe-ecto thing? I also found sample code somewhere using dataloader but I couldn't get that working at all.
I appreciate any thoughts and ideas you all have!
thanks
You will either need the (deprecated) Absinthe.Ecto package, or work with the new Dataloader. There is a section in the documentation for Absinthe on Ecto best practices, which describes the new syntax for using dataloader https://hexdocs.pm/absinthe/ecto.html#dataloader. As that also requires additions to your context, it would be too much to add the complete setup here, but the docs are pretty good.
I'm trying to determine the right way to validate a many-to-many relationship in Ecto 2. I have a Conversation model that needs to have many members, and Users can be part of many conversations, so I've established the models like so:
# User Model
defmodule MyApp.User do
...
schema "users" do
....
many_to_many :conversations, Conversation, join_through: "conversations_users"
...
end
...
end
# Conversation Model
defmodule MyApp.Conversation do
...
schema "conversations" do
has_many :messages, Message
many_to_many :members, User, join_through: "conversations_users"
timestamps()
end
def changeset(struct, _params) do
struct
|> validate_member_count
end
defp validate_member_count(changeset) do
members = Repo.all(assoc(changeset, :members))
valid? = length(members) == 2
if valid? do
add_error(changeset, :members, "foo")
else
changeset
end
end
end
However, I just can't get this to work. I've written a simple test to verify that the validations run correctly, but I keep getting the following error:
# Test
test "fails to validate a conversation with less than two members" do
changeset = Conversation.changeset(%Conversation{}, %{})
{message, []} = changeset.errors[:members]
assert message === "must have at least two members"
end
** (FunctionClauseError) no function clause matching in Ecto.Changeset.add_error/4
I'm having a hard time understanding what I'm doing wrong. It seems like it can't find the function, but I've checked the documentation and it seems like Ecto.Changeset.add_error/4 is definitely right, and the arguments to it seem correct as well.
My best guess is that I need to do something in the validation before calling my custom validator, but I just don't know what I should do.
There are 2 mistakes:
You're passing a MyApp.Conversation to validate_member_count, not an Ecto.Changeset. You can convert an Ecto Schema defining Struct into an Ecto.Changeset using Ecto.Changeset.change/1:
def changeset(struct, _params) do
struct
|> change
|> validate_member_count
end
Ecto.assoc/2 accepts an Ecto Schema Struct, not an Ecto.Changeset. You can access the underlying struct from an Ecto.Changeset using .data:
members = Repo.all(assoc(changeset.data, :members))
Final code:
def changeset(struct, _params) do
struct
|> change
|> validate_member_count
end
defp validate_member_count(changeset) do
members = Repo.all(assoc(changeset.data, :members))
valid? = length(members) == 2
if valid? do
add_error(changeset, :members, "foo")
else
changeset
end
end
I'm trying to create a small Phoenix application, and having troubles finding the best way to process user input that came to the model from a controller.
I have 2 models and 1 controller:
defmodule MyApp.Post do
use MyApp.Web, :model
schema "posts" do
field :title, :string
field :text, :string
field :comments_count, :integer
has_many :comments, MyApp.Comment
timestamps()
end
end
defmodule MyApp.Comment do
use MyApp.Web, :model
schema "comments" do
field :text, :string
field :parent_path, :string # I want to store comments in a tree, using "Materialized Path" method
belongs_to :post, MyApp.Post
timestamps()
end
defmodule Ops do
# I keep all operations that are related to comments in this module
alias MyApp.{Repo, Comment}
def create_by_user(params) do
# params came straight from the controller. Expected fields are:
# 1. text - text of the comment, required
# 2. post_id - id of the post, required
# 3. parent_id - id of the parent comment, optional
# This function must:
# 1. Validate presence of the text (this is simple)
# 2. Check that post with given "post_id" exists
# 3. If "parent_id" is given:
# 3.1. Check that parent comment exists and belongs to the same post
# 3.2. Based on fields of parent comment, calculate the "parent_path" value of the new comment
# 4. If input is valid, insert a new comment to database and update post's "comments_count" field
end
end
end
defmodule MyApp.CommentController do
use MyApp.Web, :controller
alias MyApp.{Post, Comment}
def create(conn, params) do
case Comment.Ops.create_by_user(params) do
{:ok, comment} -> conn |> put_status(200) |> json("not implemented yet")
{:error, changeset} -> conn |> put_status(422) |> json("not implemented yet")
# Also, in case of error, it would be nice to look into changeset.errors and if post wasn't found, return 404
end
end
end
What is the most elegant implementation of Comment.Ops.create_by_user function?
I’m using Rails 4.2.3. I have three fields in my model — name, first_name, and last_name. would like to have a validation rule in my model that causes a save to fail if the “name” field is empty unless either the first_name or last_name field is not empty. SOoI tried
validates_presence_of :name, :unless => !:first_name.empty? or !:last_name.empty?
but this doesn’t work. I get the error below
undefined method `validate' for true:TrueClass
What is the proper way to write the validation rule above?
Everything you need to know is here.
You can write the rule by defining a separate method for it:
class Whatever < ActiveRecord::Base
validates :name, presence: true, unless: :firstname_or_surname?
def firstname_or_surname?
firstname.present? || surname.present?
end
end
Or you can use a Proc to define it inline:
class Whatever < ActiveRecord::Base
validates :name, presence: true,
unless: Proc.new { |a| a.firstname.present? || a.surname.present? }
end