Skip to content

Release v0.5.0 #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 1, 2016
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.5.0 (2016-07-01)

* Add `ordered_by` and `ordered_by_direction` options for Ecto connections so we can sort a connection by field.

## 0.4.0 (2016-06-22)

* Update optional dependency for Ecto to support both Ecto v1.x and v2.x.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This library relies on the [GraphQL Elixir](https://github.com/graphql-elixir/gr
Add `graphql_relay` to your list of dependencies in `mix.exs`:

def deps do
[{:graphql_relay, "~> 0.4"}]
[{:graphql_relay, "~> 0.5"}]
end

## Configuration
Expand Down
2 changes: 1 addition & 1 deletion examples/todo/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule Todo.Mixfile do
{:phoenix_html, "~> 2.6"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:cowboy, "~> 1.0"},
{:graphql_relay, "~> 0.4"},
{:graphql_relay, "~> 0.5"},
{:plug_graphql, "~> 0.3"}]
end

Expand Down
6 changes: 3 additions & 3 deletions examples/todo/mix.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
%{"connection": {:hex, :connection, "1.0.3", "3145f7416be3df248a4935f24e3221dc467c1e3a158d62015b35bd54da365786", [:mix], []},
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:rebar, :make], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
"db_connection": {:hex, :db_connection, "1.0.0-rc.2", "c5b2329651ef046d85e47ec2c947cb0e3d10a69eb49fdb71e365e72d6758e4c5", [:mix], [{:sbroker, "~> 1.0.0-beta.2", [hex: :sbroker, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:connection, "~> 1.0.2", [hex: :connection, optional: false]}]},
"db_connection": {:hex, :db_connection, "1.0.0-rc.3", "d9ceb670fe300271140af46d357b669983cd16bc0d01206d7d3222dde56cf038", [:mix], [{:sbroker, "~> 1.0.0-beta.3", [hex: :sbroker, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:connection, "~> 1.0.2", [hex: :connection, optional: false]}]},
"decimal": {:hex, :decimal, "1.1.2", "79a769d4657b2d537b51ef3c02d29ab7141d2b486b516c109642d453ee08e00c", [:mix], []},
"ecto": {:hex, :ecto, "2.0.1", "cf97a4d353e14af3d3cc3b4452cfbd18b3aeee1fb4075475efeccec3853444a9", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:postgrex, "~> 0.11.2", [hex: :postgrex, optional: true]}, {:db_connection, "~> 1.0-rc.2", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}]},
"ecto": {:hex, :ecto, "2.0.2", "b02331c1f20bbe944dbd33c8ecd8f1ccffecc02e344c4471a891baf3a25f5406", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:postgrex, "~> 0.11.2", [hex: :postgrex, optional: true]}, {:db_connection, "~> 1.0-rc.2", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}]},
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []},
"graphql": {:hex, :graphql, "0.3.1", "d3bb5467877456cc2b33debc75407e9216567b10e35e83d5195e2d51e835e8c7", [:mix], []},
"graphql_relay": {:hex, :graphql_relay, "0.0.17", "d5ce633b85c9103ab0507dd3c4e9103d8d14ea9bd316d35fb9cbc9ba31234c5c", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}, {:graphql, "~> 0.3", [hex: :graphql, optional: false]}, {:ecto, "~> 1.1.4", [hex: :ecto, optional: true]}]},
"graphql_relay": {:hex, :graphql_relay, "0.5.0", "8ea16397d4dc4cc0d7bf8d47df0d4d8ad2710e59ddda78fcb581b90352b48159", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}, {:graphql, "~> 0.3", [hex: :graphql, optional: false]}]},
"phoenix": {:hex, :phoenix, "1.1.6", "7bf19002669c8f692f5a9c8d30dab7b49f3dc56228d5bde92a12fb426b4783c2", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}, {:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}]},
"phoenix_ecto": {:hex, :phoenix_ecto, "3.0.0", "b947aaf03d076f5b1448f87828f22fb7710478ee38455c67cc3fe8e9a4dfd015", [:mix], [{:ecto, "~> 2.0.0-rc", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.6", [hex: :phoenix_html, optional: true]}]},
"phoenix_html": {:hex, :phoenix_html, "2.6.0", "b9f7e091eb3d908586d9634596478fb9e577ee033d76f4ff327a745569bdd2d8", [:mix], [{:plug, "~> 0.13 or ~> 1.0", [hex: :plug, optional: false]}]},
Expand Down
70 changes: 61 additions & 9 deletions lib/graphql/relay/connection/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,60 @@ if Code.ensure_loaded?(Ecto) do
resolve(repo, query, args)
end

@doc """
Pass in an Ecto.Repo, an Ecto.Query, and Relay args to have the query
executed and results returned in the format Relay requires.

If you wish to have the results ordered by some field other than `id`,
use `ordered_by` and `ordered_by_direction`.

Example for returning data in reverse chronological order (newest first)
like you would do for a page listing blog posts:

args = %{
first: 10,
ordered_by: :inserted_at,
ordered_by_direction: :desc
}

Your GraphQL resolve function for the connection will look something like:

resolve: fn(blog, args, _ctx) ->
# Order by desc inserted_at
args = Map.merge(args, %{ordered_by: :inserted_at, ordered_by_direction: :desc})
query = Ecto.assoc(blog, :posts)
GraphQL.Relay.Connection.Ecto.resolve(Repo, query, args)
end
"""
def resolve(repo, query, args \\ %{}) do
before = cursor_to_offset(args[:before])
# `after` is a keyword http://elixir-lang.org/docs/master/elixir/Kernel.SpecialForms.html#try/1
a_after = cursor_to_offset(args[:after])
first = args[:first]
last = args[:last]
ordered_by_property = args[:ordered_by] || :id
ordered_by_direction = get_ordered_by_direction(args[:ordered_by_direction] || :asc)
opposite_ordered_by_direction = if ordered_by_direction == :asc, do: :desc, else: :asc
limit = Enum.min([first, last, connection_count(repo, query)])

query = if a_after do
from things in query, where: things.id > ^a_after
case ordered_by_direction do
:asc ->
query |> where([a], field(a, ^ordered_by_property) > ^a_after)
:desc ->
query |> where([a], field(a, ^ordered_by_property) < ^a_after)
end
else
query
end

query = if before do
from things in query, where: things.id < ^before
case ordered_by_direction do
:asc ->
query |> where([a], field(a, ^ordered_by_property) < ^before)
:desc ->
query |> where([a], field(a, ^ordered_by_property) > ^before)
end
else
query
end
Expand All @@ -97,13 +135,15 @@ if Code.ensure_loaded?(Ecto) do
end

query = if first do
from things in query, order_by: [asc: things.id], limit: ^limit
sort_values = Keyword.new([{ordered_by_direction, ordered_by_property}])
query |> order_by(^sort_values) |> limit(^limit)
else
query
end

query = if last do
from things in query, order_by: [desc: things.id], limit: ^limit
sort_values = Keyword.new([{opposite_ordered_by_direction, ordered_by_property}])
query |> order_by(^sort_values) |> limit(^limit)
else
query
end
Expand All @@ -112,7 +152,7 @@ if Code.ensure_loaded?(Ecto) do

edges = Enum.map(records, fn(record) ->
%{
cursor: cursor_for_object_in_connection(record),
cursor: cursor_for_object_in_connection(record, ordered_by_property),
node: record
}
end)
Expand Down Expand Up @@ -140,15 +180,23 @@ if Code.ensure_loaded?(Ecto) do
def cursor_to_offset(cursor) do
case Base.decode64(cursor) do
{:ok, decoded_cursor} ->
{int, _} = Integer.parse(String.slice(decoded_cursor, String.length(@prefix)..String.length(decoded_cursor)))
int
string = String.slice(decoded_cursor, String.length(@prefix)..String.length(decoded_cursor))
case Ecto.DateTime.cast(string) do
{:ok, date} -> date
:error -> string
end
:error ->
nil
end
end

def cursor_for_object_in_connection(object) do
Base.encode64("#{@prefix}#{object.id}")
def cursor_for_object_in_connection(object, property \\ :id) do
prop = case Map.get(object, property) do
%Ecto.DateTime{} = date_time -> Ecto.DateTime.to_iso8601(date_time)
prop -> to_string(prop)
end

Base.encode64("#{@prefix}#{prop}")
end

def connection_count(repo, query) do
Expand All @@ -157,6 +205,10 @@ if Code.ensure_loaded?(Ecto) do
repo.one(count_query)
end

defp get_ordered_by_direction(value) when value in [:asc, :desc] do
value
end

defp make_query_countable(query) do
query
|> remove_order
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule GraphQL.Relay.Mixfile do
use Mix.Project

@version "0.4.0"
@version "0.5.0"
@description "Relay helpers for GraphQL Elixir"
@repo_url "https://github.com/graphql-elixir/graphql_relay"

Expand Down
7 changes: 7 additions & 0 deletions priv/repo/migrations/20160307040347_create_letter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ defmodule EctoTest.Repo.Migrations.CreateLetter do
create table(:letters) do
add :letter, :string
add :second_column, :string
add :order, :integer
timestamps
end

create table(:numbers) do
add :number, :integer
add :letter_id, references(:letters, on_delete: :nothing)
timestamps
end
end
Expand Down
Loading