diff --git a/lib/code_corps/model/skill.ex b/lib/code_corps/model/skill.ex index aa7ab5ee3..ae28ff457 100644 --- a/lib/code_corps/model/skill.ex +++ b/lib/code_corps/model/skill.ex @@ -4,16 +4,16 @@ defmodule CodeCorps.Skill do @type t :: %__MODULE__{} schema "skills" do - field :title, :string field :description, :string field :original_row, :integer - - has_many :role_skills, CodeCorps.RoleSkill - has_many :roles, through: [:role_skills, :role] + field :title, :string has_many :project_skills, CodeCorps.ProjectSkill has_many :projects, through: [:project_skills, :project] + has_many :role_skills, CodeCorps.RoleSkill + has_many :roles, through: [:role_skills, :role] + timestamps() end @@ -23,7 +23,7 @@ defmodule CodeCorps.Skill do @spec changeset(CodeCorps.Skill.t, map) :: Ecto.Changeset.t def changeset(struct, params \\ %{}) do struct - |> cast(params, [:title, :description, :original_row]) + |> cast(params, [:description, :original_row, :title]) |> validate_required([:title]) |> unique_constraint(:title) end diff --git a/lib/code_corps/skills/skills.ex b/lib/code_corps/skills/skills.ex new file mode 100644 index 000000000..0aaabeb8f --- /dev/null +++ b/lib/code_corps/skills/skills.ex @@ -0,0 +1,38 @@ +defmodule CodeCorps.Skills do + @moduledoc ~S""" + Work with skills. + """ + + alias CodeCorps.{ + Repo, + Skill, + UserSkill + } + + import Ecto.Query + + @doc """ + Find the most popular skills, in order, with a limit. + """ + @spec popular(map) :: [Skill.t] + def popular(params \\ %{}) + def popular(%{"limit" => limit}), do: limit |> Integer.parse() |> apply_limit() + def popular(_), do: do_popular() + + defp apply_limit({limit, _rem}) when limit <= 100, do: do_popular(limit) + defp apply_limit(_), do: do_popular() + + @spec do_popular(pos_integer) :: [Skill.t] + def do_popular(limit \\ 10) do + query = + from s in Skill, + join: us in UserSkill, + on: s.id == us.skill_id, + group_by: s.id, + order_by: [desc: count(us.skill_id)], + limit: ^limit + + query + |> Repo.all() + end +end diff --git a/lib/code_corps_web/controllers/skill_controller.ex b/lib/code_corps_web/controllers/skill_controller.ex index e3070b111..86d1f1001 100644 --- a/lib/code_corps_web/controllers/skill_controller.ex +++ b/lib/code_corps_web/controllers/skill_controller.ex @@ -2,7 +2,7 @@ defmodule CodeCorpsWeb.SkillController do @moduledoc false use CodeCorpsWeb, :controller - alias CodeCorps.{Skill, User, Helpers.Query} + alias CodeCorps.{Helpers.Query, Skill, Skills, User} action_fallback CodeCorpsWeb.FallbackController plug CodeCorpsWeb.Plug.DataToAttributes @@ -22,7 +22,7 @@ defmodule CodeCorpsWeb.SkillController do end end - @spec create(Plug.Conn.t, map) :: Conn.t + @spec create(Conn.t, map) :: Conn.t def create(%Conn{} = conn, %{} = params) do with %User{} = current_user <- conn |> CodeCorps.Guardian.Plug.current_resource, {:ok, :authorized} <- current_user |> Policy.authorize(:create, %Skill{}, params), @@ -34,12 +34,17 @@ defmodule CodeCorpsWeb.SkillController do end @spec load_skills(map) :: list(Skill.t) + defp load_skills(%{"popular" => "true"} = params) do + params + |> Skills.popular() + |> preload() + end defp load_skills(%{} = params) do Skill |> Query.id_filter(params) |> Query.title_filter(params) |> Query.limit_filter(params) - |> Repo.all + |> Repo.all() end @preloads [:role_skills] diff --git a/test/lib/code_corps/skills/skills_test.exs b/test/lib/code_corps/skills/skills_test.exs new file mode 100644 index 000000000..6e1443bb0 --- /dev/null +++ b/test/lib/code_corps/skills/skills_test.exs @@ -0,0 +1,38 @@ +defmodule CodeCorps.AccountsTest do + @moduledoc false + + use CodeCorps.DbAccessCase + + alias CodeCorps.Skills + + describe "popular/1" do + test "returns popular skills in order with a limit" do + [least_popular, somewhat_popular, most_popular] = insert_list(3, :skill) + insert_list(3, :user_skill, skill: most_popular) + insert_list(2, :user_skill, skill: somewhat_popular) + insert_list(1, :user_skill, skill: least_popular) + + [first_result, last_result] = Skills.popular(%{"limit" => "2"}) + + assert first_result == most_popular + assert last_result == somewhat_popular + end + + test "defaults limit to 10" do + skills = insert_list(11, :skill) + skills |> Enum.each(fn skill -> insert(:user_skill, skill: skill) end) + + results = Skills.popular() + + assert results |> Enum.count() == 10 + end + + test "ignores non-number limits" do + insert(:user_skill) + + results = Skills.popular(%{"limit" => "apples"}) + + assert results |> Enum.count() == 1 + end + end +end diff --git a/test/lib/code_corps_web/controllers/skill_controller_test.exs b/test/lib/code_corps_web/controllers/skill_controller_test.exs index 49ba0928d..111d82296 100644 --- a/test/lib/code_corps_web/controllers/skill_controller_test.exs +++ b/test/lib/code_corps_web/controllers/skill_controller_test.exs @@ -53,6 +53,20 @@ defmodule CodeCorpsWeb.SkillControllerTest do returned_skills_length = json["data"] |> length assert returned_skills_length == 5 end + + test "lists popular skills", %{conn: conn} do + [skill_1, skill_2] = insert_pair(:skill) + insert(:user_skill, skill: skill_1) + insert_list(2, :user_skill, skill: skill_2) + + params = %{"popular" => "true"} + path = conn |> skill_path(:index, params) + + conn + |> get(path) + |> json_response(200) + |> assert_ids_from_response([skill_2.id, skill_1.id]) + end end describe "show" do