If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/rails_app/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 | require_relative 'boot'
3 |
4 | # require 'rails/all'
5 | require 'rails'
6 | require 'active_record/railtie'
7 | require 'action_controller/railtie'
8 | require 'action_view/railtie'
9 | require 'action_mailer/railtie'
10 | require 'active_job/railtie'
11 | #require 'action_cable/engine'
12 | require 'sprockets/railtie'
13 |
14 | # Require the gems listed in Gemfile, including any gems
15 | # you've limited to :test, :development, or :production.
16 | Bundler.require(*Rails.groups)
17 |
18 | module RailsApp
19 | class Application < Rails::Application
20 | # Settings in config/environments/* take precedence over those specified here.
21 | # Application configuration should go into files in config/initializers
22 | # -- all .rb files in that directory are automatically loaded.
23 |
24 | # Disable unwanted generators.
25 | config.generators do |generate|
26 | generate.javascripts true
27 | generate.stylesheets false
28 | generate.helper false
29 | generate.factory_girl true
30 | generate.routing_specs false
31 | generate.view_specs false
32 | generate.controller_specs false
33 | generate.request_specs false
34 | end
35 |
36 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
37 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
38 | # config.time_zone = 'Central Time (US & Canada)'
39 |
40 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
41 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
42 | # config.i18n.default_locale = :de
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/rails_app/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/rails_app/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20160627180727) do
14 |
15 | # These are extensions that must be enabled in order to support this database
16 | enable_extension "plpgsql"
17 |
18 | create_table "users", force: :cascade do |t|
19 | t.string "email", default: "", null: false
20 | t.string "encrypted_password", default: "", null: false
21 | t.string "reset_password_token"
22 | t.datetime "reset_password_sent_at"
23 | t.datetime "remember_created_at"
24 | t.integer "sign_in_count", default: 0, null: false
25 | t.datetime "current_sign_in_at"
26 | t.datetime "last_sign_in_at"
27 | t.string "current_sign_in_ip"
28 | t.string "last_sign_in_ip"
29 | t.datetime "created_at", null: false
30 | t.datetime "updated_at", null: false
31 | t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
32 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
33 | end
34 |
35 | end
36 |
--------------------------------------------------------------------------------
/rails_app/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/phoenix_app/brunch-config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | // See http://brunch.io/#documentation for docs.
3 | files: {
4 | javascripts: {
5 | joinTo: "js/app.js"
6 |
7 | // To use a separate vendor.js bundle, specify two files path
8 | // http://brunch.io/docs/config#-files-
9 | // joinTo: {
10 | // "js/app.js": /^(web\/static\/js)/,
11 | // "js/vendor.js": /^(web\/static\/vendor)|(deps)/
12 | // }
13 | //
14 | // To change the order of concatenation of files, explicitly mention here
15 | // order: {
16 | // before: [
17 | // "web/static/vendor/js/jquery-2.1.1.js",
18 | // "web/static/vendor/js/bootstrap.min.js"
19 | // ]
20 | // }
21 | },
22 | stylesheets: {
23 | joinTo: "css/app.css",
24 | order: {
25 | after: ["web/static/css/app.css"] // concat app.css last
26 | }
27 | },
28 | templates: {
29 | joinTo: "js/app.js"
30 | }
31 | },
32 |
33 | conventions: {
34 | // This option sets where we should place non-css and non-js assets in.
35 | // By default, we set this to "/web/static/assets". Files in this directory
36 | // will be copied to `paths.public`, which is "priv/static" by default.
37 | assets: /^(web\/static\/assets)/
38 | },
39 |
40 | // Phoenix paths configuration
41 | paths: {
42 | // Dependencies and current project directories to watch
43 | watched: [
44 | "web/static",
45 | "test/static"
46 | ],
47 |
48 | // Where to compile files to
49 | public: "priv/static"
50 | },
51 |
52 | // Configure your plugins
53 | plugins: {
54 | babel: {
55 | // Do not use ES6 compiler in vendor code
56 | ignore: [/web\/static\/vendor/]
57 | }
58 | },
59 |
60 | modules: {
61 | autoRequire: {
62 | "js/app.js": ["web/static/js/app"]
63 | }
64 | },
65 |
66 | npm: {
67 | enabled: true
68 | }
69 | };
70 |
--------------------------------------------------------------------------------
/phoenix_app/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule PhoenixApp.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :phoenix_app,
6 | version: "0.0.1",
7 | elixir: "~> 1.2",
8 | elixirc_paths: elixirc_paths(Mix.env),
9 | compilers: [:phoenix, :gettext] ++ Mix.compilers,
10 | build_embedded: Mix.env == :prod,
11 | start_permanent: Mix.env == :prod,
12 | aliases: aliases(),
13 | deps: deps()]
14 | end
15 |
16 | # Configuration for the OTP application.
17 | #
18 | # Type `mix help compile.app` for more information.
19 | def application do
20 | [mod: {PhoenixApp, []},
21 | applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
22 | :phoenix_ecto, :postgrex, :httpotion]]
23 | end
24 |
25 | # Specifies which paths to compile per environment.
26 | defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
27 | defp elixirc_paths(_), do: ["lib", "web"]
28 |
29 | # Specifies your project dependencies.
30 | #
31 | # Type `mix help deps` for examples and options.
32 | defp deps do
33 | [{:phoenix, "~> 1.2.0"},
34 | {:phoenix_pubsub, "~> 1.0"},
35 | {:phoenix_ecto, "~> 3.0"},
36 | {:postgrex, ">= 0.0.0"},
37 | {:phoenix_html, "~> 2.6"},
38 | {:phoenix_live_reload, "~> 1.0", only: :dev},
39 | {:gettext, "~> 0.11"},
40 | {:plug_rails_cookie_session_store, "~> 0.1"},
41 | {:httpotion, "~> 3.0.0"},
42 | {:cowboy, "~> 1.0"}]
43 | end
44 |
45 | # Aliases are shortcuts or tasks specific to the current project.
46 | # For example, to create, migrate and run the seeds file at once:
47 | #
48 | # $ mix ecto.setup
49 | #
50 | # See the documentation for `Mix` for more info on aliases.
51 | defp aliases do
52 | ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
53 | "ecto.reset": ["ecto.drop", "ecto.setup"],
54 | "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/rails_app/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => 'public, max-age=3600'
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
36 |
37 | config.action_mailer.default_url_options = { host: 'example.com' }
38 |
39 | # Print deprecation notices to the stderr.
40 | config.active_support.deprecation = :stderr
41 |
42 | # Raises error for missing translations
43 | # config.action_view.raise_on_missing_translations = true
44 | end
45 |
--------------------------------------------------------------------------------
/phoenix_app/web/web.ex:
--------------------------------------------------------------------------------
1 | defmodule PhoenixApp.Web do
2 | @moduledoc """
3 | A module that keeps using definitions for controllers,
4 | views and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use PhoenixApp.Web, :controller
9 | use PhoenixApp.Web, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below.
17 | """
18 |
19 | def model do
20 | quote do
21 | use Ecto.Schema
22 |
23 | import Ecto
24 | import Ecto.Changeset
25 | import Ecto.Query
26 | end
27 | end
28 |
29 | def controller do
30 | quote do
31 | use Phoenix.Controller
32 |
33 | alias PhoenixApp.Repo
34 | import Ecto
35 | import Ecto.Query
36 |
37 | import PhoenixApp.Router.Helpers
38 | import PhoenixApp.Gettext
39 | end
40 | end
41 |
42 | def view do
43 | quote do
44 | use Phoenix.View, root: "web/templates"
45 |
46 | # Import convenience functions from controllers
47 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
48 |
49 | # Use all HTML functionality (forms, tags, etc)
50 | use Phoenix.HTML
51 |
52 | import PhoenixApp.Router.Helpers
53 | import PhoenixApp.ErrorHelpers
54 | import PhoenixApp.Gettext
55 | import PhoenixApp.Session
56 | end
57 | end
58 |
59 | def router do
60 | quote do
61 | use Phoenix.Router
62 | end
63 | end
64 |
65 | def channel do
66 | quote do
67 | use Phoenix.Channel
68 |
69 | alias PhoenixApp.Repo
70 | import Ecto
71 | import Ecto.Query
72 | import PhoenixApp.Gettext
73 | end
74 | end
75 |
76 | @doc """
77 | When used, dispatch to the appropriate controller/view/etc.
78 | """
79 | defmacro __using__(which) when is_atom(which) do
80 | apply(__MODULE__, which, [])
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/phoenix_app/lib/phoenix_app/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule PhoenixApp.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :phoenix_app
3 |
4 | socket "/socket", PhoenixApp.UserSocket
5 |
6 | # Serve at "/" the static files from "priv/static" directory.
7 | #
8 | # You should set gzip to true if you are running phoenix.digest
9 | # when deploying your static files in production.
10 | plug Plug.Static,
11 | at: "/", from: :phoenix_app, gzip: false,
12 | only: ~w(css fonts images js favicon.ico robots.txt)
13 |
14 | # Code reloading can be explicitly enabled under the
15 | # :code_reloader configuration of your endpoint.
16 | if code_reloading? do
17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
18 | plug Phoenix.LiveReloader
19 | plug Phoenix.CodeReloader
20 | end
21 |
22 | plug Plug.RequestId
23 | plug Plug.Logger
24 |
25 | plug Plug.Parsers,
26 | parsers: [:urlencoded, :multipart, :json],
27 | pass: ["*/*"],
28 | json_decoder: Poison
29 |
30 | plug Plug.MethodOverride
31 | plug Plug.Head
32 |
33 | # The session will be stored in the cookie and signed,
34 | # this means its contents can be read but not tampered with.
35 | # Set :encryption_salt if you would also like to encrypt it.
36 | plug Plug.Session,
37 | # Remove the original cookie store that comes with Phoenix, out of the box.
38 | # store: :cookie,
39 | # key: "_phoenix_app_key",
40 | # signing_salt: "M8emDP0h"
41 | store: PlugRailsCookieSessionStore,
42 | # Decide on a shared key for your cookie. Oftentimes, this should
43 | # mirror your Rails app session key
44 | key: "_rails_app_session",
45 | secure: true,
46 | encrypt: true,
47 | domain: ".#{System.get_env("DOMAIN")}",
48 | signing_salt: System.get_env("SESSION_ENCRYPTED_SIGNED_COOKIE_SALT"),
49 | encryption_salt: System.get_env("SESSION_ENCRYPTED_COOKIE_SALT"),
50 | key_iterations: 1000,
51 | key_length: 64,
52 | key_digest: :sha,
53 | serializer: Poison
54 |
55 | plug PhoenixApp.Router
56 | end
57 |
--------------------------------------------------------------------------------
/rails_app/lib/templates/rails/scaffold_controller/controller.rb:
--------------------------------------------------------------------------------
1 | <% if namespaced? -%>
2 | require_dependency "<%= namespaced_file_path %>/application_controller"
3 |
4 | <% end -%>
5 | <% module_namespacing do -%>
6 | class <%= controller_class_name %>Controller < ApplicationController
7 | before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy]
8 |
9 | def index
10 | @<%= plural_table_name %> = <%= orm_class.all(class_name) %>
11 | end
12 |
13 | def show
14 | end
15 |
16 | def new
17 | @<%= singular_table_name %> = <%= orm_class.build(class_name) %>
18 | end
19 |
20 | def edit
21 | end
22 |
23 | def create
24 | @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
25 |
26 | if @<%= orm_instance.save %>
27 | redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %>
28 | else
29 | render :new
30 | end
31 | end
32 |
33 | def update
34 | if @<%= orm_instance.update("#{singular_table_name}_params") %>
35 | redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %>
36 | else
37 | render :edit
38 | end
39 | end
40 |
41 | def destroy
42 | @<%= orm_instance.destroy %>
43 | redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %>
44 | end
45 |
46 | private
47 |
48 | # Use callbacks to share common setup or constraints between actions.
49 | def set_<%= singular_table_name %>
50 | @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
51 | end
52 |
53 | # Only allow a trusted parameter "white list" through.
54 | def <%= "#{singular_table_name}_params" %>
55 | <%- if attributes_names.empty? -%>
56 | params.fetch(:<%= singular_table_name %>, {})
57 | <%- else -%>
58 | params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
59 | <%- end -%>
60 | end
61 | end
62 | <% end -%>
63 |
--------------------------------------------------------------------------------
/phoenix_app/test/support/model_case.ex:
--------------------------------------------------------------------------------
1 | defmodule PhoenixApp.ModelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | model tests.
5 |
6 | You may define functions here to be used as helpers in
7 | your model tests. See `errors_on/2`'s definition as reference.
8 |
9 | Finally, if the test case interacts with the database,
10 | it cannot be async. For this reason, every test runs
11 | inside a transaction which is reset at the beginning
12 | of the test unless the test case is marked as async.
13 | """
14 |
15 | use ExUnit.CaseTemplate
16 |
17 | using do
18 | quote do
19 | alias PhoenixApp.Repo
20 |
21 | import Ecto
22 | import Ecto.Changeset
23 | import Ecto.Query
24 | import PhoenixApp.ModelCase
25 | end
26 | end
27 |
28 | setup tags do
29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(PhoenixApp.Repo)
30 |
31 | unless tags[:async] do
32 | Ecto.Adapters.SQL.Sandbox.mode(PhoenixApp.Repo, {:shared, self()})
33 | end
34 |
35 | :ok
36 | end
37 |
38 | @doc """
39 | Helper for returning list of errors in a struct when given certain data.
40 |
41 | ## Examples
42 |
43 | Given a User schema that lists `:name` as a required field and validates
44 | `:password` to be safe, it would return:
45 |
46 | iex> errors_on(%User{}, %{password: "password"})
47 | [password: "is unsafe", name: "is blank"]
48 |
49 | You could then write your assertion like:
50 |
51 | assert {:password, "is unsafe"} in errors_on(%User{}, %{password: "password"})
52 |
53 | You can also create the changeset manually and retrieve the errors
54 | field directly:
55 |
56 | iex> changeset = User.changeset(%User{}, password: "password")
57 | iex> {:password, "is unsafe"} in changeset.errors
58 | true
59 | """
60 | def errors_on(struct, data) do
61 | struct.__struct__.changeset(struct, data)
62 | |> Ecto.Changeset.traverse_errors(&PhoenixApp.ErrorHelpers.translate_error/1)
63 | |> Enum.flat_map(fn {key, errors} -> for msg <- errors, do: {key, msg} end)
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/rails_app/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | if Rails.root.join('tmp/caching-dev.txt').exist?
17 | config.action_controller.perform_caching = true
18 |
19 | config.cache_store = :memory_store
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => 'public, max-age=172800'
22 | }
23 | else
24 | config.action_controller.perform_caching = false
25 |
26 | config.cache_store = :null_store
27 | end
28 |
29 | # Don't care if the mailer can't send.
30 | config.action_mailer.raise_delivery_errors = false
31 |
32 | config.action_mailer.perform_caching = false
33 |
34 | # Print deprecation notices to the Rails logger.
35 | config.active_support.deprecation = :log
36 |
37 | # Raise an error on page load if there are pending migrations.
38 | config.active_record.migration_error = :page_load
39 |
40 | # Debug mode disables concatenation and preprocessing of assets.
41 | # This option may cause significant delays in view rendering with a large
42 | # number of complex assets.
43 | config.assets.debug = true
44 |
45 | # Suppress logger output for asset requests.
46 | config.assets.quiet = true
47 |
48 | # Raises error for missing translations
49 | # config.action_view.raise_on_missing_translations = true
50 |
51 | config.action_mailer.default_url_options = { host: 'localhost:3000' }
52 |
53 | # Use an evented file watcher to asynchronously detect changes in source code,
54 | # routes, locales, etc. This feature depends on the listen gem.
55 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
56 | end
57 |
--------------------------------------------------------------------------------
/rails_app/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum, this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch('PUMA_THREADS') { 5 }.to_i
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000.
11 | #
12 | port ENV.fetch('PORT') { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch('RAILS_ENV') { 'development' }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | workers_count = ENV.fetch('PUMA_WORKERS') { 1 }.to_i
25 |
26 | if workers_count > 1
27 | workers workers_count
28 |
29 | # Use the `preload_app!` method when specifying a `workers` number.
30 | # This directive tells Puma to first boot the application and load code
31 | # before forking the application. This takes advantage of Copy On Write
32 | # process behavior so workers use less memory. If you use this option
33 | # you need to make sure to reconnect any threads in the `on_worker_boot`
34 | # block.
35 | #
36 | preload_app!
37 |
38 | # The code in the `on_worker_boot` will be called if you are using
39 | # clustered mode by specifying a number of `workers`. After each worker
40 | # process is booted this block will be run, if you are using `preload_app!`
41 | # option you will want to use this block to reconnect to any threads
42 | # or connections that may have been created at application boot, Ruby
43 | # cannot share connections between processes.
44 | #
45 | on_worker_boot do
46 | ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
47 | end
48 | end
49 |
50 | # Allow puma to be restarted by `rails restart` command.
51 | plugin :tmp_restart
52 |
--------------------------------------------------------------------------------
/phoenix_app/web/static/js/socket.js:
--------------------------------------------------------------------------------
1 | // NOTE: The contents of this file will only be executed if
2 | // you uncomment its entry in "web/static/js/app.js".
3 |
4 | // To use Phoenix channels, the first step is to import Socket
5 | // and connect at the socket path in "lib/my_app/endpoint.ex":
6 | import {Socket} from "phoenix"
7 |
8 | let socket = new Socket("/socket", {params: {token: window.userToken}})
9 |
10 | // When you connect, you'll often need to authenticate the client.
11 | // For example, imagine you have an authentication plug, `MyAuth`,
12 | // which authenticates the session and assigns a `:current_user`.
13 | // If the current user exists you can assign the user's token in
14 | // the connection for use in the layout.
15 | //
16 | // In your "web/router.ex":
17 | //
18 | // pipeline :browser do
19 | // ...
20 | // plug MyAuth
21 | // plug :put_user_token
22 | // end
23 | //
24 | // defp put_user_token(conn, _) do
25 | // if current_user = conn.assigns[:current_user] do
26 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id)
27 | // assign(conn, :user_token, token)
28 | // else
29 | // conn
30 | // end
31 | // end
32 | //
33 | // Now you need to pass this token to JavaScript. You can do so
34 | // inside a script tag in "web/templates/layout/app.html.eex":
35 | //
36 | //
37 | //
38 | // You will need to verify the user token in the "connect/2" function
39 | // in "web/channels/user_socket.ex":
40 | //
41 | // def connect(%{"token" => token}, socket) do
42 | // # max_age: 1209600 is equivalent to two weeks in seconds
43 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
44 | // {:ok, user_id} ->
45 | // {:ok, assign(socket, :user, user_id)}
46 | // {:error, reason} ->
47 | // :error
48 | // end
49 | // end
50 | //
51 | // Finally, pass the token on connect as below. Or remove it
52 | // from connect if you don't care about authentication.
53 |
54 | socket.connect()
55 |
56 | // Now that you are connected, you can join channels with a topic:
57 | let channel = socket.channel("topic:subtopic", {})
58 | channel.join()
59 | .receive("ok", resp => { console.log("Joined successfully", resp) })
60 | .receive("error", resp => { console.log("Unable to join", resp) })
61 |
62 | export default socket
63 |
--------------------------------------------------------------------------------
/phoenix_app/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we configure the host to read the PORT
4 | # from the system environment. Therefore, you will need
5 | # to set PORT=80 before running your server.
6 | #
7 | # You should also configure the url host to something
8 | # meaningful, we use this information when generating URLs.
9 | #
10 | # Finally, we also include the path to a manifest
11 | # containing the digested version of static files. This
12 | # manifest is generated by the mix phoenix.digest task
13 | # which you typically run after static files are built.
14 | config :phoenix_app, PhoenixApp.Endpoint,
15 | http: [port: {:system, "PORT"}],
16 | url: [host: "example.com", port: 80],
17 | cache_static_manifest: "priv/static/manifest.json"
18 |
19 | # Do not print debug messages in production
20 | config :logger, level: :info
21 |
22 | # ## SSL Support
23 | #
24 | # To get SSL working, you will need to add the `https` key
25 | # to the previous section and set your `:url` port to 443:
26 | #
27 | # config :phoenix_app, PhoenixApp.Endpoint,
28 | # ...
29 | # url: [host: "example.com", port: 443],
30 | # https: [port: 443,
31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
33 | #
34 | # Where those two env variables return an absolute path to
35 | # the key and cert in disk or a relative path inside priv,
36 | # for example "priv/ssl/server.key".
37 | #
38 | # We also recommend setting `force_ssl`, ensuring no data is
39 | # ever sent via http, always redirecting to https:
40 | #
41 | # config :phoenix_app, PhoenixApp.Endpoint,
42 | # force_ssl: [hsts: true]
43 | #
44 | # Check `Plug.SSL` for all available options in `force_ssl`.
45 |
46 | # ## Using releases
47 | #
48 | # If you are doing OTP releases, you need to instruct Phoenix
49 | # to start the server for all endpoints:
50 | #
51 | # config :phoenix, :serve_endpoints, true
52 | #
53 | # Alternatively, you can configure exactly which server to
54 | # start per endpoint:
55 | #
56 | # config :phoenix_app, PhoenixApp.Endpoint, server: true
57 | #
58 | # You will also need to set the application root to `.` in order
59 | # for the new static assets to be served after a hot upgrade:
60 | #
61 | # config :phoenix_app, PhoenixApp.Endpoint, root: "."
62 |
63 | # Finally import the config/prod.secret.exs
64 | # which should be versioned separately.
65 | import_config "prod.secret.exs"
66 |
--------------------------------------------------------------------------------
/rails_app/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV['RAILS_ENV'] ||= 'test'
3 | require File.expand_path('../../config/environment', __FILE__)
4 |
5 | # Prevent database truncation if the environment is production
6 | abort("The Rails environment is running in production mode!") if Rails.env.production?
7 |
8 | require 'spec_helper'
9 | require 'rspec/rails'
10 | # Add additional requires below this line. Rails is not loaded until this point!
11 |
12 | # Requires supporting ruby files with custom matchers and macros, etc, in
13 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
14 | # run as spec files by default. This means that files in spec/support that end
15 | # in _spec.rb will both be required and run as specs, causing the specs to be
16 | # run twice. It is recommended that you do not name files matching this glob to
17 | # end with _spec.rb. You can configure this pattern with the --pattern
18 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
19 |
20 | Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f }
21 |
22 | # Checks for pending migrations before tests are run.
23 | # If you are not using ActiveRecord, you can remove this line.
24 | ActiveRecord::Migration.maintain_test_schema!
25 |
26 | RSpec.configure do |config|
27 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
28 | #config.fixture_path = "#{::Rails.root}/spec/fixtures"
29 |
30 | # See database_cleaner.rb for database cleanup details.
31 | config.use_transactional_fixtures = false
32 |
33 | # RSpec Rails can automatically mix in different behaviours to your tests
34 | # based on their file location, for example enabling you to call `get` and
35 | # `post` in specs under `spec/controllers`.
36 | #
37 | # You can disable this behaviour by removing the line below, and instead
38 | # explicitly tag your specs with their type, e.g.:
39 | #
40 | # RSpec.describe UsersController, :type => :controller do
41 | # # ...
42 | # end
43 | #
44 | # The different available types are documented in the features, such as in
45 | # https://relishapp.com/rspec/rspec-rails/docs
46 | config.infer_spec_type_from_file_location!
47 |
48 | # Filter lines from Rails gems in backtraces.
49 | config.filter_rails_from_backtrace!
50 | # arbitrary gems may also be filtered via:
51 | # config.filter_gems_from_backtrace("gem name")
52 | end
53 |
--------------------------------------------------------------------------------
/phoenix_app/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
13 | ## From Ecto.Changeset.cast/4
14 | msgid "can't be blank"
15 | msgstr ""
16 |
17 | ## From Ecto.Changeset.unique_constraint/3
18 | msgid "has already been taken"
19 | msgstr ""
20 |
21 | ## From Ecto.Changeset.put_change/3
22 | msgid "is invalid"
23 | msgstr ""
24 |
25 | ## From Ecto.Changeset.validate_format/3
26 | msgid "has invalid format"
27 | msgstr ""
28 |
29 | ## From Ecto.Changeset.validate_subset/3
30 | msgid "has an invalid entry"
31 | msgstr ""
32 |
33 | ## From Ecto.Changeset.validate_exclusion/3
34 | msgid "is reserved"
35 | msgstr ""
36 |
37 | ## From Ecto.Changeset.validate_confirmation/3
38 | msgid "does not match confirmation"
39 | msgstr ""
40 |
41 | ## From Ecto.Changeset.no_assoc_constraint/3
42 | msgid "is still associated to this entry"
43 | msgstr ""
44 |
45 | msgid "are still associated to this entry"
46 | msgstr ""
47 |
48 | ## From Ecto.Changeset.validate_length/3
49 | msgid "should be %{count} character(s)"
50 | msgid_plural "should be %{count} character(s)"
51 | msgstr[0] ""
52 | msgstr[1] ""
53 |
54 | msgid "should have %{count} item(s)"
55 | msgid_plural "should have %{count} item(s)"
56 | msgstr[0] ""
57 | msgstr[1] ""
58 |
59 | msgid "should be at least %{count} character(s)"
60 | msgid_plural "should be at least %{count} character(s)"
61 | msgstr[0] ""
62 | msgstr[1] ""
63 |
64 | msgid "should have at least %{count} item(s)"
65 | msgid_plural "should have at least %{count} item(s)"
66 | msgstr[0] ""
67 | msgstr[1] ""
68 |
69 | msgid "should be at most %{count} character(s)"
70 | msgid_plural "should be at most %{count} character(s)"
71 | msgstr[0] ""
72 | msgstr[1] ""
73 |
74 | msgid "should have at most %{count} item(s)"
75 | msgid_plural "should have at most %{count} item(s)"
76 | msgstr[0] ""
77 | msgstr[1] ""
78 |
79 | ## From Ecto.Changeset.validate_number/3
80 | msgid "must be less than %{number}"
81 | msgstr ""
82 |
83 | msgid "must be greater than %{number}"
84 | msgstr ""
85 |
86 | msgid "must be less than or equal to %{number}"
87 | msgstr ""
88 |
89 | msgid "must be greater than or equal to %{number}"
90 | msgstr ""
91 |
92 | msgid "must be equal to %{number}"
93 | msgstr ""
94 |
--------------------------------------------------------------------------------
/phoenix_app/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This file is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here as no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 | ## From Ecto.Changeset.cast/4
12 | msgid "can't be blank"
13 | msgstr ""
14 |
15 | ## From Ecto.Changeset.unique_constraint/3
16 | msgid "has already been taken"
17 | msgstr ""
18 |
19 | ## From Ecto.Changeset.put_change/3
20 | msgid "is invalid"
21 | msgstr ""
22 |
23 | ## From Ecto.Changeset.validate_format/3
24 | msgid "has invalid format"
25 | msgstr ""
26 |
27 | ## From Ecto.Changeset.validate_subset/3
28 | msgid "has an invalid entry"
29 | msgstr ""
30 |
31 | ## From Ecto.Changeset.validate_exclusion/3
32 | msgid "is reserved"
33 | msgstr ""
34 |
35 | ## From Ecto.Changeset.validate_confirmation/3
36 | msgid "does not match confirmation"
37 | msgstr ""
38 |
39 | ## From Ecto.Changeset.no_assoc_constraint/3
40 | msgid "is still associated to this entry"
41 | msgstr ""
42 |
43 | msgid "are still associated to this entry"
44 | msgstr ""
45 |
46 | ## From Ecto.Changeset.validate_length/3
47 | msgid "should be %{count} character(s)"
48 | msgid_plural "should be %{count} character(s)"
49 | msgstr[0] ""
50 | msgstr[1] ""
51 |
52 | msgid "should have %{count} item(s)"
53 | msgid_plural "should have %{count} item(s)"
54 | msgstr[0] ""
55 | msgstr[1] ""
56 |
57 | msgid "should be at least %{count} character(s)"
58 | msgid_plural "should be at least %{count} character(s)"
59 | msgstr[0] ""
60 | msgstr[1] ""
61 |
62 | msgid "should have at least %{count} item(s)"
63 | msgid_plural "should have at least %{count} item(s)"
64 | msgstr[0] ""
65 | msgstr[1] ""
66 |
67 | msgid "should be at most %{count} character(s)"
68 | msgid_plural "should be at most %{count} character(s)"
69 | msgstr[0] ""
70 | msgstr[1] ""
71 |
72 | msgid "should have at most %{count} item(s)"
73 | msgid_plural "should have at most %{count} item(s)"
74 | msgstr[0] ""
75 | msgstr[1] ""
76 |
77 | ## From Ecto.Changeset.validate_number/3
78 | msgid "must be less than %{number}"
79 | msgstr ""
80 |
81 | msgid "must be greater than %{number}"
82 | msgstr ""
83 |
84 | msgid "must be less than or equal to %{number}"
85 | msgstr ""
86 |
87 | msgid "must be greater than or equal to %{number}"
88 | msgstr ""
89 |
90 | msgid "must be equal to %{number}"
91 | msgstr ""
92 |
--------------------------------------------------------------------------------
/rails_app/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Disable serving static files from the `/public` folder by default since
18 | # Apache or NGINX already handles this.
19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
20 |
21 | # Compress JavaScripts and CSS.
22 | config.assets.js_compressor = :uglifier
23 | # config.assets.css_compressor = :sass
24 |
25 | # Do not fallback to assets pipeline if a precompiled asset is missed.
26 | config.assets.compile = false
27 |
28 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
29 |
30 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
31 | # config.action_controller.asset_host = 'http://assets.example.com'
32 |
33 | # Specifies the header that your server uses for sending files.
34 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
35 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
36 |
37 | # Mount Action Cable outside main process or domain
38 | # config.action_cable.mount_path = nil
39 | # config.action_cable.url = 'wss://example.com/cable'
40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
41 |
42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
43 | # config.force_ssl = true
44 |
45 | # Use the lowest log level to ensure availability of diagnostic information
46 | # when problems arise.
47 | config.log_level = :debug
48 |
49 | # Prepend all log lines with the following tags.
50 | config.log_tags = [ :request_id ]
51 |
52 | # Use a different cache store in production.
53 | # config.cache_store = :mem_cache_store
54 |
55 | # Use a real queuing backend for Active Job (and separate queues per environment)
56 | # config.active_job.queue_adapter = :resque
57 | # config.active_job.queue_name_prefix = "rails_app_#{Rails.env}"
58 | config.action_mailer.perform_caching = false
59 |
60 | # Ignore bad email addresses and do not raise email delivery errors.
61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
62 | # config.action_mailer.raise_delivery_errors = false
63 |
64 | config.action_mailer.default_url_options = { host: 'rails-app.herokuapp.com' }
65 |
66 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
67 | # the I18n.default_locale when a translation cannot be found).
68 | config.i18n.fallbacks = true
69 |
70 | # Send deprecation notices to registered listeners.
71 | config.active_support.deprecation = :notify
72 |
73 | # Use default logging formatter so that PID and timestamp are not suppressed.
74 | config.log_formatter = ::Logger::Formatter.new
75 |
76 | # Use a different logger for distributed setups.
77 | # require 'syslog/logger'
78 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
79 |
80 | if ENV["RAILS_LOG_TO_STDOUT"].present?
81 | logger = ActiveSupport::Logger.new(STDOUT)
82 | logger.formatter = config.log_formatter
83 | config.logger = ActiveSupport::TaggedLogging.new(logger)
84 | end
85 |
86 | # Do not dump schema after migrations.
87 | config.active_record.dump_schema_after_migration = false
88 | end
89 |
--------------------------------------------------------------------------------
/phoenix_app/mix.lock:
--------------------------------------------------------------------------------
1 | %{"connection": {:hex, :connection, "1.0.3", "3145f7416be3df248a4935f24e3221dc467c1e3a158d62015b35bd54da365786", [:mix], []},
2 | "cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:rebar, :make], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
3 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
4 | "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]}]},
5 | "decimal": {:hex, :decimal, "1.1.2", "79a769d4657b2d537b51ef3c02d29ab7141d2b486b516c109642d453ee08e00c", [:mix], []},
6 | "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]}]},
7 | "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []},
8 | "gettext": {:hex, :gettext, "0.11.0", "80c1dd42d270482418fa158ec5ba073d2980e3718bacad86f3d4ad71d5667679", [:mix], []},
9 | "httpotion": {:hex, :httpotion, "3.0.0", "4ce0af28f4254bdd0457d0fe065a83787751e0a5b5f5d27030948253be38df5c", [:mix], [{:ibrowse, "~> 4.2", [hex: :ibrowse, optional: false]}]},
10 | "ibrowse": {:hex, :ibrowse, "4.2.2", "b32b5bafcc77b7277eff030ed32e1acc3f610c64e9f6aea19822abcadf681b4b", [:rebar3], []},
11 | "phoenix": {:hex, :phoenix, "1.2.0", "1bdeb99c254f4c534cdf98fd201dede682297ccc62fcac5d57a2627c3b6681fb", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}]},
12 | "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]}]},
13 | "phoenix_html": {:hex, :phoenix_html, "2.6.0", "b9f7e091eb3d908586d9634596478fb9e577ee033d76f4ff327a745569bdd2d8", [:mix], [{:plug, "~> 0.13 or ~> 1.0", [hex: :plug, optional: false]}]},
14 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.5", "829218c4152ba1e9848e2bf8e161fcde6b4ec679a516259442561d21fde68d0b", [:mix], [{:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}, {:fs, "~> 0.9.1", [hex: :fs, optional: false]}]},
15 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.0", "c31af4be22afeeebfaf246592778c8c840e5a1ddc7ca87610c41ccfb160c2c57", [:mix], []},
16 | "plug": {:hex, :plug, "1.1.6", "8927e4028433fcb859e000b9389ee9c37c80eb28378eeeea31b0273350bf668b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}]},
17 | "plug_rails_cookie_session_store": {:hex, :plug_rails_cookie_session_store, "0.1.0", "8d87967eb2d4d25837e1b5778265aebf8ac797291d6ff65dbd828c4ffa7f0955", [:mix], [{:plug, ">= 0.9.0", [hex: :plug, optional: false]}, {:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}]},
18 | "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
19 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []},
20 | "postgrex": {:hex, :postgrex, "0.11.2", "139755c1359d3c5c6d6e8b1ea72556d39e2746f61c6ddfb442813c91f53487e8", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.0-rc", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]},
21 | "ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []}}
22 |
--------------------------------------------------------------------------------
/rails_app/config/locales/devise.en.yml:
--------------------------------------------------------------------------------
1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2 |
3 | en:
4 | devise:
5 | confirmations:
6 | confirmed: "Your email address has been successfully confirmed."
7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
9 | failure:
10 | already_authenticated: "You are already signed in."
11 | inactive: "Your account is not activated yet."
12 | invalid: "Invalid %{authentication_keys} or password."
13 | locked: "Your account is locked."
14 | last_attempt: "You have one more attempt before your account is locked."
15 | not_found_in_database: "Invalid %{authentication_keys} or password."
16 | timeout: "Your session expired. Please sign in again to continue."
17 | unauthenticated: "You need to sign in or sign up before continuing."
18 | unconfirmed: "You have to confirm your email address before continuing."
19 | mailer:
20 | confirmation_instructions:
21 | subject: "Confirmation instructions"
22 | reset_password_instructions:
23 | subject: "Reset password instructions"
24 | unlock_instructions:
25 | subject: "Unlock instructions"
26 | password_change:
27 | subject: "Password Changed"
28 | omniauth_callbacks:
29 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
30 | success: "Successfully authenticated from %{kind} account."
31 | passwords:
32 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
33 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
34 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
35 | updated: "Your password has been changed successfully. You are now signed in."
36 | updated_not_active: "Your password has been changed successfully."
37 | registrations:
38 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
39 | signed_up: "Welcome! You have signed up successfully."
40 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
41 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
42 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
43 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
44 | updated: "Your account has been updated successfully."
45 | sessions:
46 | signed_in: "Signed in successfully."
47 | signed_out: "Signed out successfully."
48 | already_signed_out: "Signed out successfully."
49 | unlocks:
50 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
51 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
52 | unlocked: "Your account has been unlocked successfully. Please sign in to continue."
53 | errors:
54 | messages:
55 | already_confirmed: "was already confirmed, please try signing in"
56 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
57 | expired: "has expired, please request a new one"
58 | not_found: "not found"
59 | not_locked: "was not locked"
60 | not_saved:
61 | one: "1 error prohibited this %{resource} from being saved:"
62 | other: "%{count} errors prohibited this %{resource} from being saved:"
63 |
--------------------------------------------------------------------------------
/rails_app/README.md:
--------------------------------------------------------------------------------
1 | # Rails App
2 |
3 | ...
4 |
5 | Generated with [Raygun](https://github.com/carbonfive/raygun).
6 |
7 | # Development
8 |
9 | ## Getting Started
10 |
11 | ### Requirements
12 |
13 | To run the specs or fire up the server, be sure you have these installed (and running):
14 |
15 | * Ruby 2.3 (see [.ruby-version](.ruby-version)).
16 | * PostgreSQL 9.x (`brew install postgresql`) with superuser 'postgres' with no password (`createuser -s postgres`).
17 | * PhantomJS 2.x for Capybara and Javascript testing (`brew install phantomjs`).
18 |
19 | ### First Time Setup
20 |
21 | After cloning, run [./bin/setup](bin/setup) to install missing gems and prepare the database.
22 |
23 | Note, `rake db:sample_data` (run as part of setup) loads a small set of data for development. Check out
24 | [db/sample_data.rb](db/sample_data.rb) for details.
25 |
26 | ### Running the Specs
27 |
28 | To run all Ruby and Javascript specs.
29 |
30 | $ ./bin/rake
31 |
32 | Note: `./bin/rake` runs the springified version of rake (there's a `./bin/rspec` and `./bin/rails` too). You can add
33 | `./bin` to your PATH too, then you'll always use the springified bins when they exist. See
34 | [rails/spring](https://github.com/rails/spring) for additional information.
35 |
36 | ### Running the Application Locally
37 |
38 | $ foreman start
39 | $ open http://localhost:3000
40 |
41 | ## Conventions
42 |
43 | ### Git
44 |
45 | * Branch `development` is auto-deployed to acceptance.
46 | * Branch `master` is auto-deployed to production.
47 | * Create feature branches off of `development` using the naming convention
48 | `(features|chores|bugs)/a-brief-description-######`, where ###### is the tracker id.
49 | * Rebase your branch before merging into `development` to produce clean merge bubbles.
50 | * Retain merge commits for multi-commit branches when merging into `development` (e.g. `git merge --no-ff branchname`).
51 | * Craft atomic commits that make sense on their own and can be easily cherry-picked or reverted if necessary.
52 |
53 | ### Code Style
54 |
55 | Generally speaking, follow the [Ruby Style Guide](https://github.com/bbatsov/ruby-style-guide). Additionally, these are
56 | other guidelines adopted by the team:
57 |
58 | **Always use double quotes for test/spec descriptions, unless the subject is a class/module.**
59 |
60 | ```ruby
61 | describe SomeController do
62 | context "when logged in as an admin" do
63 | describe "#some_method" do
64 | it "does some thing"
65 | end
66 | end
67 | end
68 | ```
69 |
70 | ## Additional/Optional Development Details
71 |
72 | ### Code Coverage (local)
73 |
74 | Coverage for the ruby specs:
75 |
76 | $ COVERAGE=true rspec
77 |
78 | Code coverage is reported to Code Climate on every CI build so there's a record of trending.
79 |
80 | ### Using Guard
81 |
82 | Guard is configured to run ruby and jasmine specs, and also listen for livereload connections.
83 |
84 | $ bundle exec guard
85 |
86 | ### Using Mailcatcher
87 |
88 | $ gem install mailcatcher
89 | $ mailcatcher
90 | $ open http://localhost:1080/
91 |
92 | Learn more at [mailcatcher.me](http://mailcatcher.me/). And please don't add mailcatcher to the Gemfile.
93 |
94 | ### Continuous Integration and Deployment with CircleCI
95 |
96 | This project is configured for continuous integration and deployment with CircleCI and Heroku.
97 |
98 | Check out [circle.yml](circle.yml) and [bin/deploy.sh](bin/deploy.sh) for details.
99 |
100 | # Server Environments
101 |
102 | ### Hosting
103 |
104 | Acceptance and Production are hosted on Heroku under the _email@example.com_ account.
105 |
106 | ### Environment Variables
107 |
108 | Several common features and operational parameters can be set using environment variables.
109 |
110 | **Required**
111 |
112 | * `SECRET_KEY_BASE` - Secret key base for verfying signed cookies. Should be 30+ random characters and secret!
113 |
114 | **Optional**
115 |
116 | * `HOSTNAME` - Canonical hostname for this application. Other incoming requests will be redirected to this hostname.
117 | * `BASIC_AUTH_PASSWORD` - Enable basic auth with this password.
118 | * `BASIC_AUTH_USER` - Set a basic auth username (not required, password enables basic auth).
119 | * `PORT` - Port to listen on (default: 3000).
120 | * `PUMA_WORKERS` - Number of puma workers to spawn (default: 1).
121 | * `PUMA_THREADS` - Threads per worker (default: 5).
122 | * `DB_POOL` - Number of DB connections per pool (i.e. per worker) (default: PUMA_THREADS or 5).
123 | * `RAILS_LOG_TO_STDOUT` - Log to standard out (default: false).
124 |
125 | ### Third Party Services
126 |
127 | * Heroku for hosting.
128 | * CircleCI for continuous integration and deployment.
129 |
--------------------------------------------------------------------------------
/rails_app/config/initializers/simple_form_bootstrap.rb:
--------------------------------------------------------------------------------
1 | # Use this setup block to configure all options available in SimpleForm.
2 | SimpleForm.setup do |config|
3 | config.error_notification_class = 'alert alert-danger'
4 | config.button_class = 'btn btn-default'
5 | config.boolean_label_class = nil
6 |
7 | config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
8 | b.use :html5
9 | b.use :placeholder
10 | b.optional :maxlength
11 | b.optional :pattern
12 | b.optional :min_max
13 | b.optional :readonly
14 | b.use :label, class: 'control-label'
15 |
16 | b.use :input, class: 'form-control'
17 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' }
18 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
19 | end
20 |
21 | config.wrappers :vertical_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
22 | b.use :html5
23 | b.use :placeholder
24 | b.optional :maxlength
25 | b.optional :readonly
26 | b.use :label, class: 'control-label'
27 |
28 | b.use :input
29 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' }
30 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
31 | end
32 |
33 | config.wrappers :vertical_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
34 | b.use :html5
35 | b.optional :readonly
36 |
37 | b.wrapper tag: 'div', class: 'checkbox' do |ba|
38 | ba.use :label_input
39 | end
40 |
41 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' }
42 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
43 | end
44 |
45 | config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
46 | b.use :html5
47 | b.optional :readonly
48 | b.use :label, class: 'control-label'
49 | b.use :input
50 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' }
51 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
52 | end
53 |
54 | config.wrappers :horizontal_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
55 | b.use :html5
56 | b.use :placeholder
57 | b.optional :maxlength
58 | b.optional :pattern
59 | b.optional :min_max
60 | b.optional :readonly
61 | b.use :label, class: 'col-sm-3 control-label'
62 |
63 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
64 | ba.use :input, class: 'form-control'
65 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
66 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
67 | end
68 | end
69 |
70 | config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
71 | b.use :html5
72 | b.use :placeholder
73 | b.optional :maxlength
74 | b.optional :readonly
75 | b.use :label, class: 'col-sm-3 control-label'
76 |
77 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
78 | ba.use :input
79 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
80 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
81 | end
82 | end
83 |
84 | config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
85 | b.use :html5
86 | b.optional :readonly
87 |
88 | b.wrapper tag: 'div', class: 'col-sm-offset-3 col-sm-9' do |wr|
89 | wr.wrapper tag: 'div', class: 'checkbox' do |ba|
90 | ba.use :label_input
91 | end
92 |
93 | wr.use :error, wrap_with: { tag: 'span', class: 'help-block' }
94 | wr.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
95 | end
96 | end
97 |
98 | config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
99 | b.use :html5
100 | b.optional :readonly
101 |
102 | b.use :label, class: 'col-sm-3 control-label'
103 |
104 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
105 | ba.use :input
106 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
107 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
108 | end
109 | end
110 |
111 | config.wrappers :inline_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
112 | b.use :html5
113 | b.use :placeholder
114 | b.optional :maxlength
115 | b.optional :pattern
116 | b.optional :min_max
117 | b.optional :readonly
118 | b.use :label, class: 'sr-only'
119 |
120 | b.use :input, class: 'form-control'
121 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' }
122 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
123 | end
124 |
125 | config.wrappers :multi_select, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
126 | b.use :html5
127 | b.optional :readonly
128 | b.use :label, class: 'control-label'
129 | b.wrapper tag: 'div', class: 'form-inline' do |ba|
130 | ba.use :input, class: 'form-control'
131 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
132 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
133 | end
134 | end
135 | # Wrappers for forms and inputs using the Bootstrap toolkit.
136 | # Check the Bootstrap docs (http://getbootstrap.com)
137 | # to learn about the different styles for forms and inputs,
138 | # buttons and other elements.
139 | config.default_wrapper = :vertical_form
140 | config.wrapper_mappings = {
141 | check_boxes: :vertical_radio_and_checkboxes,
142 | radio_buttons: :vertical_radio_and_checkboxes,
143 | file: :vertical_file_input,
144 | boolean: :vertical_boolean,
145 | datetime: :multi_select,
146 | date: :multi_select,
147 | time: :multi_select
148 | }
149 | end
150 |
--------------------------------------------------------------------------------
/rails_app/lib/templates/rspec/scaffold/controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | <% module_namespacing do -%>
4 | describe <%= controller_class_name %>Controller do
5 |
6 | # This should return the minimal set of attributes required to create a valid
7 | # <%= class_name %>. As you add validations to <%= class_name %>, be sure to
8 | # adjust the attributes here as well.
9 | let(:valid_attributes) {
10 | attributes_for :<%= file_name %>
11 | }
12 |
13 | let(:invalid_attributes) {
14 | skip("Add a hash of attributes invalid for your model")
15 | }
16 |
17 | # This should return the minimal set of values that should be in the session
18 | # in order to pass any filters (e.g. authentication) defined in
19 | # <%= controller_class_name %>Controller. Be sure to keep this updated too.
20 | let(:valid_session) { {} }
21 |
22 | <% unless options[:singleton] -%>
23 | describe "GET #index" do
24 | it "assigns all <%= table_name.pluralize %> as @<%= table_name.pluralize %>" do
25 | <%= file_name %> = <%= class_name %>.create! valid_attributes
26 | get :index, {}, valid_session
27 | expect(assigns(:<%= table_name %>)).to eq([<%= file_name %>])
28 | end
29 | end
30 |
31 | <% end -%>
32 | describe "GET #show" do
33 | it "assigns the requested <%= ns_file_name %> as @<%= ns_file_name %>" do
34 | <%= file_name %> = <%= class_name %>.create! valid_attributes
35 | get :show, { id: <%= file_name %>.to_param }, valid_session
36 | expect(assigns(:<%= ns_file_name %>)).to eq(<%= file_name %>)
37 | end
38 | end
39 |
40 | describe "GET #new" do
41 | it "assigns a new <%= ns_file_name %> as @<%= ns_file_name %>" do
42 | get :new, {}, valid_session
43 | expect(assigns(:<%= ns_file_name %>)).to be_a_new(<%= class_name %>)
44 | end
45 | end
46 |
47 | describe "GET #edit" do
48 | it "assigns the requested <%= ns_file_name %> as @<%= ns_file_name %>" do
49 | <%= file_name %> = <%= class_name %>.create! valid_attributes
50 | get :edit, { id: <%= file_name %>.to_param }, valid_session
51 | expect(assigns(:<%= ns_file_name %>)).to eq(<%= file_name %>)
52 | end
53 | end
54 |
55 | describe "POST #create" do
56 | context "with valid params" do
57 | it "creates a new <%= class_name %>" do
58 | expect {
59 | post :create, {:<%= ns_file_name %> => valid_attributes}, valid_session
60 | }.to change(<%= class_name %>, :count).by(1)
61 | end
62 |
63 | it "assigns a newly created <%= ns_file_name %> as @<%= ns_file_name %>" do
64 | post :create, {:<%= ns_file_name %> => valid_attributes}, valid_session
65 | expect(assigns(:<%= ns_file_name %>)).to be_a(<%= class_name %>)
66 | expect(assigns(:<%= ns_file_name %>)).to be_persisted
67 | end
68 |
69 | it "redirects to the created <%= ns_file_name %>" do
70 | post :create, {:<%= ns_file_name %> => valid_attributes}, valid_session
71 | expect(response).to redirect_to(<%= class_name %>.last)
72 | end
73 | end
74 |
75 | context "with invalid params" do
76 | it "assigns a newly created but unsaved <%= ns_file_name %> as @<%= ns_file_name %>" do
77 | post :create, {:<%= ns_file_name %> => invalid_attributes}, valid_session
78 | expect(assigns(:<%= ns_file_name %>)).to be_a_new(<%= class_name %>)
79 | end
80 |
81 | it "re-renders the 'new' template" do
82 | post :create, {:<%= ns_file_name %> => invalid_attributes}, valid_session
83 | expect(response).to render_template("new")
84 | end
85 | end
86 | end
87 |
88 | describe "PUT #update" do
89 | context "with valid params" do
90 | let(:new_attributes) {
91 | skip("Add a hash of attributes valid for your model")
92 | }
93 |
94 | it "updates the requested <%= ns_file_name %>" do
95 | <%= file_name %> = <%= class_name %>.create! valid_attributes
96 | put :update, { id: <%= file_name %>.to_param, :<%= ns_file_name %> => new_attributes }, valid_session
97 | <%= file_name %>.reload
98 | skip("Add assertions for updated state")
99 | end
100 |
101 | it "assigns the requested <%= ns_file_name %> as @<%= ns_file_name %>" do
102 | <%= file_name %> = <%= class_name %>.create! valid_attributes
103 | put :update, { id: <%= file_name %>.to_param, :<%= ns_file_name %> => valid_attributes }, valid_session
104 | expect(assigns(:<%= ns_file_name %>)).to eq(<%= file_name %>)
105 | end
106 |
107 | it "redirects to the <%= ns_file_name %>" do
108 | <%= file_name %> = <%= class_name %>.create! valid_attributes
109 | put :update, { id: <%= file_name %>.to_param, :<%= ns_file_name %> => valid_attributes }, valid_session
110 | expect(response).to redirect_to(<%= file_name %>)
111 | end
112 | end
113 |
114 | context "with invalid params" do
115 | it "assigns the <%= ns_file_name %> as @<%= ns_file_name %>" do
116 | <%= file_name %> = <%= class_name %>.create! valid_attributes
117 | put :update, { id: <%= file_name %>.to_param, :<%= ns_file_name %> => invalid_attributes }, valid_session
118 | expect(assigns(:<%= ns_file_name %>)).to eq(<%= file_name %>)
119 | end
120 |
121 | it "re-renders the 'edit' template" do
122 | <%= file_name %> = <%= class_name %>.create! valid_attributes
123 | put :update, { id: <%= file_name %>.to_param, :<%= ns_file_name %> => invalid_attributes }, valid_session
124 | expect(response).to render_template("edit")
125 | end
126 | end
127 | end
128 |
129 | describe "DELETE #destroy" do
130 | it "destroys the requested <%= ns_file_name %>" do
131 | <%= file_name %> = <%= class_name %>.create! valid_attributes
132 | expect {
133 | delete :destroy, { id: <%= file_name %>.to_param }, valid_session
134 | }.to change(<%= class_name %>, :count).by(-1)
135 | end
136 |
137 | it "redirects to the <%= table_name %> list" do
138 | <%= file_name %> = <%= class_name %>.create! valid_attributes
139 | delete :destroy, { id: <%= file_name %>.to_param }, valid_session
140 | expect(response).to redirect_to(<%= index_helper %>_url)
141 | end
142 | end
143 |
144 | end
145 | <% end -%>
146 |
--------------------------------------------------------------------------------
/rails_app/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # Coverage must be enabled before the application is loaded.
2 | if ENV['COVERAGE']
3 | require 'simplecov'
4 |
5 | # Writes the coverage stat to a file to be used by Cane.
6 | class SimpleCov::Formatter::QualityFormatter
7 | def format(result)
8 | SimpleCov::Formatter::HTMLFormatter.new.format(result)
9 | File.open('coverage/covered_percent', 'w') do |f|
10 | f.puts result.source_files.covered_percent.to_f
11 | end
12 | end
13 | end
14 | SimpleCov.formatter = SimpleCov::Formatter::QualityFormatter
15 |
16 | SimpleCov.start do
17 | add_filter '/spec/'
18 | add_filter '/config/'
19 | add_filter '/vendor/'
20 | add_group 'Models', 'app/models'
21 | add_group 'Controllers', 'app/controllers'
22 | add_group 'Helpers', 'app/helpers'
23 | add_group 'Views', 'app/views'
24 | add_group 'Mailers', 'app/mailers'
25 | end
26 | end
27 |
28 | # This file was generated by the `rspec --init` command. Conventionally, all
29 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
30 | # The generated `.rspec` file contains `--require spec_helper` which will cause
31 | # this file to always be loaded, without a need to explicitly require it in any
32 | # files.
33 | #
34 | # Given that it is always loaded, you are encouraged to keep this file as
35 | # light-weight as possible. Requiring heavyweight dependencies from this file
36 | # will add to the boot time of your test suite on EVERY test run, even for an
37 | # individual file that may not need all of that loaded. Instead, consider making
38 | # a separate helper file that requires the additional dependencies and performs
39 | # the additional setup, and require it from the spec files that actually need
40 | # it.
41 | #
42 | # The `.rspec` file also contains a few flags that are not defaults but that
43 | # users commonly want.
44 | #
45 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
46 | RSpec.configure do |config|
47 | # rspec-expectations config goes here. You can use an alternate
48 | # assertion/expectation library such as wrong or the stdlib/minitest
49 | # assertions if you prefer.
50 | config.expect_with :rspec do |expectations|
51 | # This option will default to `true` in RSpec 4. It makes the `description`
52 | # and `failure_message` of custom matchers include text for helper methods
53 | # defined using `chain`, e.g.:
54 | # be_bigger_than(2).and_smaller_than(4).description
55 | # # => "be bigger than 2 and smaller than 4"
56 | # ...rather than:
57 | # # => "be bigger than 2"
58 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
59 | end
60 |
61 | # rspec-mocks config goes here. You can use an alternate test double
62 | # library (such as bogus or mocha) by changing the `mock_with` option here.
63 | config.mock_with :rspec do |mocks|
64 | # Prevents you from mocking or stubbing a method that does not exist on
65 | # a real object. This is generally recommended, and will default to
66 | # `true` in RSpec 4.
67 | mocks.verify_partial_doubles = true
68 | end
69 |
70 | # The settings below are suggested to provide a good initial experience
71 | # with RSpec, but feel free to customize to your heart's content.
72 |
73 | # Enable aggregate failures unless explcitly disabled at the spec level.
74 | # See http://rspec.info/blog/2015/06/rspec-3-3-has-been-released/#expectations-new-aggregratefailures-api
75 | config.define_derived_metadata do |meta|
76 | meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
77 | end
78 |
79 | # These two settings work together to allow you to limit a spec run
80 | # to individual examples or groups you care about by tagging them with
81 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples
82 | # get run.
83 | config.filter_run :focus
84 | config.run_all_when_everything_filtered = true
85 |
86 | # Allows RSpec to persist some state between runs in order to support
87 | # the `--only-failures` and `--next-failure` CLI options. We recommend
88 | # you configure your source control system to ignore this file.
89 | config.example_status_persistence_file_path = "spec/examples.txt"
90 |
91 | # Limits the available syntax to the non-monkey patched syntax that is
92 | # recommended. For more details, see:
93 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
94 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
95 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
96 | # config.disable_monkey_patching!
97 |
98 | # This setting enables warnings. It's recommended, but in some cases may
99 | # be too noisy due to issues in dependencies.
100 | # config.warnings = true
101 |
102 | # Many RSpec users commonly either run the entire suite or an individual
103 | # file, and it's useful to allow more verbose output when running an
104 | # individual spec file.
105 | if config.files_to_run.one?
106 | # Use the documentation formatter for detailed output,
107 | # unless a formatter has already been configured
108 | # (e.g. via a command-line flag).
109 | config.default_formatter = 'doc'
110 | end
111 |
112 | # Print the 10 slowest examples and example groups at the
113 | # end of the spec run, to help surface which specs are running
114 | # particularly slow.
115 | # config.profile_examples = 10
116 |
117 | # Run non-feature specs (shuffled) before feature specs.
118 | # config.register_ordering(:global) do |items|
119 | # features, others = items.partition { |g| g.metadata[:type] == :feature }
120 | # others.shuffle + features.
121 | # end
122 |
123 | # Run specs in random order to surface order dependencies. If you find an
124 | # order dependency and want to debug it, you can fix the order by providing
125 | # the seed, which is printed after each run.
126 | # --seed 1234
127 | config.order = :random
128 |
129 | # Seed global randomization in this process using the `--seed` CLI option.
130 | # Setting this allows you to use `--seed` to deterministically reproduce
131 | # test failures related to randomization by passing the same `--seed` value
132 | # as the one that triggered the failure.
133 | Kernel.srand config.seed
134 | end
135 |
--------------------------------------------------------------------------------
/rails_app/config/initializers/simple_form.rb:
--------------------------------------------------------------------------------
1 | # Use this setup block to configure all options available in SimpleForm.
2 | SimpleForm.setup do |config|
3 | # Wrappers are used by the form builder to generate a
4 | # complete input. You can remove any component from the
5 | # wrapper, change the order or even add your own to the
6 | # stack. The options given below are used to wrap the
7 | # whole input.
8 | config.wrappers :default, class: :input,
9 | hint_class: :field_with_hint, error_class: :field_with_errors do |b|
10 | ## Extensions enabled by default
11 | # Any of these extensions can be disabled for a
12 | # given input by passing: `f.input EXTENSION_NAME => false`.
13 | # You can make any of these extensions optional by
14 | # renaming `b.use` to `b.optional`.
15 |
16 | # Determines whether to use HTML5 (:email, :url, ...)
17 | # and required attributes
18 | b.use :html5
19 |
20 | # Calculates placeholders automatically from I18n
21 | # You can also pass a string as f.input placeholder: "Placeholder"
22 | b.use :placeholder
23 |
24 | ## Optional extensions
25 | # They are disabled unless you pass `f.input EXTENSION_NAME => true`
26 | # to the input. If so, they will retrieve the values from the model
27 | # if any exists. If you want to enable any of those
28 | # extensions by default, you can change `b.optional` to `b.use`.
29 |
30 | # Calculates maxlength from length validations for string inputs
31 | b.optional :maxlength
32 |
33 | # Calculates pattern from format validations for string inputs
34 | b.optional :pattern
35 |
36 | # Calculates min and max from length validations for numeric inputs
37 | b.optional :min_max
38 |
39 | # Calculates readonly automatically from readonly attributes
40 | b.optional :readonly
41 |
42 | ## Inputs
43 | b.use :label_input
44 | b.use :hint, wrap_with: { tag: :span, class: :hint }
45 | b.use :error, wrap_with: { tag: :span, class: :error }
46 |
47 | ## full_messages_for
48 | # If you want to display the full error message for the attribute, you can
49 | # use the component :full_error, like:
50 | #
51 | # b.use :full_error, wrap_with: { tag: :span, class: :error }
52 | end
53 |
54 | # The default wrapper to be used by the FormBuilder.
55 | config.default_wrapper = :default
56 |
57 | # Define the way to render check boxes / radio buttons with labels.
58 | # Defaults to :nested for bootstrap config.
59 | # inline: input + label
60 | # nested: label > input
61 | config.boolean_style = :nested
62 |
63 | # Default class for buttons
64 | config.button_class = 'btn'
65 |
66 | # Method used to tidy up errors. Specify any Rails Array method.
67 | # :first lists the first message for each field.
68 | # Use :to_sentence to list all errors for each field.
69 | # config.error_method = :first
70 |
71 | # Default tag used for error notification helper.
72 | config.error_notification_tag = :div
73 |
74 | # CSS class to add for error notification helper.
75 | config.error_notification_class = 'error_notification'
76 |
77 | # ID to add for error notification helper.
78 | # config.error_notification_id = nil
79 |
80 | # Series of attempts to detect a default label method for collection.
81 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
82 |
83 | # Series of attempts to detect a default value method for collection.
84 | # config.collection_value_methods = [ :id, :to_s ]
85 |
86 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
87 | # config.collection_wrapper_tag = nil
88 |
89 | # You can define the class to use on all collection wrappers. Defaulting to none.
90 | # config.collection_wrapper_class = nil
91 |
92 | # You can wrap each item in a collection of radio/check boxes with a tag,
93 | # defaulting to :span.
94 | # config.item_wrapper_tag = :span
95 |
96 | # You can define a class to use in all item wrappers. Defaulting to none.
97 | # config.item_wrapper_class = nil
98 |
99 | # How the label text should be generated altogether with the required text.
100 | # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" }
101 | config.label_text = lambda { |label, required, explicit_label| "#{label}" }
102 |
103 | # You can define the class to use on all labels. Default is nil.
104 | # config.label_class = nil
105 |
106 | # You can define the default class to be used on forms. Can be overriden
107 | # with `html: { :class }`. Defaulting to none.
108 | # config.default_form_class = nil
109 |
110 | # You can define which elements should obtain additional classes
111 | # config.generate_additional_classes_for = [:wrapper, :label, :input]
112 |
113 | # Whether attributes are required by default (or not). Default is true.
114 | # config.required_by_default = true
115 |
116 | # Tell browsers whether to use the native HTML5 validations (novalidate form option).
117 | # These validations are enabled in SimpleForm's internal config but disabled by default
118 | # in this configuration, which is recommended due to some quirks from different browsers.
119 | # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations,
120 | # change this configuration to true.
121 | config.browser_validations = false
122 |
123 | # Collection of methods to detect if a file type was given.
124 | # config.file_methods = [ :mounted_as, :file?, :public_filename ]
125 |
126 | # Custom mappings for input types. This should be a hash containing a regexp
127 | # to match as key, and the input type that will be used when the field name
128 | # matches the regexp as value.
129 | # config.input_mappings = { /count/ => :integer }
130 |
131 | # Custom wrappers for input types. This should be a hash containing an input
132 | # type as key and the wrapper that will be used for all inputs with specified type.
133 | # config.wrapper_mappings = { string: :prepend }
134 |
135 | # Namespaces where SimpleForm should look for custom input classes that
136 | # override default inputs.
137 | # config.custom_inputs_namespaces << "CustomInputs"
138 |
139 | # Default priority for time_zone inputs.
140 | # config.time_zone_priority = nil
141 |
142 | # Default priority for country inputs.
143 | # config.country_priority = nil
144 |
145 | # When false, do not use translations for labels.
146 | # config.translate_labels = true
147 |
148 | # Automatically discover new inputs in Rails' autoload path.
149 | # config.inputs_discovery = true
150 |
151 | # Cache SimpleForm inputs discovery
152 | # config.cache_discovery = !Rails.env.development?
153 |
154 | # Default class for inputs
155 | # config.input_class = nil
156 |
157 | # Define the default class of the input wrapper of the boolean input.
158 | config.boolean_label_class = 'checkbox'
159 |
160 | # Defines if the default input wrapper class should be included in radio
161 | # collection wrappers.
162 | # config.include_default_input_wrapper_class = true
163 |
164 | # Defines which i18n scope will be used in Simple Form.
165 | # config.i18n_scope = 'simple_form'
166 | end
167 |
--------------------------------------------------------------------------------
/rails_app/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GIT
2 | remote: git://github.com/plataformatec/devise.git
3 | revision: c2c74b0a39238e7d997486814a1c8f75fdaf276f
4 | specs:
5 | devise (4.1.0)
6 | bcrypt (~> 3.0)
7 | orm_adapter (~> 0.1)
8 | railties (>= 4.1.0, < 5.1)
9 | responders
10 | warden (~> 1.2.3)
11 |
12 | GEM
13 | remote: https://rubygems.org/
14 | specs:
15 | actioncable (5.0.0.rc2)
16 | actionpack (= 5.0.0.rc2)
17 | nio4r (~> 1.2)
18 | websocket-driver (~> 0.6.1)
19 | actionmailer (5.0.0.rc2)
20 | actionpack (= 5.0.0.rc2)
21 | actionview (= 5.0.0.rc2)
22 | activejob (= 5.0.0.rc2)
23 | mail (~> 2.5, >= 2.5.4)
24 | rails-dom-testing (~> 1.0, >= 1.0.5)
25 | actionpack (5.0.0.rc2)
26 | actionview (= 5.0.0.rc2)
27 | activesupport (= 5.0.0.rc2)
28 | rack (~> 2.x)
29 | rack-test (~> 0.6.3)
30 | rails-dom-testing (~> 1.0, >= 1.0.5)
31 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
32 | actionview (5.0.0.rc2)
33 | activesupport (= 5.0.0.rc2)
34 | builder (~> 3.1)
35 | erubis (~> 2.7.0)
36 | rails-dom-testing (~> 1.0, >= 1.0.5)
37 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
38 | activejob (5.0.0.rc2)
39 | activesupport (= 5.0.0.rc2)
40 | globalid (>= 0.3.6)
41 | activemodel (5.0.0.rc2)
42 | activesupport (= 5.0.0.rc2)
43 | activerecord (5.0.0.rc2)
44 | activemodel (= 5.0.0.rc2)
45 | activesupport (= 5.0.0.rc2)
46 | arel (~> 7.0)
47 | activesupport (5.0.0.rc2)
48 | concurrent-ruby (~> 1.0, >= 1.0.2)
49 | i18n (~> 0.7)
50 | minitest (~> 5.1)
51 | tzinfo (~> 1.1)
52 | addressable (2.4.0)
53 | arel (7.0.0)
54 | autoprefixer-rails (6.3.6.2)
55 | execjs
56 | awesome_print (1.7.0)
57 | bcrypt (3.1.11)
58 | better_errors (2.1.1)
59 | coderay (>= 1.0.0)
60 | erubis (>= 2.6.6)
61 | rack (>= 0.9.0)
62 | binding_of_caller (0.7.2)
63 | debug_inspector (>= 0.0.1)
64 | bootstrap-sass (3.3.6)
65 | autoprefixer-rails (>= 5.2.1)
66 | sass (>= 3.3.4)
67 | builder (3.2.2)
68 | capybara (2.7.1)
69 | addressable
70 | mime-types (>= 1.16)
71 | nokogiri (>= 1.3.3)
72 | rack (>= 1.0.0)
73 | rack-test (>= 0.5.4)
74 | xpath (~> 2.0)
75 | cliver (0.3.2)
76 | coderay (1.1.1)
77 | coffee-rails (4.1.1)
78 | coffee-script (>= 2.2.0)
79 | railties (>= 4.0.0, < 5.1.x)
80 | coffee-script (2.4.1)
81 | coffee-script-source
82 | execjs
83 | coffee-script-source (1.10.0)
84 | concurrent-ruby (1.0.2)
85 | database_cleaner (1.5.3)
86 | debug_inspector (0.0.2)
87 | diff-lcs (1.2.5)
88 | docile (1.1.5)
89 | erubis (2.7.0)
90 | execjs (2.7.0)
91 | factory_girl (4.7.0)
92 | activesupport (>= 3.0.0)
93 | factory_girl_rails (4.7.0)
94 | factory_girl (~> 4.7.0)
95 | railties (>= 3.0.0)
96 | ffi (1.9.10)
97 | foreman (0.82.0)
98 | thor (~> 0.19.1)
99 | globalid (0.3.6)
100 | activesupport (>= 4.1.0)
101 | i18n (0.7.0)
102 | jasmine-core (2.4.1)
103 | jasmine-rails (0.12.5)
104 | jasmine-core (>= 1.3, < 3.0)
105 | phantomjs (>= 1.9)
106 | railties (>= 3.2.0)
107 | sprockets-rails
108 | jquery-rails (4.1.1)
109 | rails-dom-testing (>= 1, < 3)
110 | railties (>= 4.2.0)
111 | thor (>= 0.14, < 2.0)
112 | json (1.8.3)
113 | launchy (2.4.3)
114 | addressable (~> 2.3)
115 | listen (3.1.5)
116 | rb-fsevent (~> 0.9, >= 0.9.4)
117 | rb-inotify (~> 0.9, >= 0.9.7)
118 | ruby_dep (~> 1.2)
119 | loofah (2.0.3)
120 | nokogiri (>= 1.5.9)
121 | mail (2.6.4)
122 | mime-types (>= 1.16, < 4)
123 | method_source (0.8.2)
124 | mime-types (3.1)
125 | mime-types-data (~> 3.2015)
126 | mime-types-data (3.2016.0521)
127 | mini_portile2 (2.1.0)
128 | minitest (5.9.0)
129 | multi_json (1.12.1)
130 | nio4r (1.2.1)
131 | nokogiri (1.6.8)
132 | mini_portile2 (~> 2.1.0)
133 | pkg-config (~> 1.1.7)
134 | orm_adapter (0.5.0)
135 | pg (0.18.4)
136 | phantomjs (2.1.1.0)
137 | pkg-config (1.1.7)
138 | poltergeist (1.9.0)
139 | capybara (~> 2.1)
140 | cliver (~> 0.3.1)
141 | multi_json (~> 1.0)
142 | websocket-driver (>= 0.2.0)
143 | puma (3.4.0)
144 | rack (2.0.0.rc1)
145 | json
146 | rack-canonical-host (0.2.2)
147 | addressable (> 0, < 3)
148 | rack (>= 1.0.0, < 3)
149 | rack-test (0.6.3)
150 | rack (>= 1.0)
151 | rack-timeout (0.4.2)
152 | rails (5.0.0.rc2)
153 | actioncable (= 5.0.0.rc2)
154 | actionmailer (= 5.0.0.rc2)
155 | actionpack (= 5.0.0.rc2)
156 | actionview (= 5.0.0.rc2)
157 | activejob (= 5.0.0.rc2)
158 | activemodel (= 5.0.0.rc2)
159 | activerecord (= 5.0.0.rc2)
160 | activesupport (= 5.0.0.rc2)
161 | bundler (>= 1.3.0, < 2.0)
162 | railties (= 5.0.0.rc2)
163 | sprockets-rails (>= 2.0.0)
164 | rails-deprecated_sanitizer (1.0.3)
165 | activesupport (>= 4.2.0.alpha)
166 | rails-dom-testing (1.0.7)
167 | activesupport (>= 4.2.0.beta, < 5.0)
168 | nokogiri (~> 1.6.0)
169 | rails-deprecated_sanitizer (>= 1.0.1)
170 | rails-html-sanitizer (1.0.3)
171 | loofah (~> 2.0)
172 | railties (5.0.0.rc2)
173 | actionpack (= 5.0.0.rc2)
174 | activesupport (= 5.0.0.rc2)
175 | method_source
176 | rake (>= 0.8.7)
177 | thor (>= 0.18.1, < 2.0)
178 | rake (11.2.2)
179 | rb-fsevent (0.9.7)
180 | rb-inotify (0.9.7)
181 | ffi (>= 0.5.0)
182 | responders (2.2.0)
183 | railties (>= 4.2.0, < 5.1)
184 | rspec-core (3.5.0.beta4)
185 | rspec-support (= 3.5.0.beta4)
186 | rspec-expectations (3.5.0.beta4)
187 | diff-lcs (>= 1.2.0, < 2.0)
188 | rspec-support (= 3.5.0.beta4)
189 | rspec-mocks (3.5.0.beta4)
190 | diff-lcs (>= 1.2.0, < 2.0)
191 | rspec-support (= 3.5.0.beta4)
192 | rspec-rails (3.5.0.beta4)
193 | actionpack (>= 3.0)
194 | activesupport (>= 3.0)
195 | railties (>= 3.0)
196 | rspec-core (= 3.5.0.beta4)
197 | rspec-expectations (= 3.5.0.beta4)
198 | rspec-mocks (= 3.5.0.beta4)
199 | rspec-support (= 3.5.0.beta4)
200 | rspec-support (3.5.0.beta4)
201 | ruby_dep (1.3.1)
202 | sass (3.4.22)
203 | sassc (1.10.0)
204 | bundler
205 | ffi (~> 1.9.6)
206 | sass (>= 3.3.0)
207 | sassc-rails (1.2.1)
208 | railties (>= 4.0.0)
209 | sass
210 | sassc (~> 1.9)
211 | sprockets (> 2.11)
212 | sprockets-rails
213 | tilt
214 | simple_form (3.2.1)
215 | actionpack (> 4, < 5.1)
216 | activemodel (> 4, < 5.1)
217 | simplecov (0.11.2)
218 | docile (~> 1.1.0)
219 | json (~> 1.8)
220 | simplecov-html (~> 0.10.0)
221 | simplecov-html (0.10.0)
222 | slim (3.0.7)
223 | temple (~> 0.7.6)
224 | tilt (>= 1.3.3, < 2.1)
225 | slim-rails (3.1.0)
226 | actionpack (>= 3.1)
227 | railties (>= 3.1)
228 | slim (~> 3.0)
229 | spring (1.7.1)
230 | spring-commands-rspec (1.0.4)
231 | spring (>= 0.9.1)
232 | spring-watcher-listen (2.0.0)
233 | listen (>= 2.7, < 4.0)
234 | spring (~> 1.2)
235 | sprockets (3.6.2)
236 | concurrent-ruby (~> 1.0)
237 | rack (> 1, < 3)
238 | sprockets-rails (3.0.4)
239 | actionpack (>= 4.0)
240 | activesupport (>= 4.0)
241 | sprockets (>= 3.0.0)
242 | temple (0.7.7)
243 | thor (0.19.1)
244 | thread_safe (0.3.5)
245 | tilt (2.0.5)
246 | tzinfo (1.2.2)
247 | thread_safe (~> 0.1)
248 | uglifier (3.0.0)
249 | execjs (>= 0.3.0, < 3)
250 | warden (1.2.6)
251 | rack (>= 1.0)
252 | websocket-driver (0.6.4)
253 | websocket-extensions (>= 0.1.0)
254 | websocket-extensions (0.1.2)
255 | xpath (2.0.0)
256 | nokogiri (~> 1.3)
257 |
258 | PLATFORMS
259 | ruby
260 |
261 | DEPENDENCIES
262 | autoprefixer-rails
263 | awesome_print
264 | better_errors
265 | binding_of_caller
266 | bootstrap-sass
267 | capybara
268 | coffee-rails
269 | database_cleaner
270 | devise!
271 | factory_girl_rails
272 | foreman
273 | jasmine-rails
274 | jquery-rails
275 | launchy
276 | listen
277 | pg
278 | poltergeist
279 | puma
280 | rack-canonical-host
281 | rack-timeout
282 | rails (~> 5.0.0.rc2)
283 | rspec-rails (~> 3.5.0.beta3)
284 | sassc-rails
285 | simple_form
286 | simplecov
287 | slim-rails
288 | spring
289 | spring-commands-rspec
290 | spring-watcher-listen
291 | uglifier
292 |
293 | BUNDLED WITH
294 | 1.12.5
295 |
--------------------------------------------------------------------------------
/rails_app/config/initializers/devise.rb:
--------------------------------------------------------------------------------
1 | # Use this hook to configure devise mailer, warden hooks and so forth.
2 | # Many of these configuration options can be set straight in your model.
3 | Devise.setup do |config|
4 | # The secret key used by Devise. Devise uses this key to generate
5 | # random tokens. Changing this key will render invalid all existing
6 | # confirmation, reset password and unlock tokens in the database.
7 | # Devise will use the `secret_key_base` on Rails 4+ applications as its `secret_key`
8 | # by default. You can change it below and use your own secret key.
9 | config.secret_key = 'f5fc3d398f06ae74db7e8558fc168865fa68891f47601a90002bdea1f47f07c94bc07e9ced6fe32f9697fc61bb63af689b92105b71cfe6b5f65d11b35cbbdea8'
10 |
11 | # ==> Mailer Configuration
12 | # Configure the e-mail address which will be shown in Devise::Mailer,
13 | # note that it will be overwritten if you use your own mailer class
14 | # with default "from" parameter.
15 | config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
16 |
17 | # Configure the class responsible to send e-mails.
18 | # config.mailer = 'Devise::Mailer'
19 |
20 | # ==> ORM configuration
21 | # Load and configure the ORM. Supports :active_record (default) and
22 | # :mongoid (bson_ext recommended) by default. Other ORMs may be
23 | # available as additional gems.
24 | require 'devise/orm/active_record'
25 |
26 | # ==> Configuration for any authentication mechanism
27 | # Configure which keys are used when authenticating a user. The default is
28 | # just :email. You can configure it to use [:username, :subdomain], so for
29 | # authenticating a user, both parameters are required. Remember that those
30 | # parameters are used only when authenticating and not when retrieving from
31 | # session. If you need permissions, you should implement that in a before filter.
32 | # You can also supply a hash where the value is a boolean determining whether
33 | # or not authentication should be aborted when the value is not present.
34 | # config.authentication_keys = [:email]
35 |
36 | # Configure parameters from the request object used for authentication. Each entry
37 | # given should be a request method and it will automatically be passed to the
38 | # find_for_authentication method and considered in your model lookup. For instance,
39 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
40 | # The same considerations mentioned for authentication_keys also apply to request_keys.
41 | # config.request_keys = []
42 |
43 | # Configure which authentication keys should be case-insensitive.
44 | # These keys will be downcased upon creating or modifying a user and when used
45 | # to authenticate or find a user. Default is :email.
46 | config.case_insensitive_keys = [:email]
47 |
48 | # Configure which authentication keys should have whitespace stripped.
49 | # These keys will have whitespace before and after removed upon creating or
50 | # modifying a user and when used to authenticate or find a user. Default is :email.
51 | config.strip_whitespace_keys = [:email]
52 |
53 | # Tell if authentication through request.params is enabled. True by default.
54 | # It can be set to an array that will enable params authentication only for the
55 | # given strategies, for example, `config.params_authenticatable = [:database]` will
56 | # enable it only for database (email + password) authentication.
57 | # config.params_authenticatable = true
58 |
59 | # Tell if authentication through HTTP Auth is enabled. False by default.
60 | # It can be set to an array that will enable http authentication only for the
61 | # given strategies, for example, `config.http_authenticatable = [:database]` will
62 | # enable it only for database authentication. The supported strategies are:
63 | # :database = Support basic authentication with authentication key + password
64 | # config.http_authenticatable = false
65 |
66 | # If 401 status code should be returned for AJAX requests. True by default.
67 | # config.http_authenticatable_on_xhr = true
68 |
69 | # The realm used in Http Basic Authentication. 'Application' by default.
70 | # config.http_authentication_realm = 'Application'
71 |
72 | # It will change confirmation, password recovery and other workflows
73 | # to behave the same regardless if the e-mail provided was right or wrong.
74 | # Does not affect registerable.
75 | # config.paranoid = true
76 |
77 | # By default Devise will store the user in session. You can skip storage for
78 | # particular strategies by setting this option.
79 | # Notice that if you are skipping storage for all authentication paths, you
80 | # may want to disable generating routes to Devise's sessions controller by
81 | # passing skip: :sessions to `devise_for` in your config/routes.rb
82 | config.skip_session_storage = [:http_auth]
83 |
84 | # By default, Devise cleans up the CSRF token on authentication to
85 | # avoid CSRF token fixation attacks. This means that, when using AJAX
86 | # requests for sign in and sign up, you need to get a new CSRF token
87 | # from the server. You can disable this option at your own risk.
88 | # config.clean_up_csrf_token_on_authentication = true
89 |
90 | # ==> Configuration for :database_authenticatable
91 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If
92 | # using other encryptors, it sets how many times you want the password re-encrypted.
93 | #
94 | # Limiting the stretches to just one in testing will increase the performance of
95 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
96 | # a value less than 10 in other environments. Note that, for bcrypt (the default
97 | # encryptor), the cost increases exponentially with the number of stretches (e.g.
98 | # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
99 | config.stretches = Rails.env.test? ? 1 : 10
100 |
101 | # Setup a pepper to generate the encrypted password.
102 | # config.pepper = 'a280d8cdb85c847495fa7abb3fa1a688af8852c36cfc6007887d846802eabce88e4ef08ab6baaf2b8b60a015f2aa9c1952c63b54237cf860109923ad18c59187'
103 |
104 | # Send a notification email when the user's password is changed
105 | # config.send_password_change_notification = false
106 |
107 | # ==> Configuration for :confirmable
108 | # A period that the user is allowed to access the website even without
109 | # confirming their account. For instance, if set to 2.days, the user will be
110 | # able to access the website for two days without confirming their account,
111 | # access will be blocked just in the third day. Default is 0.days, meaning
112 | # the user cannot access the website without confirming their account.
113 | # config.allow_unconfirmed_access_for = 2.days
114 |
115 | # A period that the user is allowed to confirm their account before their
116 | # token becomes invalid. For example, if set to 3.days, the user can confirm
117 | # their account within 3 days after the mail was sent, but on the fourth day
118 | # their account can't be confirmed with the token any more.
119 | # Default is nil, meaning there is no restriction on how long a user can take
120 | # before confirming their account.
121 | # config.confirm_within = 3.days
122 |
123 | # If true, requires any email changes to be confirmed (exactly the same way as
124 | # initial account confirmation) to be applied. Requires additional unconfirmed_email
125 | # db field (see migrations). Until confirmed, new email is stored in
126 | # unconfirmed_email column, and copied to email column on successful confirmation.
127 | config.reconfirmable = true
128 |
129 | # Defines which key will be used when confirming an account
130 | # config.confirmation_keys = [:email]
131 |
132 | # ==> Configuration for :rememberable
133 | # The time the user will be remembered without asking for credentials again.
134 | # config.remember_for = 2.weeks
135 |
136 | # Invalidates all the remember me tokens when the user signs out.
137 | config.expire_all_remember_me_on_sign_out = true
138 |
139 | # If true, extends the user's remember period when remembered via cookie.
140 | # config.extend_remember_period = false
141 |
142 | # Options to be passed to the created cookie. For instance, you can set
143 | # secure: true in order to force SSL only cookies.
144 | # config.rememberable_options = {}
145 |
146 | # ==> Configuration for :validatable
147 | # Range for password length.
148 | config.password_length = 8..72
149 |
150 | # Email regex used to validate email formats. It simply asserts that
151 | # one (and only one) @ exists in the given string. This is mainly
152 | # to give user feedback and not to assert the e-mail validity.
153 | # config.email_regexp = /\A[^@]+@[^@]+\z/
154 |
155 | # ==> Configuration for :timeoutable
156 | # The time you want to timeout the user session without activity. After this
157 | # time the user will be asked for credentials again. Default is 30 minutes.
158 | # config.timeout_in = 30.minutes
159 |
160 | # ==> Configuration for :lockable
161 | # Defines which strategy will be used to lock an account.
162 | # :failed_attempts = Locks an account after a number of failed attempts to sign in.
163 | # :none = No lock strategy. You should handle locking by yourself.
164 | # config.lock_strategy = :failed_attempts
165 |
166 | # Defines which key will be used when locking and unlocking an account
167 | # config.unlock_keys = [:email]
168 |
169 | # Defines which strategy will be used to unlock an account.
170 | # :email = Sends an unlock link to the user email
171 | # :time = Re-enables login after a certain amount of time (see :unlock_in below)
172 | # :both = Enables both strategies
173 | # :none = No unlock strategy. You should handle unlocking by yourself.
174 | # config.unlock_strategy = :both
175 |
176 | # Number of authentication tries before locking an account if lock_strategy
177 | # is failed attempts.
178 | # config.maximum_attempts = 20
179 |
180 | # Time interval to unlock the account if :time is enabled as unlock_strategy.
181 | # config.unlock_in = 1.hour
182 |
183 | # Warn on the last attempt before the account is locked.
184 | # config.last_attempt_warning = true
185 |
186 | # ==> Configuration for :recoverable
187 | #
188 | # Defines which key will be used when recovering the password for an account
189 | # config.reset_password_keys = [:email]
190 |
191 | # Time interval you can reset your password with a reset password key.
192 | # Don't put a too small interval or your users won't have the time to
193 | # change their passwords.
194 | config.reset_password_within = 6.hours
195 |
196 | # When set to false, does not sign a user in automatically after their password is
197 | # reset. Defaults to true, so a user is signed in automatically after a reset.
198 | # config.sign_in_after_reset_password = true
199 |
200 | # ==> Configuration for :encryptable
201 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use
202 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
203 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
204 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
205 | # REST_AUTH_SITE_KEY to pepper).
206 | #
207 | # Require the `devise-encryptable` gem when using anything other than bcrypt
208 | # config.encryptor = :sha512
209 |
210 | # ==> Scopes configuration
211 | # Turn scoped views on. Before rendering "sessions/new", it will first check for
212 | # "users/sessions/new". It's turned off by default because it's slower if you
213 | # are using only default views.
214 | # config.scoped_views = false
215 |
216 | # Configure the default scope given to Warden. By default it's the first
217 | # devise role declared in your routes (usually :user).
218 | # config.default_scope = :user
219 |
220 | # Set this configuration to false if you want /users/sign_out to sign out
221 | # only the current scope. By default, Devise signs out all scopes.
222 | # config.sign_out_all_scopes = true
223 |
224 | # ==> Navigation configuration
225 | # Lists the formats that should be treated as navigational. Formats like
226 | # :html, should redirect to the sign in page when the user does not have
227 | # access, but formats like :xml or :json, should return 401.
228 | #
229 | # If you have any extra navigational formats, like :iphone or :mobile, you
230 | # should add them to the navigational formats lists.
231 | #
232 | # The "*/*" below is required to match Internet Explorer requests.
233 | # config.navigational_formats = ['*/*', :html]
234 |
235 | # The default HTTP method used to sign out a resource. Default is :delete.
236 | config.sign_out_via = :delete
237 |
238 | # ==> OmniAuth
239 | # Add a new OmniAuth provider. Check the wiki for more information on setting
240 | # up on your models and hooks.
241 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
242 |
243 | # ==> Warden configuration
244 | # If you want to use other strategies, that are not supported by Devise, or
245 | # change the failure app, you can configure them inside the config.warden block.
246 | #
247 | # config.warden do |manager|
248 | # manager.intercept_401 = false
249 | # manager.default_strategies(scope: :user).unshift :some_external_strategy
250 | # end
251 |
252 | # ==> Mountable engine configurations
253 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine
254 | # is mountable, there are some extra configurations to be taken into account.
255 | # The following options are available, assuming the engine is mounted as:
256 | #
257 | # mount MyEngine, at: '/my_engine'
258 | #
259 | # The router that invoked `devise_for`, in the example above, would be:
260 | # config.router_name = :my_engine
261 | #
262 | # When using OmniAuth, Devise cannot automatically set OmniAuth path,
263 | # so you need to do it manually. For the users scope, it would be:
264 | # config.omniauth_path_prefix = '/my_engine/users/auth'
265 | end
266 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rails, meet Phoenix
2 |
3 | ### Migrating to Phoenix with Rails session sharing
4 |
5 | You’ve resolved to build your company’s Next Big Thing in Phoenix and Elixir. That’s great! You’re facing a problem though - all user authentication and access concerns are performed on your Rails system, and the work to reimplement this in Phoenix is significant.
6 |
7 | Fortunately for you, there is a great Phoenix plug to share session data between Rails and Phoenix. If you pull this off, you'll be able to build your new API on your Phoenix app, all while letting Rails handle user authentication and session management.
8 |
9 | ### Before we begin
10 | In this scenario, you want to build out a new API in Phoenix that is consumed by your frontend single-page application, whose sessions are hosted on Rails. We'll call the Rails app `rails_app` and your new Phoenix app `phoenix_app`.
11 |
12 | Additionally, each app will use a different subdomain. The Rails app will be deployed at the `www.myapp.com` subdomain. The Phoenix app will be deployed at the `api.myapp.com` subdomain.
13 |
14 | We are going to take [Chris Constantin](https://github.com/cconstantin)'s excellent [`PlugRailsCookieSessionStore`](https://github.com/cconstantin/plug_rails_cookie_session_store) plug and integrate it into our Phoenix project. Both apps will be configured with identical cookie domains, encryption salts, signing salts, and security tokens.
15 |
16 | In the examples that follow, I'll be using the latest versions of each framework at the time of writing, Rails 4.2 and Phoenix 1.2.
17 |
18 | ### Cookie-based session storage
19 |
20 | Our session data is stored on the client in a secure, encrypted, validated cookie. We won't cover the basics of cookies here, but [you can read more about them here](http://www.justinweiss.com/articles/how-rails-sessions-work/).
21 |
22 | Our approach will only work if your current Rails system utilizes cookie-based sessions. We will not cover the use case with a database-backed session store in SQL, Redis, or Memcache.
23 |
24 | ### Step 1: Configure Rails accordingly
25 |
26 | #### Configure the cookie store
27 |
28 | Let's set up your Rails app to use a JSON cookie storage format:
29 |
30 | ```ruby
31 | # config/initializer/session_store.rb
32 |
33 | # Use cookie session storage in JSON format. Here, we scope the cookie to the root domain.
34 | Rails.application.config.session_store :cookie_store, key: '_rails_app_session', domain: ".#{ENV['DOMAIN']}"
35 | Rails.application.config.action_dispatch.cookies_serializer = :json
36 |
37 | # These salts are optional, but it doesn't hurt to explicitly configure them the same between the two apps.
38 | Rails.application.config.action_dispatch.encrypted_cookie_salt = ENV['SESSION_ENCRYPTED_COOKIE_SALT']
39 | Rails.application.config.action_dispatch.encrypted_signed_cookie_salt = ENV['SESSION_ENCRYPTED_SIGNED_COOKIE_SALT']
40 |
41 | ```
42 |
43 | Your app may not be configured with a `SESSION_ENCRYPTED_COOKIE_SALT` and `SESSION_ENCRYPTED_SIGNED_COOKIE_SALT`. You may generate a pair with any random values.
44 |
45 | [Some speculate](http://nipperlabs.com/rails-secretkeybase) that Rails does not require the two salts by default because the `SECRET_KEY_BASE` is sufficiently long enough to not require a salt. In our example, we choose to supply them anyways to be explicit.
46 |
47 | Another important value to note here is that we have chosen a key for our session cookie - `_rails_app_session`. This value will be the shared cookie key for both apps.
48 |
49 | ### Step 2: Configure the plug for Phoenix
50 |
51 | Turning our attention to our Phoenix app, in the `mix.exs` file, add the library dependency:
52 |
53 | ```elixir
54 | # mix.exs
55 | defmodule PhoenixApp
56 | defp deps do
57 | # snip
58 | {:plug_rails_cookie_session_store, "~> 0.1"},
59 | # snip
60 | end
61 | end
62 | ```
63 |
64 | Then run `mix deps.get` to fetch the new library.
65 |
66 | Now in your `web/phoenix_app/endpoint.ex` file, remove the configuration for the existing session store and add the configuration for the Rails session store.
67 |
68 | ```elixir
69 | # lib/phoenix_app/endpoint.ex
70 | defmodule PhoenixApp.Endpoint do
71 | plug Plug.Session,
72 | # Remove the original cookie store that comes with Phoenix, out of the box.
73 | # store: :cookie,
74 | # key: "_phoenix_app_key",
75 | # signing_salt: "M8emDP0h"
76 | store: PlugRailsCookieSessionStore,
77 | # Decide on a shared key for your cookie. Oftentimes, this should
78 | # mirror your Rails app session key
79 | key: "_rails_app_session",
80 | secure: true,
81 | encrypt: true,
82 | # Specifies the matching rules on the hostname that this cookie will be valid for
83 | domain: ".#{System.get_env("DOMAIN")}",
84 | signing_salt: System.get_env("SESSION_ENCRYPTED_SIGNED_COOKIE_SALT"),
85 | encryption_salt: System.get_env("SESSION_ENCRYPTED_COOKIE_SALT"),
86 | key_iterations: 1000,
87 | key_length: 64,
88 | key_digest: :sha,
89 | # Specify a JSON serializer to use on the session
90 | serializer: Poison
91 | end
92 |
93 | ```
94 |
95 | We set a `DOMAIN` environment variable with the value
96 | `myapp.com`. The goal is for these two apps to be able to be deployed at any subdomain that ends in `myapp.com`, and still be able to share the cookie.
97 |
98 | The `secure` flag configures the app to send a secure cookie, which only is served over SSL HTTPS connections. It is highly recommended for your site; if you haven't upgraded to SSL, you should do so now!
99 |
100 | Our cookies are signed such that their origins are guaranteed to have been computed from our app(s). This is done for free with Rails (and Phoenix's) session libraries. The signature is derived from the `secret_key_base` and `signing_salt`.
101 |
102 | The `encrypt` flag encrypts the contents of the cookie's value with an encryption key derived from `secret_key_base` and `encryption_salt`. This should always be set to `true`.
103 |
104 | `key_iterations`, `key_length` and `key_digest` are configurations that dictate how the signing and encryption keys are derived. These are [configured to match Rails' defaults](https://github.com/rails/rails/blob/4-2-stable/railties/lib/rails/application.rb) (see also: [defaults](https://github.com/rails/rails/blob/4-2-stable/activesupport/lib/active_support/key_generator.rb)). Unless your Rails app has custom configurations for these values, you should leave them be.
105 |
106 | ### Step 3: Configure both apps to read from the new environment variables
107 |
108 | Be sure your development and production versions of your app are configured with identical values for `DOMAIN`, `SESSION_ENCRYPTED_COOKIE_SALT` and `SESSION_ENCRYPTED_SIGNED_COOKIE_SALT`. You'll want to make sure your production apps store identical key-value pairs.
109 |
110 | ### Step 4: Change Phoenix controllers to verify sessions based on session data.
111 |
112 | Now when the Phoenix app receives incoming requests, it can simply look up user session data in the session cookie to determine whether the user is logged in, and who that user is.
113 |
114 | In this example, our Rails app implements user auth with Devise and Warden. We know that Warden stores the user ID and a segment of the password hash in the `warden.user.user.key` session variable.
115 |
116 | Here's what the raw session data looks like when the `PlugRailsCookieSessionStore` extracts it from the cookie:
117 |
118 | ```elixir
119 | %{"_csrf_token" => "ELeSt4MBUINKi0STEBpslw3UevGZuVLUx5zGVP5NlQU=",
120 | "session_id" => "17ec9b696fe76ba4a777d625e57f3521",
121 | "warden.user.user.key" => [[2], "$2a$10$R/3NKl9KQViQxY8eoMCIp."]}
122 | ```
123 |
124 | ```elixir
125 | defmodule PhoenixApp.SomeApiResourceController do
126 | use PhoenixApp.Web, :controller
127 |
128 | def index(conn, _params) do
129 | {:ok, user_id} = load_user(conn)
130 |
131 | conn
132 | |> assign(:user_id, user_id)
133 | |> render("index.html")
134 | end
135 |
136 | plug :verify_session
137 |
138 | # If we've found a user, then allow the request to continue.
139 | # Otherwise, halt the request and return a 401
140 | defp verify_session(conn, _) do
141 | case load_user(conn) do
142 | {:ok, user_id} -> conn
143 | {:error, _} -> conn |> send_resp(401, "Unauthorized") |> halt
144 | end
145 | end
146 |
147 | defp load_user(conn) do
148 | # => The Warden user storage scheme: [user_id, password_hash_truncated]
149 | # [[1], "$2a$10$vnx35UTTJQURfqbM6srv3e"]
150 | warden_key = conn |> get_session("warden.user.user.key")
151 |
152 | case warden_key do
153 | [[user_id], _] -> {:ok, user_id}
154 | _ -> {:error, :not_found}
155 | end
156 | end
157 | end
158 | ```
159 |
160 | A very naive plug implementation simply renders a 401 if the session key is not found in the session, otherwise it allows the request through.
161 |
162 | ### Step 5: Move session concerns into its own module
163 |
164 | Let's move session concerns around session parsing out of the controller into its own `Session` module. Additionally, we include two helpers, `current_user/1` and `logged_in?/1`.
165 |
166 | ```elixir
167 | # web/models/session.ex
168 | defmodule PhoenixApp.Session do
169 | use PhoenixApp.Web, :controller
170 | def current_user(conn) do
171 | # Our app's concept of a User is merely whatever is stored in the
172 | # Session key. In the future, we could then use this as the delegation
173 | # point to fetch more details about the user from a backend store.
174 | case load_user(conn) do
175 | {:ok, user_id} -> user_id
176 | {:error, :not_found} -> nil
177 | end
178 | end
179 |
180 | def logged_in?(conn) do
181 | !!current_user(conn)
182 | end
183 |
184 | def load_user(conn) do
185 | # => The Warden user storage scheme: [user_id, password_hash_truncated]
186 | # [[1], "$2a$10$vnx35UTTJQURfqbM6srv3e"]
187 | warden_key = conn |> get_session("warden.user.user.key")
188 |
189 | case warden_key do
190 | [[user_id], _] -> {:ok, user_id}
191 | _ -> {:error, :not_found}
192 | end
193 | end
194 | end
195 | ```
196 |
197 | This leaves the controller looking skinnier, implementing only the Plug. Extracted methods are delegated to the new `Session` module.
198 |
199 | ```elixir
200 | defmodule PhoenixApp.SomeApiResourceController do
201 | use PhoenixApp.Web, :controller
202 | alias PhoenixApp.Session
203 |
204 | def index(conn, _params) do
205 | IO.inspect conn.private.plug_session
206 | user_id = Session.current_user(conn)
207 |
208 | conn
209 | |> assign(:user_id, user_id)
210 | |> render("index.html")
211 | end
212 |
213 | plug :verify_session
214 |
215 | # Future refinements could extract this into its own Plug file.
216 | defp verify_session(conn, _) do
217 | case Session.logged_in?(conn) do
218 | false -> conn |> send_resp(401, "Unauthorized") |> halt
219 | _ -> conn
220 | end
221 | end
222 | end
223 | ```
224 |
225 | Finally, we implement some nice helpers for your APIs:
226 |
227 | ```elixir
228 | # web/web.ex
229 |
230 | def view do
231 | quote do
232 | # snip
233 | import PhoenixApp.Session
234 | end
235 | end
236 | ```
237 |
238 | This gives you the ability to call `logged_in?(@conn)` and `current_user(@conn)` from within your views, should you desire to.
239 |
240 | ### Step 6: Fetching additional information from the backend
241 |
242 | Let's enhance our `Session` module with the capability to fetch additional information from another resource.
243 |
244 | In this case, we'll model a call an external User API to fetch extended data about the User, potentially with some sensitive information (that's why we didn't want to serialize it into the session).
245 |
246 | ```elixir
247 | # web/models/user.ex
248 | defmodule PhoenixApp.User do
249 | # Gets some user identity information like email, avatar image.
250 | # For this example, we'll use a random user generator.
251 | #
252 | # This example hits an API, but this could just as easily be something that hits
253 | # the database, or Redis, or some cache.
254 | def fetch(user_id) do
255 | %{ body: body } = HTTPotion.get("https://randomuser.me/api?seed=#{user_id}")
256 | [result | _ ] = body |> Poison.decode! |> Map.get("results")
257 | result
258 | end
259 | end
260 | ```
261 |
262 | Now our `Session` can be extended to return the proper `User`, which may provide more utility to us as we implement our Phoenix feature.
263 |
264 | ```elixir
265 | defmodule PhoenixApp.Session do
266 | use PhoenixApp.Web, :controller
267 | alias PhoenixApp.User
268 |
269 | def current_user(conn) do
270 | case load_user(conn) do
271 | # Changed current_user/1 to now return a User or a nil.
272 | {:ok, user_id} -> user_id |> User.fetch
273 | {:error, :not_found} -> nil
274 | end
275 | end
276 |
277 | # snip
278 | end
279 | ```
280 |
281 | #### Here's the two apps in action:
282 |
283 | 
284 |
285 | ### Heroku deployment gotchas
286 |
287 | If you are deploying this to Heroku with the popular [Heroku Elixir buildpack](git@github.com:HashNuke/heroku-buildpack-elixir.git), please be aware that adding or changing environment variables that are required at build time require that the new environment variables outlined here are added to your `elixir_buildpack.config` file in your repository.
288 |
289 | ```elixir
290 | # elixir_buildpack.config
291 | config_vars_to_export=(SECRET_KEY_BASE SESSION_ENCRYPTED_COOKIE_SALT SESSION_ENCRYPTED_SIGNED_COOKIE_SALT DOMAIN)
292 | ```
293 |
294 | ### Caveats and considerations
295 |
296 | #### CSRF incompatibilites
297 |
298 | At the time of this writing, Phoenix and Rails overwrite each others' session CSRF tokens with incompatible token schemes. This means that you are not able to make remote POST or PUT requests across the apps with CSRF protection turned on. Our current approach will work best with a read-only API, at the moment.
299 |
300 | #### Be judicious about what you store in a cookie
301 |
302 | Cookies themselves have their own strengths and drawbacks. We should note that you should be judicious about the amount of [data you store in a session](http://guides.rubyonrails.org/security.html#replay-attacks-for-cookiestore-sessions) (hint: only the bare minimum, and nothing sensitive).
303 |
304 | The OWASP guidelines also provide some [general security practices around cookie session storage](https://www.owasp.org/index.php/Session_Management_Cheat_Sheet).
305 |
306 | #### Moving beyond session sharing
307 |
308 | Even though this scheme may work in the short run, coupling our apps at this level in the long run will result in headaches as the apps are coupled to intricate session implementation details. If, in the long run, you wanted to continue scaling out your Phoenix app ecosystem, you may want to look into the following authentication patterns, both of which move your system toward a microservices architecture.
309 |
310 | 1) Develop an [API gateway](http://microservices.io/patterns/apigateway.html) whose purpose is to be the browser's buffer to your internal service architecture. This one gateway is responsible for identity access and control, decrypting session data and proxying requests to an umbrella of internal services (which may be Rails or Phoenix). Internal services may receive user identities in unencrypted form.
311 |
312 | 2) Consider implementing a [JWT token implementation](https://jwt.io/) across your apps, in which [all session and authorization claims are stored in the token itself, and encrypted in the client and server.](https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/). This scheme may still rely on cookies (you may store the token in a cookie, or pass it around in an HTTP header). The benefits of this scheme is the ability for your app(s) to manage identity and authentication claims on their own without having to verify against a third party. Drawbacks of this scheme are [the difficulty around revoking or expiring sessions](http://blog.prevoty.com/does-jwt-put-your-web-app-at-risk).
313 |
314 | Each of these approaches is not without overhead and complexity; be sure to do your homework before your proceed.
315 |
316 | ### Conclusion
317 |
318 | That's it! I hope I've illustrated a quick and easy way to get a working Phoenix app sharing sessions with Rails app(s), should you decide to prototype one in your existing system. I've also pushed up a [sample app if you want to cross-reference the code](https://github.com/andrewhao/sample-rails-phoenix-shared-sessions/). Good luck!
319 |
--------------------------------------------------------------------------------