├── chapter_2 ├── bacon │ ├── lib │ │ └── bacon.rb │ └── spec │ │ └── bacon_spec.rb ├── bacon_test.rb └── example_test.rb ├── things_i_bought ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ ├── application.js │ │ │ └── purchases.coffee │ │ └── stylesheets │ │ │ ├── application.css │ │ │ ├── purchases.scss │ │ │ └── scaffolds.scss │ ├── controllers │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ └── purchases_controller.rb │ ├── helpers │ │ ├── application_helper.rb │ │ └── purchases_helper.rb │ ├── mailers │ │ └── .keep │ ├── models │ │ ├── .keep │ │ ├── concerns │ │ │ └── .keep │ │ └── purchase.rb │ └── views │ │ ├── layouts │ │ └── application.html.erb │ │ └── purchases │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder ├── bin │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ └── spring ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── routes.rb │ └── secrets.yml ├── db │ ├── migrate │ │ └── 20150311101434_create_purchases.rb │ ├── schema.rb │ └── seeds.rb ├── lib │ ├── assets │ │ └── .keep │ └── tasks │ │ └── .keep ├── log │ └── .keep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── favicon.ico │ └── robots.txt ├── test │ ├── controllers │ │ ├── .keep │ │ └── purchases_controller_test.rb │ ├── fixtures │ │ ├── .keep │ │ └── purchases.yml │ ├── helpers │ │ └── .keep │ ├── integration │ │ └── .keep │ ├── mailers │ │ └── .keep │ ├── models │ │ ├── .keep │ │ └── purchase_test.rb │ └── test_helper.rb └── vendor │ └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── ticketee ├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── Procfile ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ ├── admin │ │ │ │ ├── application.coffee │ │ │ │ ├── projects.coffee │ │ │ │ ├── states.coffee │ │ │ │ └── users.coffee │ │ │ ├── api │ │ │ │ └── tickets.coffee │ │ │ ├── application.js │ │ │ ├── attachments.coffee │ │ │ ├── comments.coffee │ │ │ ├── projects.coffee │ │ │ ├── tags.coffee │ │ │ └── tickets.coffee │ │ └── stylesheets │ │ │ ├── admin │ │ │ ├── application.scss │ │ │ ├── projects.scss │ │ │ ├── states.scss │ │ │ └── users.scss │ │ │ ├── api │ │ │ └── tickets.scss │ │ │ ├── application.css.scss │ │ │ ├── attachments.scss │ │ │ ├── comments.scss │ │ │ ├── projects.scss │ │ │ ├── responsive.scss │ │ │ ├── tags.scss │ │ │ └── tickets.scss │ ├── controllers │ │ ├── admin │ │ │ ├── application_controller.rb │ │ │ ├── projects_controller.rb │ │ │ ├── states_controller.rb │ │ │ └── users_controller.rb │ │ ├── api │ │ │ ├── application_controller.rb │ │ │ ├── tickets_controller.rb │ │ │ └── v2 │ │ │ │ └── tickets.rb │ │ ├── application_controller.rb │ │ ├── attachments_controller.rb │ │ ├── comments_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── projects_controller.rb │ │ ├── tags_controller.rb │ │ └── tickets_controller.rb │ ├── helpers │ │ ├── admin │ │ │ ├── application_helper.rb │ │ │ ├── projects_helper.rb │ │ │ ├── states_helper.rb │ │ │ └── users_helper.rb │ │ ├── api │ │ │ └── tickets_helper.rb │ │ ├── application_helper.rb │ │ ├── attachments_helper.rb │ │ ├── comments_helper.rb │ │ ├── projects_helper.rb │ │ ├── tags_helper.rb │ │ └── tickets_helper.rb │ ├── mailers │ │ ├── .keep │ │ ├── application_mailer.rb │ │ └── comment_notifier.rb │ ├── models │ │ ├── .keep │ │ ├── attachment.rb │ │ ├── comment.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── project.rb │ │ ├── role.rb │ │ ├── state.rb │ │ ├── tag.rb │ │ ├── ticket.rb │ │ └── user.rb │ ├── policies │ │ ├── application_policy.rb │ │ ├── attachment_policy.rb │ │ ├── comment_policy.rb │ │ ├── project_policy.rb │ │ └── ticket_policy.rb │ ├── serializers │ │ └── ticket_serializer.rb │ ├── services │ │ └── comment_creator.rb │ ├── uploaders │ │ └── attachment_uploader.rb │ └── views │ │ ├── admin │ │ ├── application │ │ │ └── index.html.erb │ │ ├── projects │ │ │ ├── _form.html.erb │ │ │ └── new.html.erb │ │ ├── states │ │ │ ├── _form.html.erb │ │ │ ├── index.html.erb │ │ │ └── new.html.erb │ │ └── users │ │ │ ├── _form.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── index.html.erb │ │ │ ├── new.html.erb │ │ │ └── show.html.erb │ │ ├── attachments │ │ ├── _form.html.erb │ │ └── new.html.erb │ │ ├── comment_notifier │ │ └── created.text.erb │ │ ├── comments │ │ ├── _comment.html.erb │ │ └── _form.html.erb │ │ ├── devise │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── mailer │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── reset_password_instructions.html.erb │ │ │ └── unlock_instructions.html.erb │ │ ├── passwords │ │ │ ├── edit.html.erb │ │ │ └── new.html.erb │ │ ├── registrations │ │ │ ├── edit.html.erb │ │ │ └── new.html.erb │ │ ├── sessions │ │ │ └── new.html.erb │ │ ├── shared │ │ │ └── _links.html.erb │ │ └── unlocks │ │ │ └── new.html.erb │ │ ├── layouts │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ │ ├── projects │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ └── show.html.erb │ │ ├── states │ │ └── _state.html.erb │ │ ├── tags │ │ ├── _form.html.erb │ │ └── _tag.html.erb │ │ └── tickets │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb ├── bin │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ └── spring ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── carrierwave.rb │ │ ├── cookies_serializer.rb │ │ ├── devise.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── session_store.rb │ │ ├── simple_form.rb │ │ ├── simple_form_bootstrap.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ ├── devise.en.yml │ │ ├── en.yml │ │ └── simple_form.en.yml │ ├── routes.rb │ └── secrets.yml ├── db │ ├── migrate │ │ ├── 20150314100956_create_projects.rb │ │ ├── 20150318033648_create_tickets.rb │ │ ├── 20150321110052_devise_create_users.rb │ │ ├── 20150322065031_add_author_to_tickets.rb │ │ ├── 20150322084252_add_admin_to_users.rb │ │ ├── 20150323072600_add_archived_at_to_users.rb │ │ ├── 20150325092856_create_roles.rb │ │ ├── 20150401104001_add_attachment_to_tickets.rb │ │ ├── 20150402090612_create_attachments.rb │ │ ├── 20150402090619_remove_attachment_from_tickets.rb │ │ ├── 20150403021520_create_comments.rb │ │ ├── 20150403044120_create_states.rb │ │ ├── 20150403070314_add_previous_state_to_comments.rb │ │ ├── 20150403084623_add_default_to_states.rb │ │ ├── 20150404113718_create_tags.rb │ │ ├── 20150404113820_create_join_table_tags_tickets.rb │ │ ├── 20150405052036_create_join_table_ticket_watchers.rb │ │ └── 20150406055658_add_api_key_to_users.rb │ ├── schema.rb │ └── seeds.rb ├── lib │ ├── assets │ │ └── .keep │ ├── heartbeat │ │ ├── application.rb │ │ ├── config.ru │ │ └── test_application.rb │ ├── link_jumbler.rb │ ├── tasks │ │ └── .keep │ └── templates │ │ └── erb │ │ └── scaffold │ │ └── _form.html.erb ├── log │ └── .keep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── favicon.ico │ └── robots.txt ├── spec │ ├── controllers │ │ ├── admin │ │ │ ├── application_controller_spec.rb │ │ │ ├── projects_controller_spec.rb │ │ │ └── states_controller_spec.rb │ │ ├── api │ │ │ └── tickets_controller_spec.rb │ │ ├── attachments_controller_spec.rb │ │ ├── comments_controller_spec.rb │ │ ├── projects_controller_spec.rb │ │ ├── tags_controller_spec.rb │ │ └── tickets_controller_spec.rb │ ├── factories │ │ ├── attachment_factory.rb │ │ ├── comment_factory.rb │ │ ├── project_factory.rb │ │ ├── state_factory.rb │ │ ├── ticket_factory.rb │ │ └── user_factory.rb │ ├── features │ │ ├── admin │ │ │ ├── archiving_users_spec.rb │ │ │ ├── creating_projects_spec.rb │ │ │ ├── creating_states_spec.rb │ │ │ ├── creating_users_spec.rb │ │ │ ├── deleting_projects_spec.rb │ │ │ ├── editing_users_spec.rb │ │ │ ├── managing_roles_spec.rb │ │ │ └── managing_states_spec.rb │ │ ├── creating_comments_spec.rb │ │ ├── creating_tickets_spec.rb │ │ ├── deleting_tags_spec.rb │ │ ├── deleting_tickets_spec.rb │ │ ├── editing_projects_spec.rb │ │ ├── editing_tickets_spec.rb │ │ ├── hidden_links_spec.rb │ │ ├── searching_spec.rb │ │ ├── signing_in_spec.rb │ │ ├── signing_out_spec.rb │ │ ├── signing_up_spec.rb │ │ ├── ticket_notifications_spec.rb │ │ ├── viewing_attachments_spec.rb │ │ ├── viewing_projects_spec.rb │ │ ├── viewing_tickets_spec.rb │ │ └── watching_tickets_spec.rb │ ├── fixtures │ │ ├── gradient.txt │ │ ├── speed.txt │ │ └── spin.txt │ ├── mailers │ │ ├── comment_notifier_spec.rb │ │ └── previews │ │ │ └── comment_notifier_preview.rb │ ├── policies │ │ ├── attachment_policy_spec.rb │ │ ├── comment_policy_spec.rb │ │ ├── project_policy_spec.rb │ │ └── ticket_policy_spec.rb │ ├── rails_helper.rb │ ├── requests │ │ └── api │ │ │ ├── tickets_spec.rb │ │ │ └── v2 │ │ │ └── tickets_spec.rb │ ├── spec_helper.rb │ └── support │ │ ├── authorization_helpers.rb │ │ ├── capybara_finders.rb │ │ ├── capybara_matchers.rb │ │ ├── database_cleaning.rb │ │ ├── email_spec.rb │ │ └── pundit_matcher.rb └── vendor │ └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep └── ticketee_api └── tickets.rb /chapter_2/bacon/lib/bacon.rb: -------------------------------------------------------------------------------- 1 | class Bacon 2 | attr_accessor :expired 3 | 4 | def edible? 5 | !expired 6 | end 7 | 8 | def expired! 9 | self.expired = true 10 | end 11 | end -------------------------------------------------------------------------------- /chapter_2/bacon/spec/bacon_spec.rb: -------------------------------------------------------------------------------- 1 | require "bacon" 2 | 3 | RSpec.describe Bacon do 4 | it "is edible" do 5 | expect(Bacon.new.edible?).to be(true) 6 | end 7 | 8 | it "can expire" do 9 | bacon = Bacon.new 10 | bacon.expired! 11 | expect(bacon).to_not be_edible 12 | end 13 | end -------------------------------------------------------------------------------- /chapter_2/bacon_test.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | 3 | class Bacon 4 | def self.saved? 5 | false 6 | end 7 | end 8 | 9 | class BaconTest < Minitest::Test 10 | def test_saved 11 | assert Bacon.saved?, "Our bacon was not saved :(" 12 | end 13 | end -------------------------------------------------------------------------------- /chapter_2/example_test.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | 3 | class ExampleTest < Minitest::Test 4 | def truth 5 | assert true 6 | end 7 | end -------------------------------------------------------------------------------- /things_i_bought/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | !/log/.keep 17 | /tmp 18 | -------------------------------------------------------------------------------- /things_i_bought/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '4.2.0' 6 | # Use sqlite3 as the database for Active Record 7 | gem 'sqlite3' 8 | # Use SCSS for stylesheets 9 | gem 'sass-rails', '~> 5.0' 10 | # Use Uglifier as compressor for JavaScript assets 11 | gem 'uglifier', '>= 1.3.0' 12 | # Use CoffeeScript for .coffee assets and views 13 | gem 'coffee-rails', '~> 4.1.0' 14 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 15 | # gem 'therubyracer', platforms: :ruby 16 | 17 | # Use jquery as the JavaScript library 18 | gem 'jquery-rails' 19 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 20 | gem 'turbolinks' 21 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 22 | gem 'jbuilder', '~> 2.0' 23 | # bundle exec rake doc:rails generates the API under doc/api. 24 | gem 'sdoc', '~> 0.4.0', group: :doc 25 | 26 | # Use ActiveModel has_secure_password 27 | # gem 'bcrypt', '~> 3.1.7' 28 | 29 | # Use Unicorn as the app server 30 | # gem 'unicorn' 31 | 32 | # Use Capistrano for deployment 33 | # gem 'capistrano-rails', group: :development 34 | 35 | group :development, :test do 36 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 37 | gem 'byebug' 38 | 39 | # Access an IRB console on exception pages or by using <%= console %> in views 40 | gem 'web-console', '~> 2.0' 41 | 42 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 43 | gem 'spring' 44 | end 45 | 46 | -------------------------------------------------------------------------------- /things_i_bought/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /things_i_bought/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /things_i_bought/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/app/assets/images/.keep -------------------------------------------------------------------------------- /things_i_bought/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /things_i_bought/app/assets/javascripts/purchases.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /things_i_bought/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /things_i_bought/app/assets/stylesheets/purchases.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the purchases controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /things_i_bought/app/assets/stylesheets/scaffolds.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | p, ol, ul, td { 10 | font-family: verdana, arial, helvetica, sans-serif; 11 | font-size: 13px; 12 | line-height: 18px; 13 | } 14 | 15 | pre { 16 | background-color: #eee; 17 | padding: 10px; 18 | font-size: 11px; 19 | } 20 | 21 | a { 22 | color: #000; 23 | &:visited { 24 | color: #666; 25 | } 26 | &:hover { 27 | color: #fff; 28 | background-color: #000; 29 | } 30 | } 31 | 32 | div { 33 | &.field, &.actions { 34 | margin-bottom: 10px; 35 | } 36 | } 37 | 38 | #notice { 39 | color: green; 40 | } 41 | 42 | .field_with_errors { 43 | padding: 2px; 44 | background-color: red; 45 | display: table; 46 | } 47 | 48 | #error_explanation { 49 | width: 450px; 50 | border: 2px solid red; 51 | padding: 7px; 52 | padding-bottom: 0; 53 | margin-bottom: 20px; 54 | background-color: #f0f0f0; 55 | h2 { 56 | text-align: left; 57 | font-weight: bold; 58 | padding: 5px 5px 5px 15px; 59 | font-size: 12px; 60 | margin: -7px; 61 | margin-bottom: 0px; 62 | background-color: #c00; 63 | color: #fff; 64 | } 65 | ul li { 66 | font-size: 12px; 67 | list-style: square; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /things_i_bought/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /things_i_bought/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /things_i_bought/app/controllers/purchases_controller.rb: -------------------------------------------------------------------------------- 1 | class PurchasesController < ApplicationController 2 | before_action :set_purchase, only: [:show, :edit, :update, :destroy] 3 | 4 | # GET /purchases 5 | # GET /purchases.json 6 | def index 7 | @purchases = Purchase.all 8 | end 9 | 10 | # GET /purchases/1 11 | # GET /purchases/1.json 12 | def show 13 | end 14 | 15 | # GET /purchases/new 16 | def new 17 | @purchase = Purchase.new 18 | end 19 | 20 | # GET /purchases/1/edit 21 | def edit 22 | end 23 | 24 | # POST /purchases 25 | # POST /purchases.json 26 | def create 27 | @purchase = Purchase.new(purchase_params) 28 | 29 | respond_to do |format| 30 | if @purchase.save 31 | format.html { redirect_to @purchase, notice: 'Purchase was successfully created.' } 32 | format.json { render :show, status: :created, location: @purchase } 33 | else 34 | format.html { render :new } 35 | format.json { render json: @purchase.errors, status: :unprocessable_entity } 36 | end 37 | end 38 | end 39 | 40 | # PATCH/PUT /purchases/1 41 | # PATCH/PUT /purchases/1.json 42 | def update 43 | respond_to do |format| 44 | if @purchase.update(purchase_params) 45 | format.html { redirect_to @purchase, notice: 'Purchase was successfully updated.' } 46 | format.json { render :show, status: :ok, location: @purchase } 47 | else 48 | format.html { render :edit } 49 | format.json { render json: @purchase.errors, status: :unprocessable_entity } 50 | end 51 | end 52 | end 53 | 54 | # DELETE /purchases/1 55 | # DELETE /purchases/1.json 56 | def destroy 57 | @purchase.destroy 58 | respond_to do |format| 59 | format.html { redirect_to purchases_url, notice: 'Purchase was successfully destroyed.' } 60 | format.json { head :no_content } 61 | end 62 | end 63 | 64 | private 65 | # Use callbacks to share common setup or constraints between actions. 66 | def set_purchase 67 | @purchase = Purchase.find(params[:id]) 68 | end 69 | 70 | # Never trust parameters from the scary internet, only allow the white list through. 71 | def purchase_params 72 | params.require(:purchase).permit(:name, :cost) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /things_i_bought/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /things_i_bought/app/helpers/purchases_helper.rb: -------------------------------------------------------------------------------- 1 | module PurchasesHelper 2 | end 3 | -------------------------------------------------------------------------------- /things_i_bought/app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/app/mailers/.keep -------------------------------------------------------------------------------- /things_i_bought/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/app/models/.keep -------------------------------------------------------------------------------- /things_i_bought/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/app/models/concerns/.keep -------------------------------------------------------------------------------- /things_i_bought/app/models/purchase.rb: -------------------------------------------------------------------------------- 1 | class Purchase < ActiveRecord::Base 2 | validates :name, presence: true 3 | validates :cost, numericality: { greater_than: 0 } 4 | end -------------------------------------------------------------------------------- /things_i_bought/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ThingsIBought 5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /things_i_bought/app/views/purchases/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@purchase) do |f| %> 2 | <% if @purchase.errors.any? %> 3 |
4 |

<%= pluralize(@purchase.errors.count, "error") %> prohibited this purchase from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :name %>
16 | <%= f.text_field :name %> 17 |
18 |
19 | <%= f.label :cost %>
20 | <%= f.text_field :cost %> 21 |
22 |
23 | <%= f.submit %> 24 |
25 | <% end %> 26 | -------------------------------------------------------------------------------- /things_i_bought/app/views/purchases/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Purchase

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @purchase %> | 6 | <%= link_to 'Back', purchases_path %> 7 | -------------------------------------------------------------------------------- /things_i_bought/app/views/purchases/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

Listing Purchases

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% @purchases.each do |purchase| %> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <% end %> 24 | 25 |
NameCost
<%= purchase.name %><%= purchase.cost %><%= link_to 'Show', purchase %><%= link_to 'Edit', edit_purchase_path(purchase) %><%= link_to 'Destroy', purchase, method: :delete, data: { confirm: 'Are you sure?' } %>
26 | 27 |
28 | 29 | <%= link_to 'New Purchase', new_purchase_path %> 30 | -------------------------------------------------------------------------------- /things_i_bought/app/views/purchases/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@purchases) do |purchase| 2 | json.extract! purchase, :id, :name, :cost 3 | json.url purchase_url(purchase, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /things_i_bought/app/views/purchases/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Purchase

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', purchases_path %> 6 | -------------------------------------------------------------------------------- /things_i_bought/app/views/purchases/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | Name: 5 | <%= @purchase.name %> 6 |

7 | 8 |

9 | Cost: 10 | <%= @purchase.cost %> 11 |

12 | 13 | <%= link_to 'Edit', edit_purchase_path(@purchase) %> | 14 | <%= link_to 'Back', purchases_path %> 15 | -------------------------------------------------------------------------------- /things_i_bought/app/views/purchases/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @purchase, :id, :name, :cost, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /things_i_bought/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /things_i_bought/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require_relative '../config/boot' 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /things_i_bought/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require_relative '../config/boot' 7 | require 'rake' 8 | Rake.application.run 9 | -------------------------------------------------------------------------------- /things_i_bought/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /things_i_bought/bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) 11 | Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } 12 | gem "spring", match[1] 13 | require "spring/binstub" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /things_i_bought/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /things_i_bought/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module ThingsIBought 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | 23 | # Do not swallow errors in after_commit/after_rollback callbacks. 24 | config.active_record.raise_in_transactional_callbacks = true 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /things_i_bought/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /things_i_bought/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /things_i_bought/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /things_i_bought/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 and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /things_i_bought/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 static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /things_i_bought/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /things_i_bought/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /things_i_bought/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /things_i_bought/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /things_i_bought/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /things_i_bought/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /things_i_bought/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_things_i_bought_session' 4 | -------------------------------------------------------------------------------- /things_i_bought/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /things_i_bought/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /things_i_bought/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :purchases 3 | 4 | # The priority is based upon order of creation: first created -> highest priority. 5 | # See how all your routes lay out with "rake routes". 6 | 7 | # You can have the root of your site routed with "root" 8 | # root 'welcome#index' 9 | 10 | # Example of regular route: 11 | # get 'products/:id' => 'catalog#view' 12 | 13 | # Example of named route that can be invoked with purchase_url(id: product.id) 14 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase 15 | 16 | # Example resource route (maps HTTP verbs to controller actions automatically): 17 | # resources :products 18 | 19 | # Example resource route with options: 20 | # resources :products do 21 | # member do 22 | # get 'short' 23 | # post 'toggle' 24 | # end 25 | # 26 | # collection do 27 | # get 'sold' 28 | # end 29 | # end 30 | 31 | # Example resource route with sub-resources: 32 | # resources :products do 33 | # resources :comments, :sales 34 | # resource :seller 35 | # end 36 | 37 | # Example resource route with more complex sub-resources: 38 | # resources :products do 39 | # resources :comments 40 | # resources :sales do 41 | # get 'recent', on: :collection 42 | # end 43 | # end 44 | 45 | # Example resource route with concerns: 46 | # concern :toggleable do 47 | # post 'toggle' 48 | # end 49 | # resources :posts, concerns: :toggleable 50 | # resources :photos, concerns: :toggleable 51 | 52 | # Example resource route within a namespace: 53 | # namespace :admin do 54 | # # Directs /admin/products/* to Admin::ProductsController 55 | # # (app/controllers/admin/products_controller.rb) 56 | # resources :products 57 | # end 58 | end 59 | -------------------------------------------------------------------------------- /things_i_bought/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: fe00d7ed0bb367cf0c12c5db8577e47f883b00f7987e9c6638661bfd7ffdf767eead8e2c4cb13122e9e594128088bc144d003dda1ca223c587902aebe423231b 15 | 16 | test: 17 | secret_key_base: 8565490b69526ad344a52239671cc127e067d80f6ceaf655cd4fb8fa39d1c9102d90b86d337d0dd13c58580ef0a58e9578da30babfda13726563b6c03e111437 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /things_i_bought/db/migrate/20150311101434_create_purchases.rb: -------------------------------------------------------------------------------- 1 | class CreatePurchases < ActiveRecord::Migration 2 | def change 3 | create_table :purchases do |t| 4 | t.string :name 5 | t.decimal :cost 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /things_i_bought/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20150311101434) do 15 | 16 | create_table "purchases", force: :cascade do |t| 17 | t.string "name" 18 | t.decimal "cost" 19 | t.datetime "created_at", null: false 20 | t.datetime "updated_at", null: false 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /things_i_bought/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /things_i_bought/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/lib/assets/.keep -------------------------------------------------------------------------------- /things_i_bought/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/lib/tasks/.keep -------------------------------------------------------------------------------- /things_i_bought/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/log/.keep -------------------------------------------------------------------------------- /things_i_bought/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 | -------------------------------------------------------------------------------- /things_i_bought/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 | -------------------------------------------------------------------------------- /things_i_bought/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /things_i_bought/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/public/favicon.ico -------------------------------------------------------------------------------- /things_i_bought/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /things_i_bought/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/test/controllers/.keep -------------------------------------------------------------------------------- /things_i_bought/test/controllers/purchases_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PurchasesControllerTest < ActionController::TestCase 4 | setup do 5 | @purchase = purchases(:one) 6 | end 7 | 8 | test "should get index" do 9 | get :index 10 | assert_response :success 11 | assert_not_nil assigns(:purchases) 12 | end 13 | 14 | test "should get new" do 15 | get :new 16 | assert_response :success 17 | end 18 | 19 | test "should create purchase" do 20 | assert_difference('Purchase.count') do 21 | post :create, purchase: { cost: @purchase.cost, name: @purchase.name } 22 | end 23 | 24 | assert_redirected_to purchase_path(assigns(:purchase)) 25 | end 26 | 27 | test "should show purchase" do 28 | get :show, id: @purchase 29 | assert_response :success 30 | end 31 | 32 | test "should get edit" do 33 | get :edit, id: @purchase 34 | assert_response :success 35 | end 36 | 37 | test "should update purchase" do 38 | patch :update, id: @purchase, purchase: { cost: @purchase.cost, name: @purchase.name } 39 | assert_redirected_to purchase_path(assigns(:purchase)) 40 | end 41 | 42 | test "should destroy purchase" do 43 | assert_difference('Purchase.count', -1) do 44 | delete :destroy, id: @purchase 45 | end 46 | 47 | assert_redirected_to purchases_path 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /things_i_bought/test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/test/fixtures/.keep -------------------------------------------------------------------------------- /things_i_bought/test/fixtures/purchases.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: MyString 5 | cost: 9.99 6 | 7 | two: 8 | name: MyString 9 | cost: 9.99 10 | -------------------------------------------------------------------------------- /things_i_bought/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/test/helpers/.keep -------------------------------------------------------------------------------- /things_i_bought/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/test/integration/.keep -------------------------------------------------------------------------------- /things_i_bought/test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/test/mailers/.keep -------------------------------------------------------------------------------- /things_i_bought/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/test/models/.keep -------------------------------------------------------------------------------- /things_i_bought/test/models/purchase_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PurchaseTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /things_i_bought/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 7 | fixtures :all 8 | 9 | # Add more helper methods to be used by all tests here... 10 | end 11 | -------------------------------------------------------------------------------- /things_i_bought/vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /things_i_bought/vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/things_i_bought/vendor/assets/stylesheets/.keep -------------------------------------------------------------------------------- /ticketee/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | !/log/.keep 17 | /tmp 18 | 19 | uploads 20 | -------------------------------------------------------------------------------- /ticketee/.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /ticketee/.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 2.2.1 3 | script: rspec 4 | before_install: 5 | - export DISPLAY=:99.0 6 | - sh -e /etc/init.d/xvfb start 7 | deploy: 8 | provider: heroku 9 | api_key: 10 | secure: Q5efxQBnGeFY1wDm0pTpZaQKwRofw4jwCj7C4U5/ylzToeVq06O2UOQdcJc6iyJT/KGQ48OgU8ksFls61+f8aDqB8iFwvaTcWqDIBtgCsS8sA+YH57ceWT7Z2P/4tW0Y0zsPOnII240TMB+hp1bnwwMXCpz80IHMQmXtgE0IhoM= 11 | app: r4ia-ticketee 12 | on: 13 | repo: rubysherpas/r4ia_examples 14 | -------------------------------------------------------------------------------- /ticketee/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby "2.2.1" 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '4.2.1' 6 | 7 | gem "sqlite3", group: [:development, :test] 8 | gem "pg", group: :production 9 | 10 | # Use SCSS for stylesheets 11 | gem 'sass-rails', '~> 5.0' 12 | # Use Uglifier as compressor for JavaScript assets 13 | gem 'uglifier', '>= 1.3.0' 14 | # Use CoffeeScript for .coffee assets and views 15 | gem 'coffee-rails', '~> 4.1.0' 16 | # See https://github.com/rails/execjs#readme for more supported runtimes 17 | # gem 'therubyracer', platforms: :ruby 18 | 19 | # Use jquery as the JavaScript library 20 | gem 'jquery-rails' 21 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 22 | gem 'jbuilder', '~> 2.0' 23 | # bundle exec rake doc:rails generates the API under doc/api. 24 | gem 'sdoc', '~> 0.4.0', group: :doc 25 | 26 | # Use ActiveModel has_secure_password 27 | # gem 'bcrypt', '~> 3.1.7' 28 | 29 | # Use Unicorn as the app server 30 | # gem 'unicorn' 31 | 32 | # Use Capistrano for deployment 33 | # gem 'capistrano-rails', group: :development 34 | 35 | gem "bootstrap-sass", "~> 3.3" 36 | gem "font-awesome-rails", "~> 4.3" 37 | gem "simple_form", "~> 3.1.0" 38 | gem "devise", "~> 3.4.1" 39 | gem "pundit", "~> 0.3.0" 40 | gem "searcher", github: "radar/searcher" 41 | gem "active_model_serializers", "~> 0.9.3" 42 | 43 | gem "carrierwave", "~> 0.10.0" 44 | gem "fog", "~> 1.29.0" 45 | gem "rails_12factor", group: :production 46 | gem "puma", group: :production 47 | 48 | gem "sinatra" 49 | 50 | group :development, :test do 51 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 52 | gem 'byebug' 53 | 54 | # Access an IRB console on exception pages or by using <%= console %> in views 55 | gem 'web-console', '~> 2.0' 56 | 57 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 58 | gem 'spring' 59 | 60 | gem "rspec-rails", "~> 3.2.1" 61 | end 62 | 63 | group :test do 64 | gem "capybara", "~> 2.4" 65 | gem "factory_girl_rails", "~> 4.5" 66 | gem "selenium-webdriver", "~> 2.45" 67 | gem "database_cleaner", "~> 1.4" 68 | gem "email_spec", "~> 1.6.0" 69 | end 70 | -------------------------------------------------------------------------------- /ticketee/Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development} 2 | -------------------------------------------------------------------------------- /ticketee/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /ticketee/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /ticketee/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/app/assets/images/.keep -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/admin/application.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/admin/projects.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/admin/states.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/admin/users.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/api/tickets.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require bootstrap-sprockets 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/attachments.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/comments.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/projects.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/tags.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | 5 | $ -> 6 | $(".tag .remove").on "ajax:success", -> 7 | $(this).parent().fadeOut() 8 | -------------------------------------------------------------------------------- /ticketee/app/assets/javascripts/tickets.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | 5 | $ -> 6 | $("#add_file").on "ajax:success", (event, data) -> 7 | $("#attachments").append data 8 | $(this).data "params", { index: $("#attachments div.file").length } 9 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/admin/application.scss: -------------------------------------------------------------------------------- 1 | #admin .col-md-3 h2 { 2 | font-size: 13px; 3 | letter-spacing: 1px; 4 | text-transform: uppercase; 5 | font-weight: bold; 6 | color: #959595; 7 | padding-left: 15px; 8 | } 9 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/admin/projects.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the admin/projects controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/admin/states.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the admin/states controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/admin/users.scss: -------------------------------------------------------------------------------- 1 | .roles { 2 | @extend .table; 3 | @extend .form-horizontal; 4 | 5 | tr:first-child { 6 | td, th { 7 | border-top: 0px; 8 | } 9 | } 10 | 11 | label { 12 | @extend .control-label; 13 | } 14 | 15 | select { 16 | @extend .form-control; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/api/tickets.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the api/tickets controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap-sprockets"; 2 | @import "bootstrap"; 3 | @import "font-awesome"; 4 | @import "projects"; 5 | @import "tickets"; 6 | @import "admin/application"; 7 | @import "admin/users"; 8 | @import "responsive"; 9 | 10 | header { 11 | @extend .page-header; 12 | position: relative; 13 | padding-bottom: 0px; 14 | 15 | &:first-child { 16 | margin-top: 20px; 17 | } 18 | } 19 | 20 | ul.actions { 21 | @extend .list-unstyled; 22 | @extend .list-inline; 23 | } 24 | 25 | a.new, a.edit, a.delete { 26 | @extend .btn; 27 | 28 | &:before { 29 | font-family: "FontAwesome"; 30 | padding-right: 0.5em; 31 | } 32 | } 33 | 34 | a.new { 35 | @extend .btn-success; 36 | 37 | &:before { 38 | @extend .fa-plus; 39 | } 40 | } 41 | 42 | a.edit { 43 | @extend .btn-primary; 44 | 45 | &:before { 46 | @extend .fa-pencil; 47 | } 48 | } 49 | 50 | a.delete { 51 | @extend .btn-danger; 52 | 53 | &:before { 54 | @extend .fa-trash; 55 | } 56 | } 57 | 58 | .alert-notice { 59 | @extend .alert-success; 60 | } 61 | 62 | .alert-alert { 63 | @extend .alert-danger; 64 | } 65 | 66 | form { 67 | max-width: 500px; 68 | } 69 | 70 | body { 71 | padding-top: 70px; 72 | } 73 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/attachments.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the attachments controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/comments.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the comments controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/projects.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the projects controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | #projects h2 { 6 | font-size: 16px; 7 | font-weight: bold; 8 | margin: 20px 0px 0px; 9 | } -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/responsive.scss: -------------------------------------------------------------------------------- 1 | @media(min-width: $screen-sm-min) { 2 | header { 3 | h1, h2, h3, h4, h5, h6 { 4 | max-width: 55%; 5 | } 6 | } 7 | 8 | ul.actions { 9 | position: absolute; 10 | bottom: -2px; 11 | right: 2px; 12 | max-width: 45%; 13 | text-align: right; 14 | } 15 | } 16 | 17 | @media(max-width: $screen-xs-max) { 18 | input#search { 19 | display: inline-block; 20 | vertical-align: middle; 21 | width: auto; 22 | } 23 | } 24 | 25 | @media(max-width: $screen-sm-max) { 26 | ul.actions .form-inline { 27 | padding-bottom: 5px; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/tags.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the tags controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /ticketee/app/assets/stylesheets/tickets.scss: -------------------------------------------------------------------------------- 1 | #attributes { 2 | @extend .table; 3 | @extend .table-condensed; 4 | width: 65%; 5 | 6 | th { 7 | width: 35%; 8 | } 9 | 10 | tr:first-child { 11 | td, th { 12 | border-top: 0px; 13 | } 14 | } 15 | 16 | td, th { 17 | line-height: 24px !important; 18 | } 19 | } 20 | 21 | blockquote.comment p { 22 | font-size: 14px; 23 | } 24 | 25 | .state { 26 | @extend .label; 27 | font-size: 12px; 28 | padding: 0.3em 0.6em; 29 | } 30 | 31 | #tickets li, #states li { 32 | padding-bottom: 10px; 33 | } 34 | 35 | .tag { 36 | @extend .state; 37 | @extend .label-info; 38 | margin-right: 10px; 39 | 40 | a { 41 | color: white; 42 | 43 | &.remove { 44 | font-family: "FontAwesome"; 45 | @extend .fa-close; 46 | margin-right: 0.5em; 47 | text-decoration: none; 48 | } 49 | } 50 | } 51 | 52 | .watch, .unwatch { 53 | @extend .btn; 54 | @extend .btn-xs; 55 | font-weight: bold; 56 | 57 | &:before { 58 | font-family: "FontAwesome"; 59 | padding-right: 0.5em; 60 | } 61 | } 62 | 63 | .watch { 64 | @extend .btn-success; 65 | 66 | &:before { 67 | @extend .fa-eye; 68 | } 69 | } 70 | 71 | .unwatch { 72 | @extend .btn-danger; 73 | 74 | &:before { 75 | @extend .fa-eye-slash; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ticketee/app/controllers/admin/application_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ApplicationController < ApplicationController 2 | skip_after_action :verify_authorized, :verify_policy_scoped 3 | before_action :authorize_admin! 4 | 5 | def index 6 | end 7 | 8 | private 9 | 10 | def authorize_admin! 11 | authenticate_user! 12 | 13 | unless current_user.admin? 14 | redirect_to root_path, alert: "You must be an admin to do that." 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /ticketee/app/controllers/admin/projects_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ProjectsController < Admin::ApplicationController 2 | def new 3 | @project = Project.new 4 | end 5 | 6 | def create 7 | @project = Project.new(project_params) 8 | 9 | if @project.save 10 | flash[:notice] = "Project has been created." 11 | redirect_to @project 12 | else 13 | flash.now[:alert] = "Project has not been created." 14 | render "new" 15 | end 16 | end 17 | 18 | def destroy 19 | @project = Project.find(params[:id]) 20 | @project.destroy 21 | 22 | flash[:notice] = "Project has been deleted." 23 | redirect_to projects_path 24 | end 25 | 26 | private 27 | 28 | def project_params 29 | params.require(:project).permit(:name, :description) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /ticketee/app/controllers/admin/states_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::StatesController < Admin::ApplicationController 2 | def index 3 | @states = State.all 4 | end 5 | 6 | def new 7 | @state = State.new 8 | end 9 | 10 | def create 11 | @state = State.new(state_params) 12 | if @state.save 13 | flash[:notice] = "State has been created." 14 | redirect_to admin_states_path 15 | else 16 | flash.now[:alert] = "State has not been created." 17 | render "new" 18 | end 19 | end 20 | 21 | def make_default 22 | @state = State.find(params[:id]) 23 | @state.make_default! 24 | 25 | flash[:notice] = "'#{@state.name}' is now the default state." 26 | redirect_to admin_states_path 27 | end 28 | 29 | private 30 | 31 | def state_params 32 | params.require(:state).permit(:name, :color) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /ticketee/app/controllers/admin/users_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::UsersController < Admin::ApplicationController 2 | before_action :set_projects, only: [:new, :create, :edit, :update] 3 | before_action :set_user, only: [:show, :edit, :update, :archive] 4 | 5 | def index 6 | @users = User.excluding_archived.order(:email) 7 | end 8 | 9 | def show 10 | end 11 | 12 | def new 13 | @user = User.new 14 | end 15 | 16 | def create 17 | @user = User.new(user_params) 18 | build_roles_for(@user) 19 | 20 | if @user.save 21 | flash[:notice] = "User has been created." 22 | redirect_to admin_users_path 23 | else 24 | flash.now[:alert] = "User has not been created." 25 | render "new" 26 | end 27 | end 28 | 29 | def edit 30 | end 31 | 32 | def update 33 | if params[:user][:password].blank? 34 | params[:user].delete(:password) 35 | end 36 | 37 | User.transaction do 38 | @user.roles.clear 39 | build_roles_for(@user) 40 | 41 | if @user.update(user_params) 42 | flash[:notice] = "User has been updated." 43 | redirect_to admin_users_path 44 | else 45 | flash.now[:alert] = "User has not been updated." 46 | render "edit" 47 | raise ActiveRecord::Rollback 48 | end 49 | end 50 | end 51 | 52 | def archive 53 | if @user == current_user 54 | flash[:alert] = "You cannot archive yourself!" 55 | else 56 | @user.archive 57 | flash[:notice] = "User has been archived." 58 | end 59 | 60 | redirect_to admin_users_path 61 | end 62 | 63 | private 64 | 65 | def set_projects 66 | @projects = Project.order(:name) 67 | end 68 | 69 | def build_roles_for(user) 70 | role_data = params.fetch(:roles, []) 71 | role_data.each do |project_id, role_name| 72 | if role_name.present? 73 | user.roles.build(project_id: project_id, role: role_name) 74 | end 75 | end 76 | end 77 | 78 | def user_params 79 | params.require(:user).permit(:email, :password, :admin) 80 | end 81 | 82 | def set_user 83 | @user = User.find(params[:id]) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /ticketee/app/controllers/api/application_controller.rb: -------------------------------------------------------------------------------- 1 | class API::ApplicationController < ApplicationController 2 | attr_reader :current_user 3 | before_action :authenticate_user 4 | 5 | private 6 | 7 | def authenticate_user 8 | authenticate_with_http_token do |token| 9 | @current_user = User.find_by(api_key: token) 10 | end 11 | 12 | if @current_user.nil? 13 | render json: { error: "Unauthorized" }, status: 401 14 | return 15 | end 16 | end 17 | 18 | def not_authorized 19 | render json: { error: "Unauthorized" }, status: 403 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /ticketee/app/controllers/api/tickets_controller.rb: -------------------------------------------------------------------------------- 1 | class API::TicketsController < API::ApplicationController 2 | attr_reader :current_user 3 | 4 | before_action :set_project 5 | 6 | def show 7 | @ticket = @project.tickets.find(params[:id]) 8 | authorize @ticket, :show? 9 | render json: @ticket 10 | end 11 | 12 | def create 13 | @ticket = @project.tickets.build(ticket_params) 14 | authorize @ticket, :create? 15 | if @ticket.save 16 | render json: @ticket, status: 201 17 | else 18 | render json: { errors: @ticket.errors.full_messages }, status: 422 19 | end 20 | end 21 | 22 | private 23 | 24 | def ticket_params 25 | params.require(:ticket).permit(:name, :description) 26 | end 27 | 28 | def set_project 29 | @project = Project.find(params[:project_id]) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /ticketee/app/controllers/api/v2/tickets.rb: -------------------------------------------------------------------------------- 1 | require "sinatra" 2 | 3 | module API 4 | module V2 5 | class Tickets < Sinatra::Base 6 | before do 7 | headers "Content-Type" => "text/json" 8 | set_user 9 | set_project 10 | end 11 | 12 | get "/:id" do 13 | ticket = @project.tickets.find(params[:id]) 14 | unless TicketPolicy.new(@user, ticket).show? 15 | halt 404, "The ticket you were looking for could not be found." 16 | end 17 | TicketSerializer.new(ticket).to_json 18 | end 19 | 20 | private 21 | 22 | def set_project 23 | @project = Project.find(params[:project_id]) 24 | end 25 | 26 | def set_user 27 | if env["HTTP_AUTHORIZATION"].present? 28 | if auth_token = /Token token=(.*)/.match(env["HTTP_AUTHORIZATION"]) 29 | @user = User.find_by(api_key: auth_token[1]) 30 | return @user if @user.present? 31 | end 32 | end 33 | 34 | unauthenticated! 35 | end 36 | 37 | def unauthenticated! 38 | halt 401, {error: "Unauthenticated"}.to_json 39 | end 40 | 41 | def params 42 | hash = env["action_dispatch.request.path_parameters"].merge!(super) 43 | HashWithIndifferentAccess.new(hash) 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /ticketee/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include Pundit 3 | 4 | after_action :verify_authorized, except: [:index], 5 | unless: :devise_controller? 6 | after_action :verify_policy_scoped, only: [:index], 7 | unless: :devise_controller? 8 | 9 | # Prevent CSRF attacks by raising an exception. 10 | # For APIs, you may want to use :null_session instead. 11 | protect_from_forgery with: :exception 12 | 13 | rescue_from Pundit::NotAuthorizedError, with: :not_authorized 14 | 15 | private 16 | 17 | def not_authorized 18 | redirect_to root_path, alert: "You aren't allowed to do that." 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /ticketee/app/controllers/attachments_controller.rb: -------------------------------------------------------------------------------- 1 | class AttachmentsController < ApplicationController 2 | skip_after_action :verify_authorized, only: [:new] 3 | 4 | def show 5 | attachment = Attachment.find(params[:id]) 6 | authorize attachment, :show? 7 | send_file file_to_send(attachment), disposition: :inline 8 | end 9 | 10 | def new 11 | @index = params[:index].to_i 12 | @ticket = Ticket.new 13 | @ticket.attachments.build 14 | render layout: false 15 | end 16 | 17 | private 18 | 19 | def file_to_send(attachment) 20 | if URI.parse(attachment.file.url).scheme 21 | filename = "/tmp/#{attachment.attributes["file"]}" 22 | File.open(filename, "wb+") do |tf| 23 | tf.write open(attachment.file.url).read 24 | end 25 | filename 26 | else 27 | attachment.file.path 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /ticketee/app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | before_action :set_ticket 3 | 4 | def create 5 | @creator = CommentCreator.build(@ticket.comments, current_user, 6 | sanitized_parameters) 7 | authorize @creator.comment, :create? 8 | 9 | if @creator.save 10 | flash[:notice] = "Comment has been created." 11 | redirect_to [@ticket.project, @ticket] 12 | else 13 | flash.now[:alert] = "Comment has not been created." 14 | @project = @ticket.project 15 | @comment = @creator.comment 16 | render "tickets/show" 17 | end 18 | end 19 | 20 | private 21 | 22 | def set_ticket 23 | @ticket = Ticket.find(params[:ticket_id]) 24 | end 25 | 26 | def comment_params 27 | params.require(:comment).permit(:text, :state_id, :tag_names) 28 | end 29 | 30 | def sanitized_parameters 31 | whitelisted_params = comment_params 32 | 33 | unless policy(@ticket).change_state? 34 | whitelisted_params.delete(:state_id) 35 | end 36 | 37 | unless policy(@ticket).tag? 38 | whitelisted_params.delete(:tag_names) 39 | end 40 | 41 | whitelisted_params 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /ticketee/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /ticketee/app/controllers/projects_controller.rb: -------------------------------------------------------------------------------- 1 | class ProjectsController < ApplicationController 2 | before_action :set_project, only: [:show, :edit, :update] 3 | 4 | def index 5 | @projects = policy_scope(Project) 6 | end 7 | 8 | def show 9 | authorize @project, :show? 10 | @tickets = @project.tickets 11 | end 12 | 13 | def edit 14 | authorize @project, :update? 15 | end 16 | 17 | def update 18 | authorize @project, :update? 19 | if @project.update(project_params) 20 | flash[:notice] = "Project has been updated." 21 | redirect_to @project 22 | else 23 | flash.now[:alert] = "Project has not been updated." 24 | render "edit" 25 | end 26 | end 27 | 28 | private 29 | 30 | def set_project 31 | @project = Project.find(params[:id]) 32 | rescue ActiveRecord::RecordNotFound 33 | flash[:alert] = "The project you were looking for could not be found." 34 | redirect_to projects_path 35 | end 36 | 37 | def project_params 38 | params.require(:project).permit(:name, :description) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /ticketee/app/controllers/tags_controller.rb: -------------------------------------------------------------------------------- 1 | class TagsController < ApplicationController 2 | def remove 3 | @ticket = Ticket.find(params[:ticket_id]) 4 | @tag = Tag.find(params[:id]) 5 | authorize @ticket, :tag? 6 | 7 | @ticket.tags.destroy(@tag) 8 | head :ok 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /ticketee/app/controllers/tickets_controller.rb: -------------------------------------------------------------------------------- 1 | class TicketsController < ApplicationController 2 | before_action :set_project 3 | before_action :set_ticket, only: [:show, :edit, :update, :destroy, :watch] 4 | 5 | def search 6 | authorize @project, :show? 7 | if params[:search].present? 8 | @tickets = @project.tickets.search(params[:search]) 9 | else 10 | @tickets = @project.tickets 11 | end 12 | render "projects/show" 13 | end 14 | 15 | def new 16 | @ticket = @project.tickets.build 17 | authorize @ticket, :create? 18 | @ticket.attachments.build 19 | end 20 | 21 | def create 22 | @ticket = @project.tickets.new 23 | 24 | whitelisted_params = ticket_params 25 | unless policy(@ticket).tag? 26 | whitelisted_params.delete(:tag_names) 27 | end 28 | 29 | @ticket.attributes = whitelisted_params 30 | @ticket.author = current_user 31 | authorize @ticket, :create? 32 | 33 | if @ticket.save 34 | flash[:notice] = "Ticket has been created." 35 | redirect_to [@project, @ticket] 36 | else 37 | flash.now[:alert] = "Ticket has not been created." 38 | render "new" 39 | end 40 | end 41 | 42 | def show 43 | authorize @ticket, :show? 44 | @comment = @ticket.comments.build(state_id: @ticket.state_id) 45 | end 46 | 47 | def edit 48 | authorize @ticket, :update? 49 | end 50 | 51 | def update 52 | authorize @ticket, :update? 53 | if @ticket.update(ticket_params) 54 | flash[:notice] = "Ticket has been updated." 55 | redirect_to [@project, @ticket] 56 | else 57 | flash.now[:alert] = "Ticket has not been updated." 58 | render "edit" 59 | end 60 | end 61 | 62 | def destroy 63 | authorize @ticket, :destroy? 64 | @ticket.destroy 65 | flash[:notice] = "Ticket has been deleted." 66 | 67 | redirect_to @project 68 | end 69 | 70 | def watch 71 | authorize @ticket, :show? 72 | if @ticket.watchers.exists?(current_user.id) 73 | @ticket.watchers.destroy(current_user) 74 | flash[:notice] = "You are no longer watching this ticket." 75 | else 76 | @ticket.watchers << current_user 77 | flash[:notice] = "You are now watching this ticket." 78 | end 79 | 80 | redirect_to project_ticket_path(@ticket.project, @ticket) 81 | end 82 | 83 | private 84 | 85 | def ticket_params 86 | params.require(:ticket).permit(:name, :description, :tag_names, 87 | attachments_attributes: [:file, :file_cache]) 88 | end 89 | 90 | def set_project 91 | @project = Project.find(params[:project_id]) 92 | end 93 | 94 | def set_ticket 95 | @ticket = @project.tickets.find(params[:id]) 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /ticketee/app/helpers/admin/application_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::ApplicationHelper 2 | def roles 3 | hash = {} 4 | 5 | Role.available_roles.each do |role| 6 | hash[role.titleize] = role 7 | end 8 | 9 | hash 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /ticketee/app/helpers/admin/projects_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::ProjectsHelper 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/helpers/admin/states_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::StatesHelper 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/helpers/admin/users_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/helpers/api/tickets_helper.rb: -------------------------------------------------------------------------------- 1 | module API::TicketsHelper 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def title(*parts) 3 | unless parts.empty? 4 | content_for :title do 5 | (parts << "Ticketee").join(" - ") 6 | end 7 | end 8 | end 9 | 10 | def admins_only(&block) 11 | block.call if current_user.try(:admin?) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /ticketee/app/helpers/attachments_helper.rb: -------------------------------------------------------------------------------- 1 | module AttachmentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/helpers/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module CommentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/helpers/projects_helper.rb: -------------------------------------------------------------------------------- 1 | module ProjectsHelper 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/helpers/tags_helper.rb: -------------------------------------------------------------------------------- 1 | module TagsHelper 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/helpers/tickets_helper.rb: -------------------------------------------------------------------------------- 1 | module TicketsHelper 2 | def state_transition_for(comment) 3 | if comment.previous_state != comment.state 4 | content_tag(:p) do 5 | value = " state changed" 6 | if comment.previous_state.present? 7 | value += " from #{render comment.previous_state}" 8 | end 9 | value += " to #{render comment.state}" 10 | value.html_safe 11 | end 12 | end 13 | end 14 | 15 | def toggle_watching_button(ticket) 16 | text = if ticket.watchers.include?(current_user) 17 | "Unwatch" 18 | else 19 | "Watch" 20 | end 21 | link_to text, watch_project_ticket_path(ticket.project, ticket), 22 | class: text.parameterize, method: :post 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /ticketee/app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/app/mailers/.keep -------------------------------------------------------------------------------- /ticketee/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /ticketee/app/mailers/comment_notifier.rb: -------------------------------------------------------------------------------- 1 | class CommentNotifier < ApplicationMailer 2 | def created(comment, user) 3 | @comment = comment 4 | @user = user 5 | @ticket = comment.ticket 6 | @project = @ticket.project 7 | 8 | subject = "[ticketee] #{@project.name} - #{@ticket.name}" 9 | mail(to: user.email, subject: subject) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /ticketee/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/app/models/.keep -------------------------------------------------------------------------------- /ticketee/app/models/attachment.rb: -------------------------------------------------------------------------------- 1 | class Attachment < ActiveRecord::Base 2 | belongs_to :ticket 3 | 4 | mount_uploader :file, AttachmentUploader 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | belongs_to :previous_state, class_name: "State" 3 | belongs_to :state 4 | belongs_to :ticket 5 | belongs_to :author, class_name: "User" 6 | 7 | validates :text, presence: true 8 | 9 | delegate :project, to: :ticket 10 | 11 | scope :persisted, lambda { where.not(id: nil) } 12 | 13 | before_create :set_previous_state 14 | after_create :set_ticket_state 15 | after_create :associate_tags_with_ticket 16 | after_create :author_watches_ticket 17 | 18 | attr_accessor :tag_names 19 | 20 | private 21 | 22 | def set_previous_state 23 | self.previous_state = ticket.state 24 | end 25 | 26 | def set_ticket_state 27 | ticket.state = state 28 | ticket.save! 29 | end 30 | 31 | def associate_tags_with_ticket 32 | if tag_names 33 | tag_names.split.each do |name| 34 | ticket.tags << Tag.find_or_create_by(name: name) 35 | end 36 | end 37 | end 38 | 39 | def author_watches_ticket 40 | if author.present? && !ticket.watchers.include?(author) 41 | ticket.watchers << author 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ticketee/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/app/models/concerns/.keep -------------------------------------------------------------------------------- /ticketee/app/models/project.rb: -------------------------------------------------------------------------------- 1 | class Project < ActiveRecord::Base 2 | has_many :tickets, dependent: :delete_all 3 | has_many :roles, dependent: :delete_all 4 | 5 | validates :name, presence: true 6 | 7 | def has_member?(user) 8 | roles.exists?(user_id: user) 9 | end 10 | 11 | [:manager, :editor, :viewer].each do |role| 12 | define_method "has_#{role}?" do |user| 13 | roles.exists?(user_id: user, role: role) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /ticketee/app/models/role.rb: -------------------------------------------------------------------------------- 1 | class Role < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :project 4 | 5 | def self.available_roles 6 | %w(manager editor viewer) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ticketee/app/models/state.rb: -------------------------------------------------------------------------------- 1 | class State < ActiveRecord::Base 2 | def self.default 3 | find_by(default: true) 4 | end 5 | 6 | def make_default! 7 | State.update_all(default: false) 8 | update!(default: true) 9 | end 10 | 11 | def to_s 12 | name 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /ticketee/app/models/tag.rb: -------------------------------------------------------------------------------- 1 | class Tag < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/models/ticket.rb: -------------------------------------------------------------------------------- 1 | class Ticket < ActiveRecord::Base 2 | belongs_to :project 3 | belongs_to :author, class_name: "User" 4 | belongs_to :state 5 | has_many :attachments, dependent: :destroy 6 | has_many :comments, dependent: :destroy 7 | has_and_belongs_to_many :tags, uniq: true 8 | has_and_belongs_to_many :watchers, join_table: "ticket_watchers", 9 | class_name: "User", uniq: true 10 | 11 | attr_accessor :tag_names 12 | 13 | validates :name, presence: true 14 | validates :description, presence: true, length: { minimum: 10 } 15 | 16 | accepts_nested_attributes_for :attachments, reject_if: :all_blank 17 | 18 | before_create :assign_default_state 19 | after_create :author_watches_me 20 | 21 | searcher do 22 | label :tag, from: :tags, field: "name" 23 | label :state, from: :state, field: "name" 24 | end 25 | 26 | def tag_names=(names) 27 | @tag_names = names 28 | names.split.each do |name| 29 | self.tags << Tag.find_or_initialize_by(name: name) 30 | end 31 | end 32 | 33 | private 34 | 35 | def author_watches_me 36 | if author.present? && !self.watchers.include?(author) 37 | self.watchers << author 38 | end 39 | end 40 | 41 | def assign_default_state 42 | self.state ||= State.default 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ticketee/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | has_many :roles 3 | 4 | # Include default devise modules. Others available are: 5 | # :confirmable, :lockable, :timeoutable and :omniauthable 6 | devise :database_authenticatable, :registerable, 7 | :recoverable, :rememberable, :trackable, :validatable 8 | 9 | scope :excluding_archived, lambda { where(archived_at: nil) } 10 | 11 | def to_s 12 | "#{email} (#{admin? ? "Admin" : "User"})" 13 | end 14 | 15 | def archive 16 | self.update(archived_at: Time.now) 17 | end 18 | 19 | def generate_api_key 20 | self.update_column(:api_key, SecureRandom.hex(16)) 21 | end 22 | 23 | def active_for_authentication? 24 | super && archived_at.nil? 25 | end 26 | 27 | def inactive_message 28 | archived_at.nil? ? super : :archived 29 | end 30 | 31 | def role_on(project) 32 | roles.find_by(project_id: project).try(:name) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /ticketee/app/policies/application_policy.rb: -------------------------------------------------------------------------------- 1 | class ApplicationPolicy 2 | attr_reader :user, :record 3 | 4 | def initialize(user, record) 5 | @user = user 6 | @record = record 7 | end 8 | 9 | def index? 10 | false 11 | end 12 | 13 | def show? 14 | scope.where(:id => record.id).exists? 15 | end 16 | 17 | def create? 18 | false 19 | end 20 | 21 | def new? 22 | create? 23 | end 24 | 25 | def update? 26 | false 27 | end 28 | 29 | def edit? 30 | update? 31 | end 32 | 33 | def destroy? 34 | false 35 | end 36 | 37 | def scope 38 | Pundit.policy_scope!(user, record.class) 39 | end 40 | 41 | class Scope 42 | attr_reader :user, :scope 43 | 44 | def initialize(user, scope) 45 | @user = user 46 | @scope = scope 47 | end 48 | 49 | def resolve 50 | scope 51 | end 52 | end 53 | end 54 | 55 | -------------------------------------------------------------------------------- /ticketee/app/policies/attachment_policy.rb: -------------------------------------------------------------------------------- 1 | class AttachmentPolicy < ApplicationPolicy 2 | class Scope < Scope 3 | def resolve 4 | scope 5 | end 6 | end 7 | 8 | def show? 9 | user.try(:admin?) || record.ticket.project.has_member?(user) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /ticketee/app/policies/comment_policy.rb: -------------------------------------------------------------------------------- 1 | class CommentPolicy < TicketPolicy 2 | end 3 | -------------------------------------------------------------------------------- /ticketee/app/policies/project_policy.rb: -------------------------------------------------------------------------------- 1 | class ProjectPolicy < ApplicationPolicy 2 | class Scope < Scope 3 | def resolve 4 | return scope.none if user.nil? 5 | return scope.all if user.admin? 6 | 7 | scope.joins(:roles).where(roles: {user_id: user}) 8 | end 9 | end 10 | 11 | def show? 12 | user.try(:admin?) || record.has_member?(user) 13 | end 14 | 15 | def update? 16 | user.try(:admin?) || record.has_manager?(user) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /ticketee/app/policies/ticket_policy.rb: -------------------------------------------------------------------------------- 1 | class TicketPolicy < ApplicationPolicy 2 | class Scope < Scope 3 | def resolve 4 | scope 5 | end 6 | end 7 | 8 | def show? 9 | user.try(:admin?) || record.project.has_member?(user) 10 | end 11 | 12 | def create? 13 | user.try(:admin?) || record.project.has_manager?(user) || 14 | record.project.has_editor?(user) 15 | end 16 | 17 | def update? 18 | user.try(:admin?) || record.project.has_manager?(user) || 19 | (record.project.has_editor?(user) && record.author == user) 20 | end 21 | 22 | def destroy? 23 | user.try(:admin?) || record.project.has_manager?(user) 24 | end 25 | 26 | def change_state? 27 | destroy? 28 | end 29 | 30 | def tag? 31 | destroy? 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /ticketee/app/serializers/ticket_serializer.rb: -------------------------------------------------------------------------------- 1 | class TicketSerializer < ActiveModel::Serializer 2 | attributes :id, :name, :description, :project_id, :created_at, 3 | :updated_at, :author_id 4 | 5 | has_one :state 6 | end 7 | -------------------------------------------------------------------------------- /ticketee/app/services/comment_creator.rb: -------------------------------------------------------------------------------- 1 | class CommentCreator 2 | attr_reader :comment 3 | 4 | def self.build(scope, current_user, comment_params) 5 | comment = scope.build(comment_params) 6 | comment.author = current_user 7 | 8 | new(comment) 9 | end 10 | 11 | def initialize(comment) 12 | @comment = comment 13 | end 14 | 15 | def save 16 | if @comment.save 17 | notify_watchers 18 | end 19 | end 20 | 21 | def notify_watchers 22 | (@comment.ticket.watchers - [@comment.author]).each do |user| 23 | CommentNotifier.created(@comment, user).deliver_now 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /ticketee/app/uploaders/attachment_uploader.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | class AttachmentUploader < CarrierWave::Uploader::Base 4 | 5 | # Include RMagick or MiniMagick support: 6 | # include CarrierWave::RMagick 7 | # include CarrierWave::MiniMagick 8 | 9 | # Override the directory where uploaded files will be stored. 10 | # This is a sensible default for uploaders that are meant to be mounted: 11 | def store_dir 12 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 13 | end 14 | 15 | # Provide a default URL as a default if there hasn't been a file uploaded: 16 | # def default_url 17 | # # For Rails 3.1+ asset pipeline compatibility: 18 | # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) 19 | # 20 | # "/images/fallback/" + [version_name, "default.png"].compact.join('_') 21 | # end 22 | 23 | # Process files as they are uploaded: 24 | # process :scale => [200, 300] 25 | # 26 | # def scale(width, height) 27 | # # do something 28 | # end 29 | 30 | # Create different versions of your uploaded files: 31 | # version :thumb do 32 | # process :resize_to_fit => [50, 50] 33 | # end 34 | 35 | # Add a white list of extensions which are allowed to be uploaded. 36 | # For images you might use something like this: 37 | # def extension_white_list 38 | # %w(jpg jpeg gif png) 39 | # end 40 | 41 | # Override the filename of the uploaded files: 42 | # Avoid using model.id or version_name here, see uploader/store.rb for details. 43 | # def filename 44 | # "something.jpg" if original_filename 45 | # end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/application/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Admin Lounge

5 |
6 | 7 |

Welcome to Ticketee's Admin Lounge. Please enjoy your stay.

8 |
9 | 10 |
11 |

Admin Links

12 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/projects/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for([:admin, project]) do |f| %> 2 | <%= f.input :name %> 3 | <%= f.input :description %> 4 | 5 | <%= f.button :submit, class: 'btn-primary' %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/projects/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

New Project

3 |
4 | 5 | <%= render "form", project: @project %> -------------------------------------------------------------------------------- /ticketee/app/views/admin/states/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for [:admin, state] do |f| %> 2 | <%= f.input :name %> 3 | <%= f.input :color %> 4 | 5 | <%= f.submit class: 'btn btn-primary' %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/states/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

States

3 | 4 | 7 |
8 | 9 | 21 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/states/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

New State

3 |
4 | 5 | <%= render "form", state: @state %> 6 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/users/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for [:admin, user] do |f| %> 2 | <%= f.input :email %> 3 | <%= f.input :password %> 4 | <%= f.input :admin, label: "Is an admin?" %> 5 | 6 |
7 |

Roles

8 |
9 | 10 | 11 | <% projects.each do |project| %> 12 | 13 | 14 | 19 | 20 | <% end %> 21 |
<%= label_tag dom_id(project), project.name %> 15 | <%= select_tag dom_id(project), options_for_select(roles, 16 | user.role_on(project)), name: "roles[#{project.id}]", 17 | include_blank: true %> 18 |
22 | 23 | <%= f.button :submit, class: "btn btn-primary" %> 24 | <% end %> 25 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/users/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Edit User

3 |
4 | 5 | <%= render "form", user: @user, projects: @projects %> 6 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/users/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Users

3 | 4 | 9 |
10 | 11 | 16 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/users/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

New User

3 |
4 | 5 | <%= render "form", user: @user, projects: @projects %> 6 | -------------------------------------------------------------------------------- /ticketee/app/views/admin/users/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= @user %>

3 | 4 | 14 |
15 | 16 |
17 |

Roles

18 |
19 | 20 | <% if @user.roles.any? %> 21 | 26 | <% else %> 27 |

This user has no roles.

28 | <% end %> 29 | -------------------------------------------------------------------------------- /ticketee/app/views/attachments/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.simple_fields_for :attachments, child_index: index do |ff| %> 2 | <%= ff.input :file, as: :file, label: "File ##{index += 1}" %> 3 | <%= ff.input :file_cache, as: :hidden %> 4 | <% end %> 5 | -------------------------------------------------------------------------------- /ticketee/app/views/attachments/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= fields_for @ticket do |f| %> 2 | <%= render partial: "form", locals: { f: f, index: @index } %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /ticketee/app/views/comment_notifier/created.text.erb: -------------------------------------------------------------------------------- 1 | Hello! 2 | 3 | <%= @ticket.name %> for <%= @project.name %> has been updated. 4 | 5 | <%= @comment.author.email %> wrote: 6 | 7 | <%= @comment.text %> 8 | 9 | You can view this ticket online by going to: 10 | <%= project_ticket_url(@project, @ticket) %> 11 | -------------------------------------------------------------------------------- /ticketee/app/views/comments/_comment.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= simple_format(comment.text) %> 3 | 7 |
8 | 9 | <%= state_transition_for(comment) %> 10 | -------------------------------------------------------------------------------- /ticketee/app/views/comments/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

New Comment

3 |
4 | 5 | <%= simple_form_for [ticket, comment] do |f| %> 6 | <%= f.input :text %> 7 | <% if policy(ticket).change_state? %> 8 | <%= f.association :state %> 9 | <% end %> 10 | <% if policy(ticket).tag? %> 11 | <%= render "tags/form", f: f %> 12 | <% end %> 13 | 14 | <%= f.submit class: "btn btn-primary" %> 15 | <% end %> 16 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= f.error_notification %> 5 | <%= f.full_error :confirmation_token %> 6 | 7 |
8 | <%= f.input :email, required: true, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.button :submit, "Resend confirmation instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

6 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password. You can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 | <%= f.input :reset_password_token, as: :hidden %> 7 | <%= f.full_error :reset_password_token %> 8 | 9 |
10 | <%= f.input :password, label: "New password", required: true, autofocus: true %> 11 | <%= f.input :password_confirmation, label: "Confirm your new password", required: true %> 12 |
13 | 14 |
15 | <%= f.button :submit, "Change my password" %> 16 |
17 | <% end %> 18 | 19 | <%= render "devise/shared/links" %> 20 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 |
7 | <%= f.input :email, required: true, autofocus: true %> 8 |
9 | 10 |
11 | <%= f.button :submit, "Send me reset password instructions" %> 12 |
13 | <% end %> 14 | 15 | <%= render "devise/shared/links" %> 16 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 |
7 | <%= f.input :email, required: true, autofocus: true %> 8 | 9 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 10 |

Currently waiting confirmation for: <%= resource.unconfirmed_email %>

11 | <% end %> 12 | 13 | <%= f.input :password, autocomplete: "off", hint: "leave it blank if you don't want to change it", required: false %> 14 | <%= f.input :password_confirmation, required: false %> 15 | <%= f.input :current_password, hint: "we need your current password to confirm your changes", required: true %> 16 |
17 | 18 |
19 | <%= f.button :submit, "Update" %> 20 |
21 | <% end %> 22 | 23 |

Cancel my account

24 | 25 |

Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

26 | 27 | <%= link_to "Back", :back %> 28 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Sign Up

3 |
4 | 5 | <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 6 |
7 | <%= f.input :email, required: true, autofocus: true %> 8 | <%= f.input :password, required: true, hint: ("#{@minimum_password_length} characters minimum" if @validatable) %> 9 | <%= f.input :password_confirmation, required: true %> 10 |
11 | 12 |
13 | <%= f.button :submit, "Sign up", class: "btn btn-primary" %> 14 |
15 | <% end %> 16 | 17 | <%= render "devise/shared/links" %> 18 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Sign In

3 |
4 | 5 | <%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 6 |
7 | <%= f.input :email, required: false, autofocus: true %> 8 | <%= f.input :password, required: false %> 9 | <%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %> 10 |
11 | 12 |
13 | <%= f.button :submit, "Sign in", class: "btn btn-primary" %> 14 |
15 | <% end %> 16 | 17 | <%= render "devise/shared/links" %> 18 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end -%> 25 | <% end -%> 26 | -------------------------------------------------------------------------------- /ticketee/app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= f.error_notification %> 5 | <%= f.full_error :unlock_token %> 6 | 7 |
8 | <%= f.input :email, required: true, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.button :submit, "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /ticketee/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% if content_for?(:title) %> 6 | <%= yield(:title) %> 7 | <% else %> 8 | Ticketee 9 | <% end %> 10 | 11 | <%= stylesheet_link_tag 'application', media: 'all' %> 12 | <%= javascript_include_tag 'application' %> 13 | <%= csrf_meta_tags %> 14 | 21 | 22 | 23 | 24 | 25 | 72 | 73 |
74 | <% flash.each do |key, message| %> 75 |
76 | <%= message %> 77 |
78 | <% end %> 79 | 80 | <%= yield %> 81 |
82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /ticketee/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= yield %> 4 | 5 | 6 | -------------------------------------------------------------------------------- /ticketee/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /ticketee/app/views/projects/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for(project) do |f| %> 2 | <%= f.input :name %> 3 | <%= f.input :description %> 4 | 5 | <%= f.button :submit, class: 'btn-primary' %> 6 | <% end %> -------------------------------------------------------------------------------- /ticketee/app/views/projects/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Edit Project

3 |
4 | 5 | <%= render "form", project: @project %> -------------------------------------------------------------------------------- /ticketee/app/views/projects/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Projects

3 | 4 | <% admins_only do %> 5 | 10 | <% end %> 11 |
12 | 13 |
14 | <% @projects.each do |project| %> 15 |

<%= link_to project.name, project %>

16 |

<%= project.description %>

17 | <% end %> 18 |
19 | -------------------------------------------------------------------------------- /ticketee/app/views/projects/show.html.erb: -------------------------------------------------------------------------------- 1 | <% title(@project.name, "Projects") %> 2 | 3 |
4 |

<%= @project.name %>

5 | 6 | 18 |
19 | 20 |

<%= @project.description %>

21 | 22 |
23 |

Tickets

24 | 25 | 39 |
40 | 41 | 50 | -------------------------------------------------------------------------------- /ticketee/app/views/states/_state.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= state -%> 3 | 4 | -------------------------------------------------------------------------------- /ticketee/app/views/tags/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.input :tag_names, label: "Tags" %> 2 | -------------------------------------------------------------------------------- /ticketee/app/views/tags/_tag.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if policy(ticket).tag? %> 3 | <%= link_to "".html_safe, 4 | remove_ticket_tag_path(ticket, tag), method: :delete, remote: true, 5 | class: "remove", title: "remove" %> 6 | <% end %> 7 | <%= link_to tag.name, search_project_tickets_path(ticket.project, 8 | search: "tag:#{tag.name}") %> 9 |
10 | -------------------------------------------------------------------------------- /ticketee/app/views/tickets/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for([project, ticket]) do |f| %> 2 | <%= f.input :name %> 3 | <%= f.input :description %> 4 | <%= render "tags/form", f: f %> 5 | 6 |
7 |

Attachments

8 | <%= render partial: "attachments/form", locals: { f: f, index: 0 } %> 9 |
10 |

11 | <%= link_to "Add another file", new_attachment_path, remote: true, 12 | id: "add_file", data: { params: {index: ticket.attachments.size} } %> 13 |

14 | 15 | <%= f.button :submit, class: "btn-primary" %> 16 | <% end %> 17 | -------------------------------------------------------------------------------- /ticketee/app/views/tickets/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | Edit Ticket 4 | <%= @project.name %> 5 |

6 |
7 | 8 | <%= render "form", project: @project, ticket: @ticket %> 9 | -------------------------------------------------------------------------------- /ticketee/app/views/tickets/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | New Ticket 4 | <%= @project.name %> 5 |

6 |
7 | 8 | <%= render "form", project: @project, ticket: @ticket %> -------------------------------------------------------------------------------- /ticketee/app/views/tickets/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

<%= @project.name %>

4 |
5 | 6 |
7 |

<%= @ticket.name %>

8 | 9 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | <% if @ticket.state.present? %> 33 | 34 | 35 | 36 | 37 | <% end %> 38 | <% if @ticket.tags.any? %> 39 | 40 | 41 | 42 | 43 | <% end %> 44 | 45 | 46 | 50 | 51 |
Author:<%= @ticket.author.email %>
Created:<%= time_ago_in_words(@ticket.created_at) %> ago
State:<%= render @ticket.state %>
Tags:<%= render @ticket.tags, ticket: @ticket %>
Watchers: 47 | <%= toggle_watching_button(@ticket) %>
48 | <%= @ticket.watchers.map(&:email).to_sentence %> 49 |
52 | 53 | <%= simple_format(@ticket.description) %> 54 | 55 | <% if @ticket.attachments.any? %> 56 |

Attachments

57 |
58 | <% @ticket.attachments.each do |attachment| %> 59 |

60 | <%= link_to File.basename(attachment.file.url), 61 | attachment_path(attachment) %> 62 | (<%= number_to_human_size(attachment.file.size) %>) 63 |

64 | <% end %> 65 |
66 | <% end %> 67 |
68 | 69 |
70 |

Comments

71 |
72 | 73 |
74 | <% if @ticket.comments.select(&:persisted?).any? %> 75 | <%= render @ticket.comments.select(&:persisted?) %> 76 | <% else %> 77 |

There are no comments for this ticket.

78 | <% end %> 79 |
80 | 81 | <% if policy(@comment).create? %> 82 | <%= render "comments/form", ticket: @ticket, comment: @comment %> 83 | <% end %> 84 | -------------------------------------------------------------------------------- /ticketee/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /ticketee/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require_relative '../config/boot' 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /ticketee/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require_relative '../config/boot' 7 | require 'rake' 8 | Rake.application.run 9 | -------------------------------------------------------------------------------- /ticketee/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /ticketee/bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) 11 | Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } 12 | gem "spring", match[1] 13 | require "spring/binstub" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /ticketee/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /ticketee/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Ticketee 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | 23 | # Do not swallow errors in after_commit/after_rollback callbacks. 24 | config.active_record.raise_in_transactional_callbacks = true 25 | 26 | #require "link_jumbler" 27 | #config.middleware.use LinkJumbler, { "e" => "a" } 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /ticketee/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /ticketee/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /ticketee/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /ticketee/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 and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /ticketee/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 static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | config.action_mailer.default_url_options = { 34 | host: "ticketee.dev" 35 | } 36 | 37 | # Randomize the order test cases are executed. 38 | config.active_support.test_order = :random 39 | 40 | # Print deprecation notices to the stderr. 41 | config.active_support.deprecation = :stderr 42 | 43 | # Raises error for missing translations 44 | # config.action_view.raise_on_missing_translations = true 45 | 46 | config.action_controller.action_on_unpermitted_parameters = :raise 47 | end 48 | -------------------------------------------------------------------------------- /ticketee/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /ticketee/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /ticketee/config/initializers/carrierwave.rb: -------------------------------------------------------------------------------- 1 | CarrierWave.configure do |config| 2 | config.root = Rails.root 3 | 4 | if Rails.env.production? 5 | config.storage = :fog 6 | config.fog_credentials = { 7 | provider: "AWS", 8 | aws_access_key_id: ENV["S3_KEY"], 9 | aws_secret_access_key: ENV["S3_SECRET"], 10 | region: ENV["S3_REGION"] 11 | } 12 | config.fog_directory = ENV["S3_BUCKET"] 13 | else 14 | config.storage = :file 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /ticketee/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /ticketee/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /ticketee/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | ActiveSupport::Inflector.inflections(:en) do |inflect| 14 | inflect.acronym "API" 15 | end 16 | -------------------------------------------------------------------------------- /ticketee/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /ticketee/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_ticketee_session' 4 | -------------------------------------------------------------------------------- /ticketee/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /ticketee/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /ticketee/config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | # include_blanks: 27 | # defaults: 28 | # age: 'Rather not say' 29 | # prompts: 30 | # defaults: 31 | # age: 'Select your age' 32 | -------------------------------------------------------------------------------- /ticketee/config/routes.rb: -------------------------------------------------------------------------------- 1 | require "heartbeat/application" 2 | 3 | Rails.application.routes.draw do 4 | mount Heartbeat::Application, at: "/heartbeat" 5 | 6 | namespace :admin do 7 | root "application#index" 8 | 9 | resources :projects, only: [:new, :create, :destroy] 10 | resources :users do 11 | member do 12 | patch :archive 13 | end 14 | end 15 | resources :states, only: [:index, :new, :create] do 16 | member do 17 | get :make_default 18 | end 19 | end 20 | end 21 | 22 | namespace :api do 23 | namespace :v2 do 24 | mount API::V2::Tickets, at: "/projects/:project_id/tickets" 25 | end 26 | 27 | resources :projects, only: [] do 28 | resources :tickets 29 | end 30 | end 31 | 32 | devise_for :users 33 | root "projects#index" 34 | 35 | resources :projects, only: [:index, :show, :edit, :update] do 36 | resources :tickets do 37 | collection do 38 | get :search 39 | end 40 | 41 | member do 42 | post :watch 43 | end 44 | end 45 | end 46 | 47 | resources :tickets, only: [] do 48 | resources :comments, only: [:create] 49 | resources :tags, only: [] do 50 | member do 51 | delete :remove 52 | end 53 | end 54 | end 55 | 56 | resources :attachments, only: [:show, :new] 57 | end 58 | -------------------------------------------------------------------------------- /ticketee/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 517d2e7cb3575560b8f07caf66530fc4e650496f93aaec9bd2a0dd0f8f7cc498b273494db869e3a23e9fe1cd6604bfd12d8225e89bd20a3efed9c32c09cc043c 15 | 16 | test: 17 | secret_key_base: 1cf2478216f8cd5013d57fda067ecd66e1397d1d8c94e7330fa2b902ac6db346824928b6c0856621afebb73704ad01a0cf86fa10f98d48b8109153cd9a511e1f 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150314100956_create_projects.rb: -------------------------------------------------------------------------------- 1 | class CreateProjects < ActiveRecord::Migration 2 | def change 3 | create_table :projects do |t| 4 | t.string :name 5 | t.string :description 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150318033648_create_tickets.rb: -------------------------------------------------------------------------------- 1 | class CreateTickets < ActiveRecord::Migration 2 | def change 3 | create_table :tickets do |t| 4 | t.string :name 5 | t.text :description 6 | t.references :project, index: true, foreign_key: true 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150321110052_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table(:users) do |t| 4 | ## Database authenticatable 5 | t.string :email, null: false, default: "" 6 | t.string :encrypted_password, null: false, default: "" 7 | 8 | ## Recoverable 9 | t.string :reset_password_token 10 | t.datetime :reset_password_sent_at 11 | 12 | ## Rememberable 13 | t.datetime :remember_created_at 14 | 15 | ## Trackable 16 | t.integer :sign_in_count, default: 0, null: false 17 | t.datetime :current_sign_in_at 18 | t.datetime :last_sign_in_at 19 | t.string :current_sign_in_ip 20 | t.string :last_sign_in_ip 21 | 22 | ## Confirmable 23 | # t.string :confirmation_token 24 | # t.datetime :confirmed_at 25 | # t.datetime :confirmation_sent_at 26 | # t.string :unconfirmed_email # Only if using reconfirmable 27 | 28 | ## Lockable 29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 30 | # t.string :unlock_token # Only if unlock strategy is :email or :both 31 | # t.datetime :locked_at 32 | 33 | 34 | t.timestamps 35 | end 36 | 37 | add_index :users, :email, unique: true 38 | add_index :users, :reset_password_token, unique: true 39 | # add_index :users, :confirmation_token, unique: true 40 | # add_index :users, :unlock_token, unique: true 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150322065031_add_author_to_tickets.rb: -------------------------------------------------------------------------------- 1 | class AddAuthorToTickets < ActiveRecord::Migration 2 | def change 3 | add_reference :tickets, :author, index: true 4 | add_foreign_key :tickets, :users, column: :author_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150322084252_add_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAdminToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :admin, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150323072600_add_archived_at_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddArchivedAtToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :archived_at, :timestamp 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150325092856_create_roles.rb: -------------------------------------------------------------------------------- 1 | class CreateRoles < ActiveRecord::Migration 2 | def change 3 | create_table :roles do |t| 4 | t.references :user, index: true, foreign_key: true 5 | t.string :role 6 | t.references :project, index: true, foreign_key: true 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150401104001_add_attachment_to_tickets.rb: -------------------------------------------------------------------------------- 1 | class AddAttachmentToTickets < ActiveRecord::Migration 2 | def change 3 | add_column :tickets, :attachment, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150402090612_create_attachments.rb: -------------------------------------------------------------------------------- 1 | class CreateAttachments < ActiveRecord::Migration 2 | def change 3 | create_table :attachments do |t| 4 | t.string :file 5 | t.references :ticket, index: true, foreign_key: true 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150402090619_remove_attachment_from_tickets.rb: -------------------------------------------------------------------------------- 1 | class RemoveAttachmentFromTickets < ActiveRecord::Migration 2 | def change 3 | remove_column :tickets, :attachment, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150403021520_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration 2 | def change 3 | create_table :comments do |t| 4 | t.text :text 5 | t.references :ticket, index: true, foreign_key: true 6 | t.references :author, index: true 7 | 8 | t.timestamps null: false 9 | end 10 | 11 | add_foreign_key :comments, :users, column: :author_id 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150403044120_create_states.rb: -------------------------------------------------------------------------------- 1 | class CreateStates < ActiveRecord::Migration 2 | def change 3 | create_table :states do |t| 4 | t.string :name 5 | t.string :color 6 | end 7 | 8 | add_reference :tickets, :state, index: true, foreign_key: true 9 | add_reference :comments, :state, foreign_key: true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150403070314_add_previous_state_to_comments.rb: -------------------------------------------------------------------------------- 1 | class AddPreviousStateToComments < ActiveRecord::Migration 2 | def change 3 | add_reference :comments, :previous_state, index: true 4 | add_foreign_key :comments, :states, column: :previous_state_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150403084623_add_default_to_states.rb: -------------------------------------------------------------------------------- 1 | class AddDefaultToStates < ActiveRecord::Migration 2 | def change 3 | add_column :states, :default, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150404113718_create_tags.rb: -------------------------------------------------------------------------------- 1 | class CreateTags < ActiveRecord::Migration 2 | def change 3 | create_table :tags do |t| 4 | t.string :name 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150404113820_create_join_table_tags_tickets.rb: -------------------------------------------------------------------------------- 1 | class CreateJoinTableTagsTickets < ActiveRecord::Migration 2 | def change 3 | create_join_table :tags, :tickets do |t| 4 | t.index [:tag_id, :ticket_id] 5 | t.index [:ticket_id, :tag_id] 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150405052036_create_join_table_ticket_watchers.rb: -------------------------------------------------------------------------------- 1 | class CreateJoinTableTicketWatchers < ActiveRecord::Migration 2 | def change 3 | create_join_table :tickets, :users, table_name: :ticket_watchers do |t| 4 | # t.index [:ticket_id, :user_id] 5 | # t.index [:user_id, :ticket_id] 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ticketee/db/migrate/20150406055658_add_api_key_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAPIKeyToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :api_key, :string 4 | add_index :users, :api_key 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /ticketee/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | 9 | unless User.exists?(email: "admin@ticketee.com") 10 | User.create!(email: "admin@ticketee.com", password: "password", admin: true) 11 | end 12 | 13 | unless User.exists?(email: "viewer@ticketee.com") 14 | User.create!(email: "viewer@ticketee.com", password: "password") 15 | end 16 | 17 | ["Sublime Text 3", "Internet Explorer"].each do |name| 18 | unless Project.exists?(name: name) 19 | Project.create!(name: name, description: "A sample project about #{name}") 20 | end 21 | end 22 | 23 | unless State.exists? 24 | State.create(name: "New", color: "#0066CC") 25 | State.create(name: "Open", color: "#008000") 26 | State.create(name: "Closed", color: "#990000") 27 | State.create(name: "Awesome", color: "#663399") 28 | end 29 | -------------------------------------------------------------------------------- /ticketee/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/lib/assets/.keep -------------------------------------------------------------------------------- /ticketee/lib/heartbeat/application.rb: -------------------------------------------------------------------------------- 1 | module Heartbeat 2 | class Application 3 | def self.call(env) 4 | default_headers = {"Content-Type" => "text/plain"} 5 | 6 | if env["PATH_INFO"] =~ /200/ 7 | body = "Success!" 8 | status = 200 9 | else 10 | body = "Failure!" 11 | status = 500 12 | end 13 | 14 | [status, default_headers, ["#{env["PATH_INFO"]} == #{body}"]] 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /ticketee/lib/heartbeat/config.ru: -------------------------------------------------------------------------------- 1 | heartbeat_root = File.expand_path(File.dirname(__FILE__)) 2 | require heartbeat_root + "/application" 3 | require heartbeat_root + "/test_application" 4 | 5 | app = Rack::Builder.app do 6 | map "/test" do 7 | run Heartbeat::Application 8 | end 9 | 10 | map "/" do 11 | run Heartbeat::TestApplication 12 | end 13 | end 14 | 15 | run app 16 | -------------------------------------------------------------------------------- /ticketee/lib/heartbeat/test_application.rb: -------------------------------------------------------------------------------- 1 | module Heartbeat 2 | class TestApplication 3 | def self.call(env) 4 | default_headers = {"Content-Type" => "text/html"} 5 | body = %Q{ 6 | 7 | 8 | 9 | Success or FAILURE?! 10 | 11 | 12 |

Success or FAILURE?!

13 |
14 | 15 |
16 | 17 |
18 | 19 |
20 | 21 | 22 | } 23 | 24 | [200, default_headers, [body]] 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /ticketee/lib/link_jumbler.rb: -------------------------------------------------------------------------------- 1 | require "nokogiri" 2 | 3 | class LinkJumbler 4 | def initialize(app, letters) 5 | @app = app 6 | @letters = letters 7 | end 8 | 9 | def call(env) 10 | status, headers, response = @app.call(env) 11 | if headers['Content-Type'].include?("text/html") 12 | body = Nokogiri::HTML(response.body) 13 | body.css("a").each do |a| 14 | @letters.each do |find, replace| 15 | a.content = a.content.gsub(find.to_s, replace.to_s) 16 | end 17 | end 18 | else 19 | body = response.body 20 | end 21 | 22 | [status, headers, Rack::Response.new(body.to_s)] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /ticketee/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/lib/tasks/.keep -------------------------------------------------------------------------------- /ticketee/lib/templates/erb/scaffold/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%%= simple_form_for(@<%= singular_table_name %>) do |f| %> 2 | <%%= f.error_notification %> 3 | 4 |
5 | <%- attributes.each do |attribute| -%> 6 | <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> 7 | <%- end -%> 8 |
9 | 10 |
11 | <%%= f.button :submit %> 12 |
13 | <%% end %> 14 | -------------------------------------------------------------------------------- /ticketee/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/log/.keep -------------------------------------------------------------------------------- /ticketee/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 | -------------------------------------------------------------------------------- /ticketee/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 | -------------------------------------------------------------------------------- /ticketee/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /ticketee/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/public/favicon.ico -------------------------------------------------------------------------------- /ticketee/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/admin/application_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Admin::ApplicationController, type: :controller do 4 | let(:user) { FactoryGirl.create(:user) } 5 | 6 | before do 7 | allow(controller).to receive(:authenticate_user!) 8 | allow(controller).to receive(:current_user).and_return(user) 9 | end 10 | 11 | context "non-admin users" do 12 | it "are not able to access the index action" do 13 | get :index 14 | 15 | expect(response).to redirect_to "/" 16 | expect(flash[:alert]).to eq "You must be an admin to do that." 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/admin/projects_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Admin::ProjectsController, type: :controller do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/admin/states_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Admin::StatesController, type: :controller do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/api/tickets_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe API::TicketsController, type: :controller do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/attachments_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe AttachmentsController, type: :controller do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/comments_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe CommentsController, type: :controller do 4 | let(:user) { FactoryGirl.create(:user) } 5 | let(:project) { Project.create!(name: "Ticketee") } 6 | let(:state) { State.create!(name: "Hacked") } 7 | 8 | let(:ticket) do 9 | project.tickets.create(name: "State transitions", 10 | description: "Can't be hacked.", author: user) 11 | end 12 | 13 | context "a user without permission to set state" do 14 | before :each do 15 | assign_role!(user, :editor, project) 16 | sign_in user 17 | end 18 | 19 | it "cannot transition a state by passing through state_id" do 20 | post :create, { comment: { text: "Did I hack it??", 21 | state_id: state.id }, 22 | ticket_id: ticket.id } 23 | ticket.reload 24 | expect(ticket.state).to be_nil 25 | end 26 | end 27 | 28 | context "a user without permission to tag a ticket" do 29 | before do 30 | assign_role!(user, :editor, project) 31 | sign_in user 32 | end 33 | 34 | it "cannot tag a ticket when creating a comment" do 35 | post :create, { comment: { text: "Tag!", 36 | tag_names: "one two" }, 37 | ticket_id: ticket.id } 38 | ticket.reload 39 | expect(ticket.tags).to be_empty 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/projects_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ProjectsController, type: :controller do 4 | it "handles a missing project correctly" do 5 | get :show, id: "not-here" 6 | 7 | expect(response).to redirect_to(projects_path) 8 | 9 | message = "The project you were looking for could not be found." 10 | expect(flash[:alert]).to eq message 11 | end 12 | 13 | it "handles permission errors by redirecting to a safe place" do 14 | allow(controller).to receive(:current_user) 15 | 16 | project = FactoryGirl.create(:project) 17 | get :show, id: project 18 | 19 | expect(response).to redirect_to(root_path) 20 | message = "You aren't allowed to do that." 21 | expect(flash[:alert]).to eq message 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/tags_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe TagsController, type: :controller do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/spec/controllers/tickets_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe TicketsController, type: :controller do 4 | let(:project) { FactoryGirl.create(:project) } 5 | let(:user) { FactoryGirl.create(:user) } 6 | 7 | before :each do 8 | assign_role!(user, :editor, project) 9 | sign_in user 10 | end 11 | 12 | it "can create tickets, but not tag them" do 13 | post :create, ticket: { name: "New ticket!", 14 | description: "Brand spankin' new", 15 | tag_names: "these are tags" }, 16 | project_id: project.id 17 | expect(Ticket.last.tags).to be_empty 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /ticketee/spec/factories/attachment_factory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :attachment do 3 | transient do 4 | file_to_attach "spec/fixtures/speed.txt" 5 | end 6 | 7 | file { File.open file_to_attach } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ticketee/spec/factories/comment_factory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :comment do 3 | text { "A comment describing some changes that should be made 4 | to this ticket." } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /ticketee/spec/factories/project_factory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :project do 3 | name "Example project" 4 | end 5 | end -------------------------------------------------------------------------------- /ticketee/spec/factories/state_factory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :state do 3 | name "A state" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ticketee/spec/factories/ticket_factory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :ticket do 3 | name "Example ticket" 4 | description "An example ticket, nothing more" 5 | end 6 | end -------------------------------------------------------------------------------- /ticketee/spec/factories/user_factory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user do 3 | sequence(:email) { |n| "test#{n}@example.com" } 4 | password "password" 5 | 6 | trait :admin do 7 | admin true 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /ticketee/spec/features/admin/archiving_users_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "An admin can archive users" do 4 | let(:admin_user) { FactoryGirl.create(:user, :admin) } 5 | let(:user) { FactoryGirl.create(:user) } 6 | 7 | before do 8 | login_as(admin_user) 9 | end 10 | 11 | scenario "successfully" do 12 | visit admin_user_path(user) 13 | click_link "Archive User" 14 | 15 | expect(page).to have_content "User has been archived" 16 | expect(page).not_to have_content user.email 17 | end 18 | 19 | scenario "but cannot archive themselves" do 20 | visit admin_user_path(admin_user) 21 | click_link "Archive User" 22 | 23 | expect(page).to have_content "You cannot archive yourself!" 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /ticketee/spec/features/admin/creating_projects_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can create new projects" do 4 | before do 5 | login_as(FactoryGirl.create(:user, :admin)) 6 | 7 | visit "/" 8 | 9 | click_link "New Project" 10 | end 11 | 12 | scenario "with valid attributes" do 13 | fill_in "Name", with: "Sublime Text 3" 14 | fill_in "Description", with: "A text editor for everyone" 15 | click_button "Create Project" 16 | 17 | expect(page).to have_content "Project has been created." 18 | 19 | project = Project.find_by(name: "Sublime Text 3") 20 | expect(page.current_url).to eq project_url(project) 21 | 22 | title = "Sublime Text 3 - Projects - Ticketee" 23 | expect(page).to have_title title 24 | end 25 | 26 | scenario "when providing invalid attributes" do 27 | click_button "Create Project" 28 | 29 | expect(page).to have_content "Project has not been created." 30 | expect(page).to have_content "Name can't be blank" 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /ticketee/spec/features/admin/creating_states_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Admins can create new states for tickets" do 4 | before do 5 | login_as(FactoryGirl.create(:user, :admin)) 6 | end 7 | 8 | scenario "with valid details" do 9 | visit admin_root_path 10 | click_link "States" 11 | click_link "New State" 12 | 13 | fill_in "Name", with: "Won't Fix" 14 | fill_in "Color", with: "orange" 15 | click_button "Create State" 16 | 17 | expect(page).to have_content "State has been created." 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /ticketee/spec/features/admin/creating_users_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Admins can create new users" do 4 | let(:admin) { FactoryGirl.create(:user, :admin) } 5 | 6 | before do 7 | login_as(admin) 8 | visit "/" 9 | click_link "Admin" 10 | click_link "Users" 11 | click_link "New User" 12 | end 13 | 14 | scenario "with valid credentials" do 15 | fill_in "Email", with: "newbie@example.com" 16 | fill_in "Password", with: "password" 17 | click_button "Create User" 18 | expect(page).to have_content "User has been created." 19 | end 20 | 21 | scenario "when the new user is an admin" do 22 | fill_in "Email", with: "admin@example.com" 23 | fill_in "Password", with: "password" 24 | check "Is an admin?" 25 | click_button "Create User" 26 | expect(page).to have_content "User has been created." 27 | expect(page).to have_content "admin@example.com (Admin)" 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /ticketee/spec/features/admin/deleting_projects_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can delete projects" do 4 | before do 5 | login_as(FactoryGirl.create(:user, :admin)) 6 | end 7 | 8 | scenario "successfully" do 9 | FactoryGirl.create(:project, name: "Sublime Text 3") 10 | 11 | visit "/" 12 | click_link "Sublime Text 3" 13 | click_link "Delete Project" 14 | 15 | expect(page).to have_content "Project has been deleted." 16 | expect(page.current_url).to eq projects_url 17 | expect(page).to have_no_content "Sublime Text 3" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /ticketee/spec/features/admin/editing_users_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Admins can change a user's details" do 4 | let(:admin_user) { FactoryGirl.create(:user, :admin) } 5 | let(:user) { FactoryGirl.create(:user) } 6 | 7 | before do 8 | login_as(admin_user) 9 | visit admin_user_path(user) 10 | click_link "Edit User" 11 | end 12 | 13 | scenario "with valid details" do 14 | fill_in "Email", with: "newguy@example.com" 15 | click_button "Update User" 16 | 17 | expect(page).to have_content "User has been updated." 18 | expect(page).to have_content "newguy@example.com" 19 | expect(page).to_not have_content user.email 20 | end 21 | 22 | scenario "when toggling a user's admin ability" do 23 | check "Is an admin?" 24 | click_button "Update User" 25 | 26 | expect(page).to have_content "User has been updated." 27 | expect(page).to have_content "#{user.email} (Admin)" 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /ticketee/spec/features/admin/managing_roles_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Admins can manage a user's roles" do 4 | let(:admin) { FactoryGirl.create(:user, :admin) } 5 | let(:user) { FactoryGirl.create(:user) } 6 | 7 | let!(:ie) { FactoryGirl.create(:project, name: "Internet Explorer") } 8 | let!(:st3) { FactoryGirl.create(:project, name: "Sublime Text 3") } 9 | 10 | before do 11 | login_as(admin) 12 | end 13 | 14 | scenario "when assigning roles to an existing user" do 15 | visit admin_user_path(user) 16 | click_link "Edit User" 17 | 18 | select "Viewer", from: "Internet Explorer" 19 | select "Manager", from: "Sublime Text 3" 20 | 21 | click_button "Update User" 22 | expect(page).to have_content "User has been updated" 23 | 24 | click_link user.email 25 | expect(page).to have_content "Internet Explorer: Viewer" 26 | expect(page).to have_content "Sublime Text 3: Manager" 27 | end 28 | 29 | scenario "when assigning roles to a new user" do 30 | visit new_admin_user_path 31 | 32 | fill_in "Email", with: "newuser@ticketee.com" 33 | fill_in "Password", with: "password" 34 | 35 | select "Editor", from: "Internet Explorer" 36 | click_button "Create User" 37 | 38 | click_link "newuser@ticketee.com" 39 | expect(page).to have_content "Internet Explorer: Editor" 40 | expect(page).not_to have_content "Sublime Text 3" 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /ticketee/spec/features/admin/managing_states_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Admins can manage states" do 4 | let!(:state) { FactoryGirl.create :state, name: "New" } 5 | 6 | before do 7 | login_as(FactoryGirl.create(:user, :admin)) 8 | visit admin_states_path 9 | end 10 | 11 | scenario "and mark a state as default" do 12 | within list_item("New") do 13 | click_link "Make Default" 14 | end 15 | 16 | expect(page).to have_content "'New' is now the default state." 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /ticketee/spec/features/creating_comments_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can comment on tickets" do 4 | let(:user) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project) } 6 | let(:ticket) { FactoryGirl.create(:ticket, 7 | project: project, author: user) } 8 | 9 | before do 10 | login_as(user) 11 | assign_role!(user, :manager, project) 12 | end 13 | 14 | scenario "with valid attributes" do 15 | visit project_ticket_path(project, ticket) 16 | 17 | fill_in "Text", with: "Added a comment!" 18 | click_button "Create Comment" 19 | 20 | expect(page).to have_content "Comment has been created." 21 | within("#comments") do 22 | expect(page).to have_content "Added a comment!" 23 | end 24 | end 25 | 26 | scenario "with invalid attributes" do 27 | visit project_ticket_path(project, ticket) 28 | click_button "Create Comment" 29 | 30 | expect(page).to have_content "Comment has not been created." 31 | end 32 | 33 | scenario "when changing a ticket's state" do 34 | FactoryGirl.create(:state, name: "Open") 35 | 36 | visit project_ticket_path(project, ticket) 37 | fill_in "Text", with: "This is a real issue" 38 | select "Open", from: "State" 39 | click_button "Create Comment" 40 | 41 | expect(page).to have_content "Comment has been created." 42 | within("#ticket .state") do 43 | expect(page).to have_content "Open" 44 | end 45 | within("#comments") do 46 | expect(page).to have_content "state changed to Open" 47 | end 48 | end 49 | 50 | scenario "but cannot change the state without permission" do 51 | assign_role!(user, :editor, project) 52 | visit project_ticket_path(project, ticket) 53 | 54 | expect(page).not_to have_select "State" 55 | end 56 | 57 | scenario "when adding a new tag to a ticket" do 58 | visit project_ticket_path(project, ticket) 59 | expect(page).not_to have_content "bug" 60 | 61 | fill_in "Text", with: "Adding the bug tag" 62 | fill_in "Tags", with: "bug" 63 | click_button "Create Comment" 64 | 65 | expect(page).to have_content "Comment has been created." 66 | within("#ticket #tags") do 67 | expect(page).to have_content "bug" 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /ticketee/spec/features/deleting_tags_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can delete unwanted tags from a ticket" do 4 | let(:user) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project) } 6 | let(:ticket) do 7 | FactoryGirl.create(:ticket, project: project, 8 | tag_names: "ThisTagMustDie", author: user) 9 | end 10 | 11 | before do 12 | login_as(user) 13 | assign_role!(user, :manager, project) 14 | visit project_ticket_path(project, ticket) 15 | end 16 | 17 | scenario "successfully", js: true do 18 | within tag("ThisTagMustDie") do 19 | click_link "remove" 20 | end 21 | expect(page).to_not have_content "ThisTagMustDie" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /ticketee/spec/features/deleting_tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can delete tickets" do 4 | let(:author) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project) } 6 | let(:ticket) do 7 | FactoryGirl.create(:ticket, project: project, author: author) 8 | end 9 | 10 | before do 11 | login_as(author) 12 | assign_role!(author, :manager, project) 13 | visit project_ticket_path(project, ticket) 14 | end 15 | 16 | scenario "successfully" do 17 | click_link "Delete Ticket" 18 | 19 | expect(page).to have_content "Ticket has been deleted." 20 | expect(page.current_url).to eq project_url(project) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /ticketee/spec/features/editing_projects_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Project managers can edit existing projects" do 4 | let(:user) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project, name: "Sublime Text 3") } 6 | 7 | before do 8 | login_as(user) 9 | assign_role!(user, :manager, project) 10 | 11 | visit "/" 12 | click_link "Sublime Text 3" 13 | click_link "Edit Project" 14 | end 15 | 16 | scenario "with valid attributes" do 17 | fill_in "Name", with: "Sublime Text 4 beta" 18 | click_button "Update Project" 19 | 20 | expect(page).to have_content "Project has been updated." 21 | expect(page).to have_content "Sublime Text 4 beta" 22 | end 23 | 24 | scenario "when providing invalid attributes" do 25 | fill_in "Name", with: "" 26 | click_button "Update Project" 27 | 28 | expect(page).to have_content "Project has not been updated." 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /ticketee/spec/features/editing_tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can edit existing tickets" do 4 | let(:author) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project) } 6 | let(:ticket) do 7 | FactoryGirl.create(:ticket, project: project, author: author) 8 | end 9 | 10 | before do 11 | assign_role!(author, :editor, project) 12 | login_as(author) 13 | 14 | visit project_ticket_path(project, ticket) 15 | click_link "Edit Ticket" 16 | end 17 | 18 | scenario "with valid attributes" do 19 | fill_in "Name", with: "Make it really shiny!" 20 | click_button "Update Ticket" 21 | 22 | expect(page).to have_content "Ticket has been updated." 23 | 24 | within("#ticket h2") do 25 | expect(page).to have_content "Make it really shiny!" 26 | expect(page).not_to have_content ticket.name 27 | end 28 | end 29 | 30 | scenario "with invalid attributes" do 31 | fill_in "Name", with: "" 32 | click_button "Update Ticket" 33 | 34 | expect(page).to have_content "Ticket has not been updated." 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /ticketee/spec/features/hidden_links_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can only see the appropriate links" do 4 | let(:project) { FactoryGirl.create(:project) } 5 | let(:user) { FactoryGirl.create(:user) } 6 | let(:admin) { FactoryGirl.create(:user, :admin) } 7 | let(:ticket) do 8 | FactoryGirl.create(:ticket, project: project, author: user) 9 | end 10 | 11 | context "anonymous users" do 12 | scenario "cannot see the New Project link" do 13 | visit "/" 14 | expect(page).not_to have_link "New Project" 15 | end 16 | end 17 | 18 | context "non-admin users (project viewers)" do 19 | before do 20 | login_as(user) 21 | assign_role!(user, :viewer, project) 22 | end 23 | 24 | scenario "cannot see the New Project link" do 25 | visit "/" 26 | expect(page).not_to have_link "New Project" 27 | end 28 | 29 | scenario "cannot see the Edit Project link" do 30 | visit project_path(project) 31 | expect(page).not_to have_link "Edit Project" 32 | end 33 | 34 | scenario "cannot see the Delete Project link" do 35 | visit project_path(project) 36 | expect(page).not_to have_link "Delete Project" 37 | end 38 | 39 | scenario "cannot see the New Ticket link" do 40 | visit project_path(project) 41 | expect(page).not_to have_link "New Ticket" 42 | end 43 | 44 | scenario "cannot see the Edit Ticket link" do 45 | visit project_ticket_path(project, ticket) 46 | expect(page).not_to have_link "Edit Ticket" 47 | end 48 | 49 | scenario "cannot see the Delete Ticket link" do 50 | visit project_ticket_path(project, ticket) 51 | expect(page).not_to have_link "Delete Ticket" 52 | end 53 | 54 | scenario "cannot see the New Comment form" do 55 | visit project_ticket_path(project, ticket) 56 | expect(page).not_to have_heading "New Comment" 57 | end 58 | end 59 | 60 | context "admin users" do 61 | before { login_as(admin) } 62 | 63 | scenario "can see the New Project link" do 64 | visit "/" 65 | expect(page).to have_link "New Project" 66 | end 67 | 68 | scenario "can see the Edit Project link" do 69 | visit project_path(project) 70 | expect(page).to have_link "Edit Project" 71 | end 72 | 73 | scenario "can see the Delete Project link" do 74 | visit project_path(project) 75 | expect(page).to have_link "Delete Project" 76 | end 77 | 78 | scenario "can see the New Ticket link" do 79 | visit project_path(project) 80 | expect(page).to have_link "New Ticket" 81 | end 82 | 83 | scenario "can see the Edit Ticket link" do 84 | visit project_ticket_path(project, ticket) 85 | expect(page).to have_link "Edit Ticket" 86 | end 87 | 88 | scenario "can see the Delete Ticket link" do 89 | visit project_ticket_path(project, ticket) 90 | expect(page).to have_link "Delete Ticket" 91 | end 92 | 93 | scenario "can see the New Comment form" do 94 | visit project_ticket_path(project, ticket) 95 | expect(page).to have_heading "New Comment" 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /ticketee/spec/features/searching_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can search for tickets matching specific criteria" do 4 | let(:user) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project) } 6 | let(:open) { State.create(name: "Open", default: true) } 7 | let(:closed) { State.create(name: "Closed") } 8 | 9 | let!(:ticket_1) do 10 | FactoryGirl.create(:ticket, name: "Create projects", 11 | project: project, author: user, tag_names: "iteration_1", 12 | state: open) 13 | end 14 | 15 | let!(:ticket_2) do 16 | FactoryGirl.create(:ticket, name: "Create users", 17 | project: project, author: user, tag_names: "iteration_2", 18 | state: closed) 19 | end 20 | 21 | before do 22 | assign_role!(user, :manager, project) 23 | login_as(user) 24 | visit project_path(project) 25 | end 26 | 27 | scenario "searching by tag" do 28 | fill_in "Search", with: "tag:iteration_1" 29 | click_button "Search" 30 | within("#tickets") do 31 | expect(page).to have_link "Create projects" 32 | expect(page).to_not have_link "Create users" 33 | end 34 | end 35 | 36 | scenario "searching by state" do 37 | fill_in "Search", with: "state:Open" 38 | click_button "Search" 39 | within("#tickets") do 40 | expect(page).to have_link "Create projects" 41 | expect(page).to_not have_link "Create users" 42 | end 43 | end 44 | 45 | scenario "when clicking on a tag" do 46 | click_link "Create projects" 47 | click_link "iteration_1" 48 | within("#tickets") do 49 | expect(page).to have_content "Create projects" 50 | expect(page).to_not have_content "Create users" 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /ticketee/spec/features/signing_in_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can sign in" do 4 | let!(:user) { FactoryGirl.create(:user) } 5 | 6 | scenario "with valid credentials" do 7 | visit "/" 8 | click_link "Sign in" 9 | fill_in "Email", with: user.email 10 | fill_in "Password", with: "password" 11 | click_button "Sign in" 12 | 13 | expect(page).to have_content "Signed in successfully." 14 | expect(page).to have_content "Signed in as #{user.email}" 15 | end 16 | 17 | scenario "unless they are archived" do 18 | user.archive 19 | 20 | visit "/" 21 | click_link "Sign in" 22 | fill_in "Email", with: user.email 23 | fill_in "Password", with: "password" 24 | click_button "Sign in" 25 | 26 | expect(page).to have_content "Your account has been archived." 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /ticketee/spec/features/signing_out_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Signed-in users can sign out" do 4 | let!(:user) { FactoryGirl.create(:user) } 5 | 6 | before do 7 | login_as(user) 8 | end 9 | 10 | scenario do 11 | visit "/" 12 | click_link "Sign out" 13 | expect(page).to have_content "Signed out successfully." 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /ticketee/spec/features/signing_up_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can sign up" do 4 | scenario "when providing valid details" do 5 | visit "/" 6 | click_link "Sign up" 7 | fill_in "Email", with: "test@example.com" 8 | fill_in "user_password", with: "password" 9 | fill_in "Password confirmation", with: "password" 10 | click_button "Sign up" 11 | expect(page).to have_content("You have signed up successfully.") 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /ticketee/spec/features/ticket_notifications_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can receive notifications about ticket updates" do 4 | let(:alice) { FactoryGirl.create(:user, email: "alice@example.com") } 5 | let(:bob) { FactoryGirl.create(:user, email: "bob@example.com") } 6 | let(:project) { FactoryGirl.create(:project) } 7 | let(:ticket) do 8 | FactoryGirl.create(:ticket, project: project, author: alice) 9 | end 10 | 11 | before do 12 | assign_role!(alice, :manager, project) 13 | assign_role!(bob, :manager, project) 14 | 15 | login_as(bob) 16 | visit project_ticket_path(project, ticket) 17 | end 18 | 19 | scenario "ticket authors automatically receive notifications" do 20 | fill_in "Text", with: "Is it out yet?" 21 | click_button "Create Comment" 22 | 23 | email = find_email!(alice.email) 24 | expected_subject = "[ticketee] #{project.name} - #{ticket.name}" 25 | expect(email.subject).to eq expected_subject 26 | 27 | click_first_link_in_email(email) 28 | expect(current_path).to eq project_ticket_path(project, ticket) 29 | end 30 | 31 | scenario "comment authors are automatically subscribed to a ticket" do 32 | fill_in "Text", with: "Is it out yet?" 33 | click_button "Create Comment" 34 | click_link "Sign out" 35 | 36 | reset_mailer 37 | 38 | login_as(alice) 39 | visit project_ticket_path(project, ticket) 40 | fill_in "Text", with: "Not yet - sorry!" 41 | click_button "Create Comment" 42 | 43 | expect(page).to have_content "Comment has been created." 44 | expect(unread_emails_for(bob.email).count).to eq 1 45 | expect(unread_emails_for(alice.email).count).to eq 0 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /ticketee/spec/features/viewing_attachments_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can view a ticket's attached files" do 4 | let(:user) { FactoryGirl.create :user } 5 | let(:project) { FactoryGirl.create :project } 6 | let(:ticket) { FactoryGirl.create :ticket, project: project, 7 | author: user } 8 | let!(:attachment) { FactoryGirl.create :attachment, ticket: ticket, 9 | file_to_attach: "spec/fixtures/speed.txt" } 10 | 11 | before do 12 | assign_role!(user, :viewer, project) 13 | login_as(user) 14 | end 15 | 16 | scenario "successfully" do 17 | visit project_ticket_path(project, ticket) 18 | click_link "speed.txt" 19 | 20 | expect(current_path).to eq attachment_path(attachment) 21 | expect(page).to have_content "The blink tag can blink faster" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /ticketee/spec/features/viewing_projects_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can view projects" do 4 | let(:user) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project, name: "Sublime Text 3") } 6 | 7 | before do 8 | login_as(user) 9 | assign_role!(user, :viewer, project) 10 | end 11 | 12 | scenario "with the project details" do 13 | visit "/" 14 | click_link "Sublime Text 3" 15 | expect(page.current_url).to eq project_url(project) 16 | end 17 | 18 | scenario "unless they do not have permission" do 19 | FactoryGirl.create(:project, name: "Hidden") 20 | visit "/" 21 | expect(page).not_to have_content "Hidden" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /ticketee/spec/features/viewing_tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can view tickets" do 4 | before do 5 | author = FactoryGirl.create(:user) 6 | 7 | sublime = FactoryGirl.create(:project, name: "Sublime Text 3") 8 | assign_role!(author, :viewer, sublime) 9 | FactoryGirl.create(:ticket, project: sublime, 10 | author: author, name: "Make it shiny!", 11 | description: "Gradients! Starbursts! Oh my!") 12 | 13 | ie = FactoryGirl.create(:project, name: "Internet Explorer") 14 | assign_role!(author, :viewer, ie) 15 | FactoryGirl.create(:ticket, project: ie, author: author, 16 | name: "Standards compliance", description: "Isn't a joke.") 17 | 18 | login_as(author) 19 | visit "/" 20 | end 21 | 22 | scenario "for a given project" do 23 | click_link "Sublime Text 3" 24 | 25 | expect(page).to have_content "Make it shiny!" 26 | expect(page).to_not have_content "Standards compliance" 27 | 28 | click_link "Make it shiny!" 29 | within("#ticket h2") do 30 | expect(page).to have_content "Make it shiny!" 31 | end 32 | 33 | expect(page).to have_content "Gradients! Starbursts! Oh my!" 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /ticketee/spec/features/watching_tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Users can watch and unwatch tickets" do 4 | let(:user) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project) } 6 | let(:ticket) do 7 | FactoryGirl.create(:ticket, project: project, author: user) 8 | end 9 | 10 | before do 11 | assign_role!(user, "viewer", project) 12 | login_as(user) 13 | visit project_ticket_path(project, ticket) 14 | end 15 | 16 | scenario "successfully" do 17 | within("#watchers") do 18 | expect(page).to have_content user.email 19 | end 20 | 21 | click_link "Unwatch" 22 | expect(page).to have_content "You are no longer watching this " + 23 | "ticket." 24 | 25 | within("#watchers") do 26 | expect(page).to_not have_content user.email 27 | end 28 | 29 | click_link "Watch" 30 | expect(page).to have_content "You are now watching this ticket." 31 | 32 | within("#watchers") do 33 | expect(page).to have_content user.email 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /ticketee/spec/fixtures/gradient.txt: -------------------------------------------------------------------------------- 1 | Everything looks better with a gradient! 2 | -------------------------------------------------------------------------------- /ticketee/spec/fixtures/speed.txt: -------------------------------------------------------------------------------- 1 | The blink tag can blink faster if you use the speed="hyper" attribute. 2 | -------------------------------------------------------------------------------- /ticketee/spec/fixtures/spin.txt: -------------------------------------------------------------------------------- 1 | Spinning blink tags have a 200% higher click rate! 2 | -------------------------------------------------------------------------------- /ticketee/spec/mailers/comment_notifier_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe CommentNotifier, type: :mailer do 4 | describe "created" do 5 | let(:project) { FactoryGirl.create(:project) } 6 | let(:ticket_owner) { FactoryGirl.create(:user) } 7 | let(:ticket) do 8 | FactoryGirl.create(:ticket, 9 | project: project, author: ticket_owner) 10 | end 11 | 12 | let(:commenter) { FactoryGirl.create(:user) } 13 | let(:comment) do 14 | Comment.new(ticket: ticket, author: commenter, 15 | text: "Test comment") 16 | end 17 | 18 | let(:email) do 19 | CommentNotifier.created(comment, ticket_owner) 20 | end 21 | 22 | it "sends out an email notification about a new comment" do 23 | expect(email.to).to include ticket_owner.email 24 | title = "#{ticket.name} for #{project.name} has been updated." 25 | expect(email.body.to_s).to include title 26 | expect(email.body.to_s).to include "#{commenter.email} wrote:" 27 | expect(email.body.to_s).to include comment.text 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /ticketee/spec/mailers/previews/comment_notifier_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/comment_notifier 2 | class CommentNotifierPreview < ActionMailer::Preview 3 | 4 | end 5 | -------------------------------------------------------------------------------- /ticketee/spec/policies/attachment_policy_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe AttachmentPolicy do 4 | context "permissions" do 5 | subject { AttachmentPolicy.new(user, attachment) } 6 | 7 | let(:user) { FactoryGirl.create(:user) } 8 | let(:project) { FactoryGirl.create(:project) } 9 | let(:ticket) { FactoryGirl.create(:ticket, project: project) } 10 | let(:attachment) { FactoryGirl.create(:attachment, ticket: ticket) } 11 | 12 | context "for anonymous users" do 13 | let(:user) { nil } 14 | it { should_not permit_action :show } 15 | end 16 | 17 | context "for viewers of the project" do 18 | before { assign_role!(user, :viewer, project) } 19 | it { should permit_action :show } 20 | end 21 | 22 | context "for editors of the project" do 23 | before { assign_role!(user, :editor, project) } 24 | it { should permit_action :show } 25 | end 26 | 27 | context "for managers of the project" do 28 | before { assign_role!(user, :manager, project) } 29 | it { should permit_action :show } 30 | end 31 | 32 | context "for managers of other projects" do 33 | before do 34 | assign_role!(user, :manager, FactoryGirl.create(:project)) 35 | end 36 | it { should_not permit_action :show } 37 | end 38 | 39 | context "for administrators" do 40 | let(:user) { FactoryGirl.create :user, :admin } 41 | it { should permit_action :show } 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ticketee/spec/policies/comment_policy_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe CommentPolicy do 4 | context "permissions" do 5 | subject { CommentPolicy.new(user, comment) } 6 | 7 | let(:user) { FactoryGirl.create(:user) } 8 | let(:project) { FactoryGirl.create(:project) } 9 | let(:ticket) { FactoryGirl.create(:ticket, project: project) } 10 | let(:comment) { FactoryGirl.create(:comment, ticket: ticket) } 11 | 12 | context "for anonymous users" do 13 | let(:user) { nil } 14 | it { should_not permit_action :create } 15 | end 16 | 17 | context "for viewers of the project" do 18 | before { assign_role!(user, :viewer, project) } 19 | it { should_not permit_action :create } 20 | end 21 | 22 | context "for editors of the project" do 23 | before { assign_role!(user, :editor, project) } 24 | it { should permit_action :create } 25 | end 26 | 27 | context "for managers of the project" do 28 | before { assign_role!(user, :manager, project) } 29 | it { should permit_action :create } 30 | end 31 | 32 | context "for managers of other projects" do 33 | before do 34 | assign_role!(user, :manager, FactoryGirl.create(:project)) 35 | end 36 | it { should_not permit_action :create } 37 | end 38 | 39 | context "for administrators" do 40 | let(:user) { FactoryGirl.create :user, :admin } 41 | it { should permit_action :create } 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ticketee/spec/policies/project_policy_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | describe ProjectPolicy do 4 | 5 | let(:user) { User.new } 6 | 7 | subject { ProjectPolicy } 8 | 9 | context "policy_scope" do 10 | subject { Pundit.policy_scope(user, Project) } 11 | 12 | let!(:project) { FactoryGirl.create :project } 13 | let(:user) { FactoryGirl.create :user } 14 | 15 | it "is empty for anonymous users" do 16 | expect(Pundit.policy_scope(nil, Project)).to be_empty 17 | end 18 | 19 | it "includes projects a user is allowed to view" do 20 | assign_role!(user, :viewer, project) 21 | expect(subject).to include(project) 22 | end 23 | 24 | it "doesn't include projects a user is not allowed to view" do 25 | expect(subject).to be_empty 26 | end 27 | 28 | it "returns all projects for admins" do 29 | user.admin = true 30 | expect(subject).to include(project) 31 | end 32 | end 33 | 34 | context "permissions" do 35 | subject { ProjectPolicy.new(user, project) } 36 | 37 | let(:user) { FactoryGirl.create(:user) } 38 | let(:project) { FactoryGirl.create(:project) } 39 | 40 | context "for anonymous users" do 41 | let(:user) { nil } 42 | 43 | it { should_not permit_action :show } 44 | it { should_not permit_action :update } 45 | end 46 | 47 | context "for viewers of the project" do 48 | before { assign_role!(user, :viewer, project) } 49 | 50 | it { should permit_action :show } 51 | it { should_not permit_action :update } 52 | end 53 | 54 | context "for editors of the project" do 55 | before { assign_role!(user, :editor, project) } 56 | 57 | it { should permit_action :show } 58 | it { should_not permit_action :update } 59 | end 60 | 61 | context "for managers of the project" do 62 | before { assign_role!(user, :manager, project) } 63 | 64 | it { should permit_action :show } 65 | it { should permit_action :update } 66 | end 67 | 68 | context "for managers of other projects" do 69 | before do 70 | assign_role!(user, :manager, FactoryGirl.create(:project)) 71 | end 72 | 73 | it { should_not permit_action :show } 74 | it { should_not permit_action :update } 75 | end 76 | 77 | context "for administrators" do 78 | let(:user) { FactoryGirl.create :user, :admin } 79 | 80 | it { should permit_action :show } 81 | it { should permit_action :update } 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /ticketee/spec/policies/ticket_policy_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe TicketPolicy do 4 | context "permissions" do 5 | subject { TicketPolicy.new(user, ticket) } 6 | 7 | let(:user) { FactoryGirl.create(:user) } 8 | let(:project) { FactoryGirl.create(:project) } 9 | let(:ticket) { FactoryGirl.create(:ticket, project: project) } 10 | 11 | context "for anonymous users" do 12 | let(:user) { nil } 13 | 14 | it { should_not permit_action :show } 15 | it { should_not permit_action :create } 16 | it { should_not permit_action :update } 17 | it { should_not permit_action :destroy } 18 | it { should_not permit_action :change_state } 19 | it { should_not permit_action :tag } 20 | end 21 | 22 | context "for viewers of the project" do 23 | before { assign_role!(user, :viewer, project) } 24 | 25 | it { should permit_action :show } 26 | it { should_not permit_action :create } 27 | it { should_not permit_action :update } 28 | it { should_not permit_action :destroy } 29 | it { should_not permit_action :change_state } 30 | it { should_not permit_action :tag } 31 | end 32 | 33 | context "for editors of the project" do 34 | before { assign_role!(user, :editor, project) } 35 | 36 | it { should permit_action :show } 37 | it { should permit_action :create } 38 | it { should_not permit_action :update } 39 | it { should_not permit_action :destroy } 40 | it { should_not permit_action :change_state } 41 | it { should_not permit_action :tag } 42 | 43 | context "when the editor created the ticket" do 44 | before { ticket.author = user } 45 | 46 | it { should permit_action :update } 47 | end 48 | end 49 | 50 | context "for managers of the project" do 51 | before { assign_role!(user, :manager, project) } 52 | 53 | it { should permit_action :show } 54 | it { should permit_action :create } 55 | it { should permit_action :update } 56 | it { should permit_action :destroy } 57 | it { should permit_action :change_state } 58 | it { should permit_action :tag } 59 | end 60 | 61 | context "for managers of other projects" do 62 | before do 63 | assign_role!(user, :manager, FactoryGirl.create(:project)) 64 | end 65 | 66 | it { should_not permit_action :show } 67 | it { should_not permit_action :create } 68 | it { should_not permit_action :update } 69 | it { should_not permit_action :destroy } 70 | it { should_not permit_action :change_state } 71 | it { should_not permit_action :tag } 72 | end 73 | 74 | context "for administrators" do 75 | let(:user) { FactoryGirl.create :user, :admin } 76 | 77 | it { should permit_action :show } 78 | it { should permit_action :create } 79 | it { should permit_action :update } 80 | it { should permit_action :destroy } 81 | it { should permit_action :change_state } 82 | it { should permit_action :tag } 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /ticketee/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 'spec_helper' 4 | require File.expand_path('../../config/environment', __FILE__) 5 | require 'rspec/rails' 6 | # Add additional requires below this line. Rails is not loaded until this point! 7 | require "pundit/rspec" 8 | 9 | # Requires supporting ruby files with custom matchers and macros, etc, in 10 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 11 | # run as spec files by default. This means that files in spec/support that end 12 | # in _spec.rb will both be required and run as specs, causing the specs to be 13 | # run twice. It is recommended that you do not name files matching this glob to 14 | # end with _spec.rb. You can configure this pattern with the --pattern 15 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 16 | # 17 | # The following line is provided for convenience purposes. It has the downside 18 | # of increasing the boot-up time by auto-requiring all files in the support 19 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 20 | # require only the support files necessary. 21 | # 22 | Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 23 | 24 | # Checks for pending migrations before tests are run. 25 | # If you are not using ActiveRecord, you can remove this line. 26 | ActiveRecord::Migration.maintain_test_schema! 27 | 28 | RSpec.configure do |config| 29 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 30 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 31 | 32 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 33 | # examples within a transaction, remove the following line or assign false 34 | # instead of true. 35 | config.use_transactional_fixtures = false 36 | 37 | # RSpec Rails can automatically mix in different behaviours to your tests 38 | # based on their file location, for example enabling you to call `get` and 39 | # `post` in specs under `spec/controllers`. 40 | # 41 | # You can disable this behaviour by removing the line below, and instead 42 | # explicitly tag your specs with their type, e.g.: 43 | # 44 | # RSpec.describe UsersController, :type => :controller do 45 | # # ... 46 | # end 47 | # 48 | # The different available types are documented in the features, such as in 49 | # https://relishapp.com/rspec/rspec-rails/docs 50 | config.infer_spec_type_from_file_location! 51 | 52 | config.include Warden::Test::Helpers, type: :feature 53 | config.after(type: :feature) { Warden.test_reset! } 54 | config.include Devise::TestHelpers, type: :controller 55 | end 56 | -------------------------------------------------------------------------------- /ticketee/spec/requests/api/tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Tickets API" do 4 | let(:user) { FactoryGirl.create(:user) } 5 | let(:project) { FactoryGirl.create(:project) } 6 | let(:state) { FactoryGirl.create(:state, name: "Open") } 7 | let(:ticket) do 8 | FactoryGirl.create(:ticket, project: project, state: state) 9 | end 10 | 11 | before do 12 | assign_role!(user, :manager, project) 13 | user.generate_api_key 14 | end 15 | 16 | context "as an authenticated user" do 17 | let(:headers) do 18 | { "HTTP_AUTHORIZATION" => "Token token=#{user.api_key}" } 19 | end 20 | 21 | it "retrieves a ticket's information" do 22 | get api_project_ticket_path(project, ticket, format: :json), 23 | {}, headers 24 | expect(response.status).to eq 200 25 | 26 | json = TicketSerializer.new(ticket).to_json 27 | expect(response.body).to eq json 28 | end 29 | 30 | it "can create a ticket" do 31 | params = { 32 | format: "json", 33 | ticket: { 34 | name: "Test Ticket", 35 | description: "Just testing things out." 36 | } 37 | } 38 | 39 | post api_project_tickets_path(project, params), {}, headers 40 | expect(response.status).to eq 201 41 | 42 | json = TicketSerializer.new(Ticket.last).to_json 43 | expect(response.body).to eq json 44 | end 45 | 46 | it "cannot create a ticket with invalid data" do 47 | params = { 48 | format: "json", 49 | ticket: { 50 | name: "", description: "" 51 | } 52 | } 53 | post api_project_tickets_path(project, params), {}, headers 54 | 55 | expect(response.status).to eq 422 56 | json = { 57 | "errors" => [ 58 | "Name can't be blank", 59 | "Description can't be blank", 60 | "Description is too short (minimum is 10 characters)" 61 | ] 62 | } 63 | expect(JSON.parse(response.body)).to eq json 64 | end 65 | 66 | context "without permission to view the project" do 67 | before do 68 | user.roles.delete_all 69 | end 70 | 71 | it "responds with a 403" do 72 | get api_project_ticket_path(project, ticket, format: "json"), 73 | {}, headers 74 | expect(response.status).to eq 403 75 | error = { "error" => "Unauthorized" } 76 | expect(JSON.parse(response.body)).to eq error 77 | end 78 | end 79 | end 80 | 81 | context "as an unauthenticated user" do 82 | it "responds with a 401" do 83 | get api_project_ticket_path(project, ticket, format: 'json') 84 | expect(response.status).to eq 401 85 | error = { "error" => "Unauthorized" } 86 | expect(JSON.parse(response.body)).to eq error 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /ticketee/spec/requests/api/v2/tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | describe API::V2::Tickets do 4 | let(:project) { FactoryGirl.create(:project) } 5 | let(:user) { FactoryGirl.create(:user) } 6 | let(:ticket) { FactoryGirl.create(:ticket, project: project) } 7 | let(:url) { "/api/v2/projects/#{project.id}/tickets/#{ticket.id}" } 8 | let(:headers) do 9 | { "HTTP_AUTHORIZATION" => "Token token=#{user.api_key}" } 10 | end 11 | 12 | before do 13 | assign_role!(user, :manager, project) 14 | user.generate_api_key 15 | end 16 | 17 | context "successful requests" do 18 | it "can view a ticket's details" do 19 | get url, {}, headers 20 | 21 | expect(response.status).to eq 200 22 | json = TicketSerializer.new(ticket).to_json 23 | expect(response.body).to eq json 24 | end 25 | end 26 | 27 | context "unsuccessful requests" do 28 | it "doesn't allow requests that don't pass through an API key" do 29 | get url 30 | expect(response.status).to eq 401 31 | expect(response.body).to include "Unauthenticated" 32 | end 33 | 34 | it "doesn't allow requests that pass an invalid API key" do 35 | get url, {}, { "HTTP_AUTHORIZATION" => "Token token=notavalidkey" } 36 | expect(response.status).to eql 401 37 | expect(response.body).to include "Unauthenticated" 38 | end 39 | 40 | it "doesn't allow access a ticket that the user doesn't have permission to" do 41 | project.roles.delete_all 42 | get url, {}, headers 43 | expect(response.status).to eq 404 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /ticketee/spec/support/authorization_helpers.rb: -------------------------------------------------------------------------------- 1 | module AuthorizationHelpers 2 | def assign_role!(user, role, project) 3 | Role.where(user: user, project: project).delete_all 4 | Role.create!(user: user, role: role, project: project) 5 | end 6 | end 7 | 8 | RSpec.configure do |c| 9 | c.include AuthorizationHelpers 10 | end 11 | -------------------------------------------------------------------------------- /ticketee/spec/support/capybara_finders.rb: -------------------------------------------------------------------------------- 1 | module CapybaraFinders 2 | def list_item(content) 3 | find("ul:not(.actions) li", text: content) 4 | end 5 | 6 | def tag(content) 7 | find("div.tag", text: content) 8 | end 9 | end 10 | 11 | RSpec.configure do |c| 12 | c.include CapybaraFinders, type: :feature 13 | end 14 | -------------------------------------------------------------------------------- /ticketee/spec/support/capybara_matchers.rb: -------------------------------------------------------------------------------- 1 | module CapybaraMatchers 2 | def has_heading?(text) 3 | has_css?("h1, h2, h3, h4, h5, h6", text: text) 4 | end 5 | end 6 | 7 | Capybara::Session.include(CapybaraMatchers) 8 | -------------------------------------------------------------------------------- /ticketee/spec/support/database_cleaning.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:suite) do 3 | DatabaseCleaner.strategy = :deletion 4 | DatabaseCleaner.clean_with(:deletion) 5 | end 6 | 7 | config.before(:each) do 8 | DatabaseCleaner.start 9 | end 10 | 11 | config.after(:each) do 12 | DatabaseCleaner.clean 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /ticketee/spec/support/email_spec.rb: -------------------------------------------------------------------------------- 1 | require "email_spec" 2 | 3 | RSpec.configure do |config| 4 | config.include EmailSpec::Helpers 5 | config.include EmailSpec::Matchers 6 | end 7 | -------------------------------------------------------------------------------- /ticketee/spec/support/pundit_matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :permit_action do |action| 2 | match do |policy| 3 | policy.public_send("#{action}?") 4 | end 5 | 6 | failure_message do |policy| 7 | "#{policy.class} does not allow #{policy.user || "nil"} to " + 8 | "perform :#{action}? on #{policy.record}." 9 | end 10 | 11 | failure_message_when_negated do |policy| 12 | "#{policy.class} does not forbid #{policy.user || "nil"} from " + 13 | "performing :#{action}? on #{policy.record}." 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /ticketee/vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /ticketee/vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubysherpas/r4ia_examples/05d2d758da9f83caa0df51878448a30ef807513d/ticketee/vendor/assets/stylesheets/.keep -------------------------------------------------------------------------------- /ticketee_api/tickets.rb: -------------------------------------------------------------------------------- 1 | require "httparty" 2 | 3 | token = "YOUR_TOKEN_GOES_HERE" 4 | url = "http://localhost:3000/api/projects/1/tickets/1.json" 5 | 6 | response = HTTParty.get(url, 7 | headers: { 8 | "Authorization" => "Token token=#{token}" 9 | } 10 | ) 11 | 12 | puts response.parsed_response 13 | --------------------------------------------------------------------------------