### Install Project Dependencies Source: https://github.com/team-alembic/realworld/blob/main/README.md Installs the necessary Elixir and Erlang dependencies for the RealWorld example application after cloning the repository. ```bash cd realworld mix deps.get ``` -------------------------------- ### Setup and Migrate Database with Ash Postgres Source: https://github.com/team-alembic/realworld/blob/main/README.md Sets up the PostgreSQL database and applies migrations using the ash_postgres integration for the RealWorld application. This ensures the database schema is correctly configured. ```bash mix ash.setup && mix ash.migrate ``` -------------------------------- ### Start Phoenix Server Source: https://github.com/team-alembic/realworld/blob/main/README.md Starts the Phoenix development server for the RealWorld application. This allows you to view and interact with the application in your browser. ```bash mix phx.server ``` -------------------------------- ### Start Phoenix Server in IEx Source: https://github.com/team-alembic/realworld/blob/main/README.md Starts the Phoenix development server within an IEx (Interactive Elixir) session. This is useful for debugging and interactive development. ```bash iex -S mix phx.server ``` -------------------------------- ### Clone RealWorld Repo Source: https://github.com/team-alembic/realworld/blob/main/README.md Clones the RealWorld example application repository from GitHub. This is the first step in setting up the project locally. ```bash git clone https://github.com/team-alembic/realworld.git ``` -------------------------------- ### Run Project Tests Source: https://github.com/team-alembic/realworld/blob/main/README.md Executes the automated test suite for the RealWorld example application. This is crucial for verifying the correctness and stability of the codebase. ```bash mix test ``` -------------------------------- ### Create Test Database and Migrate Source: https://github.com/team-alembic/realworld/blob/main/README.md Creates a separate PostgreSQL database for testing and applies migrations using ash_postgres. This isolates test data from development data. ```bash MIX_ENV=test mix ash_postgres.create && MIX_ENV=test mix ash.migrate ``` -------------------------------- ### Ash and Phoenix Configuration for RealWorld App Source: https://context7.com/team-alembic/realworld/llms.txt Configures the RealWorld Elixir application, including Ash domains, Ecto repository, JWT signing secret, and Phoenix LiveView settings. It also lists the essential setup commands for dependencies, database, migrations, and starting the server. ```elixir # config/config.exs import Config # Define Ash domains for the application config :realworld, :ash_domains, [ Realworld.Accounts, # User authentication and management Realworld.Articles, # Article, comment, tag, and favorite resources Realworld.Profiles # User following/follower relationships ] # Database repository config :realworld, ecto_repos: [Realworld.Repo] # Token signing secret for JWT authentication config :realworld, token_signing_secret: "your_secret_key_here_at_least_64_characters_long" # Phoenix endpoint configuration config :realworld, RealworldWeb.Endpoint, url: [host: "localhost"], pubsub_server: Realworld.PubSub, live_view: [signing_salt: "random_salt"] # Setup commands: # mix deps.get # Install dependencies # mix ash.setup # Create database # mix ash.migrate # Run migrations # mix phx.server # Start server at localhost:4000 ``` -------------------------------- ### Article Authorization Policies in Elixir Source: https://context7.com/team-alembic/realworld/llms.txt Defines authorization policies for article actions (read, create, update, destroy) within the Ash Framework. It uses `always()`, `actor_present()`, and `relates_to_actor_via(:user)` for policy enforcement. The example shows how to use these policies with Ash.Changeset for updating an article, returning an error if the user is not authorized. ```elixir defmodule Realworld.Articles.Article do use Ash.Resource policies do # Anyone can read articles policy action_type(:read) do authorize_if always() end # Only authenticated users can create articles policy action_type(:create) do authorize_if actor_present() end # Only article owner can update policy action_type(:update) do authorize_if relates_to_actor_via(:user) end # Only article owner can delete policy action_type(:destroy) do authorize_if relates_to_actor_via(:user) end end end # Usage in LiveView: # The actor is passed automatically, authorization is enforced # {:ok, article} = article # |> Ash.Changeset.for_update(:update, params, actor: current_user) # |> Ash.update() # # Returns {:error, %Ash.Error.Forbidden{}} if user doesn't own the article ``` -------------------------------- ### Create and Publish Article with Ash Source: https://context7.com/team-alembic/realworld/llms.txt Enables the creation and publishing of new articles, including title, description, body, and tags, using the Ash Framework's Articles domain. It requires the current user as the actor and returns the created article struct upon success. ```elixir alias Realworld.Articles # Publish a new article {:ok, article} = Articles.Article |> Ash.Changeset.for_create(:publish, %{ title: "Introduction to Ash Framework", description: "Learn the basics of building applications with Ash", body_raw: "# Getting Started\n\nAsh is a declarative framework...", tags: [ %{name: "elixir"}, %{name: "ash"}, %{name: "tutorial"} ] }, actor: current_user) |> Ash.create() # Result: # %Realworld.Articles.Article{ # id: "660e8400-e29b-41d4-a716-446655440001", # slug: "introduction-to-ash-framework", # title: "Introduction to Ash Framework", # description: "Learn the basics of building applications with Ash", # body_raw: "# Getting Started\n\nAsh is a declarative framework...", # body: "

Getting Started

\n

Ash is a declarative framework...

", # favorites_count: 0, # created_at: ~U[2025-01-15 12:00:00Z], # user_id: "550e8400-e29b-41d4-a716-446655440000" # } ``` -------------------------------- ### Manage Article Tags Source: https://context7.com/team-alembic/realworld/llms.txt Provides functionality to list all available tags within the system. Tags are automatically created and associated with articles during the article publishing or updating process. ```elixir alias Realworld.Articles.Tag # List all tags {:ok, tags} = Tag |> Ash.read() ``` -------------------------------- ### User Registration and Authentication with Ash Source: https://context7.com/team-alembic/realworld/llms.txt Handles user registration, sign-in, and retrieval by username using the Ash Framework's Accounts domain. It requires email, username, and password for creation, and email/password for sign-in. The result is a user struct or an error. ```elixir alias Realworld.Accounts {:ok, user} = Accounts.User |> Ash.Changeset.for_create(:register_with_password, %{ email: "user@example.com", username: "johndoe", password: "secure_password_123", password_confirmation: "secure_password_123" }) |> Ash.create() # Result: # %Realworld.Accounts.User{ # id: "550e8400-e29b-41d4-a716-446655440000", # email: "user@example.com", # username: "johndoe", # bio: nil, # image: "https://api.realworld.io/images/smiley-cyrus.jpeg", # created_at: ~U[2025-01-15 10:30:00Z], # updated_at: ~U[2025-01-15 10:30:00Z] # } # Sign in an existing user {:ok, user} = Accounts.User |> Ash.Query.for_read(:sign_in_with_password, %{ email: "user@example.com", password: "secure_password_123" }) |> Ash.read_one() # Get user by username {:ok, user} = Accounts.get_user_by_username("johndoe") ``` -------------------------------- ### List Articles with Filters using Ash Source: https://context7.com/team-alembic/realworld/llms.txt Enables querying and listing articles with support for pagination, filtering by tag, author, or favorited user using the Ash Framework. It accepts filter parameters and pagination options, returning a paginated result set. ```elixir alias Realworld.Articles # List all articles with pagination {:ok, page} = Articles.list_articles( %{filter: %{}, private_feed?: false}, page: [limit: 20, offset: 0], actor: current_user ) # Result: # %{ # count: 45, ``` -------------------------------- ### List Articles with Filters Source: https://context7.com/team-alembic/realworld/llms.txt Retrieves a list of articles based on various filters such as tags, author ID, or a private feed for followed users. It supports pagination and requires an actor for authentication and authorization. ```elixir alias Realworld.Articles # Filter by tag {:ok, page} = Articles.list_articles( %{filter: %{tag: "elixir"}, private_feed?: false}, page: [limit: 10, offset: 0], actor: current_user ) # Filter by author ID {:ok, page} = Articles.list_articles( %{filter: %{author: "550e8400-e29b-41d4-a716-446655440000"}, private_feed?: false}, page: [limit: 10, offset: 0] ) # Get private feed (articles from followed users only) {:ok, page} = Articles.list_articles( %{filter: %{}, private_feed?: true}, page: [limit: 20, offset: 0], actor: current_user ) ``` -------------------------------- ### Update Existing Article with Ash Source: https://context7.com/team-alembic/realworld/llms.txt Provides functionality to update an existing article, including its title, description, content, and tags. This operation requires ownership of the article and utilizes Ash Framework for the update process, automatically regenerating the slug. ```elixir alias Realworld.Articles # Get article by slug {:ok, article} = Articles.get_article_by_slug("introduction-to-ash-framework") |> Ash.load([:tags, :user]) # Update the article {:ok, updated_article} = article |> Ash.Changeset.for_update(:update, %{ title: "Advanced Ash Framework Guide", description: "Deep dive into Ash capabilities", body_raw: "# Advanced Topics\n\nLet's explore...", tags: [ %{name: "elixir"}, %{name: "ash"}, %{name: "advanced"} ] }, actor: current_user) |> Ash.update() # The slug is automatically regenerated from the new title # Tags are managed: existing tags are kept/related, new ones created, removed ones unrelated ``` -------------------------------- ### LiveView Article Feed Component with Real-time Updates Source: https://context7.com/team-alembic/realworld/llms.txt This LiveView component displays paginated article lists and handles real-time updates for favorite counts using Phoenix PubSub. It manages the socket's state for pagination, filtering, and holds the list of articles and available tags. It also subscribes to 'favorite:created' and 'favorite:destroyed' events to update the UI dynamically. ```elixir defmodule RealworldWeb.PageLive.Index do use RealworldWeb, :live_view @impl true def mount(_params, _session, socket) do socket = socket |> assign(:page_offset, 0) |> assign(:page_limit, 20) |> assign(:filter_tag, nil) |> assign(:articles, []) |> assign(:active_view, :global_feed) {:ok, socket} end @impl true def handle_params(params, _url, socket) do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end defp apply_action(socket, :index, _params) do with {:ok, page} <- list_articles(socket), {:ok, tags} <- Realworld.Articles.Tag |> Realworld.Articles.read() do # Subscribe to real-time updates for each article Enum.each(page.results, fn article -> RealworldWeb.Endpoint.subscribe("favorite:created:#{article.id}") RealworldWeb.Endpoint.subscribe("favorite:destroyed:#{article.id}") end) socket |> assign(:articles, page.results) |> assign(:pages, ceil(page.count / socket.assigns.page_limit)) |> assign(:tags, tags) end end # Handle favorite event @impl true def handle_event("favorite-article", %{"article_id" => article_id}, socket) do case Articles.favorite(article_id, actor: socket.assigns.current_user) do {:ok, _} -> new_articles = update_article_in_list( socket.assigns.articles, article_id, &Map.merge(&1, %{favorites_count: &1.favorites_count + 1, is_favorited: true}) ) {:noreply, assign(socket, :articles, new_articles)} _ -> {:noreply, socket} end end # Handle real-time PubSub broadcasts from other users @impl true def handle_info(%Phoenix.Socket.Broadcast{topic: "favorite:created:" <> article_id}, socket) do articles = Enum.map(socket.assigns.articles, fn article -> if article.id == article_id do %{article | favorites_count: article.favorites_count + 1} else article end end) {:noreply, assign(socket, :articles, articles)} end end ``` -------------------------------- ### Create and Delete Comments Source: https://context7.com/team-alembic/realworld/llms.txt Manages comments on articles, allowing creation with body and article association, and deletion by the comment's author. It utilizes Ash Framework for data operations and publishes real-time notifications for comment events. ```elixir alias Realworld.Articles.Comment # Create a comment on an article {:ok, comment} = Comment |> Ash.Changeset.for_create(:create, %{ body: "Great article! Looking forward to more content like this.", article_id: article_id }, actor: current_user) |> Ash.create() # List comments for an article {:ok, comments} = Comment |> Ash.Query.for_read(:comments_by_article, %{article_id: article_id}) |> Ash.read() |> Ash.load([:user]) # Delete a comment (only author can delete) {:ok, _} = Articles.destroy_comment(comment, actor: current_user) # Comments publish PubSub events on create/destroy: # Topics: "comment:created:#{article_id}" and "comment:destroyed:#{article_id}" ``` -------------------------------- ### Ash Authorization Policies for Resource Access Control Source: https://context7.com/team-alembic/realworld/llms.txt This section describes the implementation of resource-level authorization using Ash policy blocks. These blocks define the rules and conditions under which users can access or perform actions on specific resources, ensuring secure access control within the application. -------------------------------- ### Favorite and Unfavorite Articles Source: https://context7.com/team-alembic/realworld/llms.txt Allows users to favorite and unfavorite articles, updating favorite counts automatically. It also provides functionality to check if an article is favorited and publishes real-time updates via PubSub. ```elixir alias Realworld.Articles # Favorite an article {:ok, favorite} = Articles.favorite(article_id, actor: current_user) # Check if article is favorited {:ok, favorite} = Articles.favorited(article_id, actor: current_user) # Returns favorite record if exists, error if not # Unfavorite an article {:ok, removed_favorite} = Articles.unfavorite( article_id, actor: current_user, return_destroyed?: true ) # Favorites are published to PubSub for real-time UI updates # Topics: "favorite:created:#{article_id}" and "favorite:destroyed:#{article_id}" ``` -------------------------------- ### Follow and Unfollow Users Source: https://context7.com/team-alembic/realworld/llms.txt Enables users to follow and unfollow other users, facilitating personalized content feeds. It provides functions to check following status, list followings, and includes an upsert option to prevent duplicate follow relationships. ```elixir alias Realworld.Profiles # Follow a user {:ok, follow} = Profiles.follow(target_user_id, actor: current_user) # Check if following a user {:ok, follow} = Profiles.following(target_user_id, actor: current_user) # Returns follow record if exists, error if not # List all users current user is following {:ok, followings} = Profiles.list_followings(current_user.id, actor: current_user) # Unfollow a user {:ok, _} = Profiles.unfollow(target_user_id, actor: current_user) # Use upsert to prevent duplicate follows: # upsert? true # upsert_identity :unique_follow ``` -------------------------------- ### LiveView Editor Component with AshPhoenix.Form Source: https://context7.com/team-alembic/realworld/llms.txt This LiveView component facilitates article creation and editing using AshPhoenix.Form for robust validation and submission. It supports both new article creation and editing existing articles by fetching data by slug. The component also handles form validation events and dynamic tag management for articles. ```elixir defmodule RealworldWeb.EditorLive.Index do use RealworldWeb, :live_view alias Realworld.Articles.Article @impl true def handle_params(params, _url, socket) do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end # Create new article form defp apply_action(socket, :new, _) do form = AshPhoenix.Form.for_create( Article, :publish, api: Realworld.Articles, actor: socket.assigns.current_user, forms: [auto?: true] ) |> to_form() assign(socket, form: form) end # Edit existing article form defp apply_action(socket, :edit, %{"slug" => slug}) do case Articles.get_article_by_slug(slug) |> Ash.load(:tags) do {:ok, article} -> form = AshPhoenix.Form.for_update( article, :update, actor: socket.assigns.current_user, forms: [auto?: true] ) |> to_form() assign(socket, form: form) _ -> redirect(socket, to: "/") end end @impl true def handle_event("validate", %{"form" => params}, socket) do form = AshPhoenix.Form.validate(socket.assigns.form, params, errors: false) {:noreply, assign(socket, form: form)} end def handle_event("save", _params, socket) do case AshPhoenix.Form.submit(socket.assigns.form) do {:ok, result} -> {:noreply, redirect(socket, to: "/article/#{result.slug}")} {:error, form} -> {:noreply, assign(socket, form: form)} end end # Dynamic tag management def handle_event("add_tag", %{"tag" => tag}, socket) do tag = String.trim(tag) form = AshPhoenix.Form.add_form( socket.assigns.form, "form[tags]", params: %{name: tag} ) {:reply, %{tag_added: true}, assign(socket, form: form)} end def handle_event("remove_tag", %{"path" => path}, socket) do form = AshPhoenix.Form.remove_form(socket.assigns.form, path) {:noreply, assign(socket, form: form)} end end ``` -------------------------------- ### Update User Profile with Ash Source: https://context7.com/team-alembic/realworld/llms.txt Allows updating an authenticated user's profile information such as username, bio, image, and email using Ash Framework. It requires the current user object and a changeset for the update operation. The result is the updated user struct. ```elixir alias Realworld.Accounts.User # Update user profile {:ok, updated_user} = user |> Ash.Changeset.for_update(:update, %{ username: "john_updated", bio: "Software developer passionate about Elixir", image: "https://example.com/avatar.jpg", email: "newemail@example.com" }, actor: user) |> Ash.update() # Result: # %Realworld.Accounts.User{ # id: "550e8400-e29b-41d4-a716-446655440000", # email: "newemail@example.com", # username: "john_updated", # bio: "Software developer passionate about Elixir", # image: "https://example.com/avatar.jpg", # updated_at: ~U[2025-01-15 11:45:00Z] # } ``` -------------------------------- ### Delete Article Source: https://context7.com/team-alembic/realworld/llms.txt Enables the deletion of an article by its owner, enforcing ownership via policy. It first retrieves the article by its slug and then proceeds with the deletion process. ```elixir alias Realworld.Articles # Get article by slug {:ok, article} = Articles.get_article_by_slug("old-article-slug") # Delete the article (only author can delete) {:ok, deleted_article} = Articles.destroy_article(article, actor: current_user) # Authorization policy ensures only article owner can delete: # policy action_type(:destroy) do # authorize_if relates_to_actor_via(:user) # end ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.