struct__/1 is undefined, cannot expand struct error Phoenix 1.3 - phoenix-framework

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

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

Representing Elixir/Ecto associations in GraphQL with absinthe

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.

Validating Ecto "many-to-many" relationships

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

An elegant way to validate and process a complex input using changesets

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?

How to conduct Rails model validation witih or condition?

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

Resources