├── app ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── product.rb │ ├── invoice.rb │ ├── line_item.rb │ └── download.rb ├── assets │ ├── images │ │ └── .keep │ ├── stylesheets │ │ ├── base │ │ │ ├── _base.scss │ │ │ ├── _grid-settings.scss │ │ │ ├── _tables.scss │ │ │ ├── _lists.scss │ │ │ ├── _buttons.scss │ │ │ ├── _typography.scss │ │ │ ├── _variables.scss │ │ │ └── _forms.scss │ │ ├── application.scss │ │ ├── refills │ │ │ └── _flashes.scss │ │ └── invoice_pdf.scss │ └── javascripts │ │ └── application.js ├── views │ ├── pages │ │ └── .keep │ ├── application │ │ ├── _flashes.html.erb │ │ ├── _javascript.html.erb │ │ └── _analytics.html.erb │ ├── invoices │ │ ├── index.html.erb │ │ ├── show.html.erb │ │ └── pdf.html.erb │ └── layouts │ │ ├── invoice_pdf.html.erb │ │ └── application.html.erb ├── controllers │ ├── concerns │ │ └── .keep │ ├── invoices_controller.rb │ ├── application_controller.rb │ └── downloads_controller.rb └── helpers │ ├── application_helper.rb │ └── flashes_helper.rb ├── lib ├── assets │ └── .keep ├── tasks │ ├── .keep │ ├── bundler_audit.rake │ └── dev.rake └── templates │ └── erb │ └── scaffold │ └── _form.html.erb ├── public ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── spec ├── helpers │ └── .keep ├── lib │ └── .keep ├── controllers │ └── .keep ├── features │ ├── .keep │ └── user_downloads_invoice_pdf_spec.rb ├── support │ ├── features │ │ └── .keep │ ├── matchers │ │ └── .keep │ ├── mixins │ │ └── .keep │ ├── shared_examples │ │ └── .keep │ ├── i18n.rb │ ├── factory_girl.rb │ ├── action_mailer.rb │ ├── capybara_webkit.rb │ ├── shoulda_matchers.rb │ └── database_cleaner.rb ├── models │ ├── invoice_spec.rb │ ├── product_spec.rb │ └── line_item_spec.rb ├── i18n_spec.rb ├── factories.rb ├── spec_helper.rb └── rails_helper.rb ├── .ruby-version ├── vendor └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── .rspec ├── browserslist ├── config ├── initializers │ ├── json_encoding.rb │ ├── disable_xml_params.rb │ ├── cookies_serializer.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── backtrace_silencers.rb │ ├── assets.rb │ ├── wrap_parameters.rb │ ├── inflections.rb │ ├── errors.rb │ └── simple_form.rb ├── environment.rb ├── boot.rb ├── routes.rb ├── environments │ ├── staging.rb │ ├── test.rb │ ├── development.rb │ └── production.rb ├── secrets.yml ├── i18n-tasks.yml ├── smtp.rb ├── locales │ ├── en.yml │ └── simple_form.en.yml ├── database.yml ├── newrelic.yml └── application.rb ├── Procfile ├── bin ├── bundle ├── delayed_job ├── rspec ├── rake ├── rails ├── spring └── setup ├── circle.yml ├── config.ru ├── .gitignore ├── db ├── migrate │ ├── 20151222025546_create_products.rb │ ├── 20151222031031_create_invoices.rb │ ├── 20151222034730_create_line_items.rb │ └── 20151222024559_create_delayed_jobs.rb ├── seeds.rb └── schema.rb ├── .sample.env ├── .hound.yml ├── Rakefile ├── Gemfile ├── README.md └── Gemfile.lock /app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/lib/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.3 2 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/pages/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/features/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/support/features/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/support/matchers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/support/mixins/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/support/shared_examples/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | Last 2 versions 2 | Explorer >= 9 3 | iOS >= 7.1 4 | Android >= 4.4 5 | -------------------------------------------------------------------------------- /config/initializers/json_encoding.rb: -------------------------------------------------------------------------------- 1 | ActiveSupport::JSON::Encoding.time_precision = 0 2 | -------------------------------------------------------------------------------- /app/models/invoice.rb: -------------------------------------------------------------------------------- 1 | class Invoice < ActiveRecord::Base 2 | has_many :line_items 3 | end 4 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -p $PORT -C ./config/puma.rb 2 | worker: bundle exec rake jobs:work 3 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../application', __FILE__) 2 | Rails.application.initialize! 3 | -------------------------------------------------------------------------------- /config/initializers/disable_xml_params.rb: -------------------------------------------------------------------------------- 1 | ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML) 2 | -------------------------------------------------------------------------------- /spec/support/i18n.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include AbstractController::Translation 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/factory_girl.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include FactoryGirl::Syntax::Methods 3 | end 4 | -------------------------------------------------------------------------------- /app/models/line_item.rb: -------------------------------------------------------------------------------- 1 | class LineItem < ActiveRecord::Base 2 | belongs_to :invoice 3 | belongs_to :product 4 | end 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /spec/support/action_mailer.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:each) do 3 | ActionMailer::Base.deliveries.clear 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/support/capybara_webkit.rb: -------------------------------------------------------------------------------- 1 | Capybara.javascript_driver = :webkit 2 | 3 | Capybara::Webkit.configure do |config| 4 | config.block_unknown_urls 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/flashes_helper.rb: -------------------------------------------------------------------------------- 1 | module FlashesHelper 2 | def user_facing_flashes 3 | flash.to_hash.slice("alert", "error", "notice", "success") 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | checkout: 2 | post: 3 | - cp .sample.env .env 4 | database: 5 | override: 6 | - bin/setup 7 | test: 8 | override: 9 | - bin/rake 10 | -------------------------------------------------------------------------------- /spec/models/invoice_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Invoice, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/product_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Product, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/models/line_item_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe LineItem, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: '_invoicer_session' 4 | -------------------------------------------------------------------------------- /spec/support/shoulda_matchers.rb: -------------------------------------------------------------------------------- 1 | Shoulda::Matchers.configure do |config| 2 | config.integrate do |with| 3 | with.test_framework :rspec 4 | with.library :rails 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.keep 2 | *.DS_Store 3 | *.swo 4 | *.swp 5 | /.bundle 6 | /.env 7 | /coverage/* 8 | /db/*.sqlite3 9 | /log/* 10 | /public/system 11 | /public/assets 12 | /tags 13 | /tmp/* 14 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root to: "invoices#index" 3 | 4 | resources :invoices, only: [:index, :show] do 5 | resource :download, only: [:show] 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bin/delayed_job: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) 4 | require 'delayed/command' 5 | Delayed::Command.new(ARGV).daemonize 6 | -------------------------------------------------------------------------------- /config/environments/staging.rb: -------------------------------------------------------------------------------- 1 | require_relative "production" 2 | 3 | Mail.register_interceptor( 4 | RecipientInterceptor.new(ENV.fetch("EMAIL_RECIPIENTS")) 5 | ) 6 | 7 | Rails.application.configure do 8 | # ... 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/invoices_controller.rb: -------------------------------------------------------------------------------- 1 | class InvoicesController < ApplicationController 2 | def index 3 | @invoices = Invoice.all 4 | end 5 | 6 | def show 7 | @invoice = Invoice.find(params[:id]) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/application/_flashes.html.erb: -------------------------------------------------------------------------------- 1 | <% if flash.any? %> 2 |
3 | <% user_facing_flashes.each do |key, value| -%> 4 |
<%= value %>
5 | <% end -%> 6 |
7 | <% end %> 8 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require 'bundler/setup' 8 | load Gem.bin_path('rspec-core', 'rspec') 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 3 | 4 | development: 5 | <<: *default 6 | 7 | test: 8 | <<: *default 9 | 10 | staging: 11 | <<: *default 12 | 13 | production: 14 | <<: *default 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /config/i18n-tasks.yml: -------------------------------------------------------------------------------- 1 | search: 2 | paths: 3 | - "app/controllers" 4 | - "app/helpers" 5 | - "app/presenters" 6 | - "app/views" 7 | 8 | ignore_unused: 9 | - activerecord.* 10 | - date.* 11 | - simple_form.* 12 | - time.* 13 | - titles.* 14 | -------------------------------------------------------------------------------- /app/views/invoices/index.html.erb: -------------------------------------------------------------------------------- 1 |

Invoices

2 | 3 | <% @invoices.each do |invoice| %> 4 | 9 | <% end %> 10 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../../config/application', __FILE__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /db/migrate/20151222025546_create_products.rb: -------------------------------------------------------------------------------- 1 | class CreateProducts < ActiveRecord::Migration 2 | def change 3 | create_table :products do |t| 4 | t.string :description 5 | t.string :item_number 6 | t.money :price 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.sample.env: -------------------------------------------------------------------------------- 1 | # https://github.com/ddollar/forego 2 | ASSET_HOST=localhost:3000 3 | APPLICATION_HOST=localhost:3000 4 | RACK_ENV=development 5 | SECRET_KEY_BASE=development_secret 6 | EXECJS_RUNTIME=Node 7 | SMTP_ADDRESS=smtp.example.com 8 | SMTP_DOMAIN=example.com 9 | SMTP_PASSWORD=password 10 | SMTP_USERNAME=username 11 | -------------------------------------------------------------------------------- /app/views/application/_javascript.html.erb: -------------------------------------------------------------------------------- 1 | <%= javascript_include_tag :application %> 2 | 3 | <%= yield :javascript %> 4 | 5 | <%= render "analytics" %> 6 | 7 | <% if Rails.env.test? %> 8 | <%= javascript_tag do %> 9 | $.fx.off = true; 10 | $.ajaxSetup({ async: false }); 11 | <% end %> 12 | <% end %> 13 | -------------------------------------------------------------------------------- /db/migrate/20151222031031_create_invoices.rb: -------------------------------------------------------------------------------- 1 | class CreateInvoices < ActiveRecord::Migration 2 | def change 3 | create_table :invoices do |t| 4 | t.string :account 5 | t.string :number 6 | t.date :date 7 | t.text :notes 8 | 9 | t.timestamps null: false 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/smtp.rb: -------------------------------------------------------------------------------- 1 | SMTP_SETTINGS = { 2 | address: ENV.fetch("SMTP_ADDRESS"), # example: "smtp.sendgrid.net" 3 | authentication: :plain, 4 | domain: ENV.fetch("SMTP_DOMAIN"), # example: "heroku.com" 5 | enable_starttls_auto: true, 6 | password: ENV.fetch("SMTP_PASSWORD"), 7 | port: "587", 8 | user_name: ENV.fetch("SMTP_USERNAME") 9 | } 10 | -------------------------------------------------------------------------------- /db/migrate/20151222034730_create_line_items.rb: -------------------------------------------------------------------------------- 1 | class CreateLineItems < ActiveRecord::Migration 2 | def change 3 | create_table :line_items do |t| 4 | t.belongs_to :invoice, index: true, foreign_key: true 5 | t.integer :quantity 6 | t.references :product 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/tasks/bundler_audit.rake: -------------------------------------------------------------------------------- 1 | if Rails.env.development? || Rails.env.test? 2 | require "bundler/audit/cli" 3 | 4 | namespace :bundler do 5 | desc "Updates the ruby-advisory-db and runs audit" 6 | task :audit do 7 | %w(update check).each do |command| 8 | Bundler::Audit::CLI.start [command] 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | date: 3 | formats: 4 | default: 5 | "%m/%d/%Y" 6 | with_weekday: 7 | "%a %m/%d/%y" 8 | 9 | time: 10 | formats: 11 | default: 12 | "%a, %b %-d, %Y at %r" 13 | date: 14 | "%b %-d, %Y" 15 | short: 16 | "%B %d" 17 | 18 | titles: 19 | application: Invoicer 20 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_base.scss: -------------------------------------------------------------------------------- 1 | // Bitters 1.1.0 2 | // http://bitters.bourbon.io 3 | // Copyright 2013-2015 thoughtbot, inc. 4 | // MIT License 5 | 6 | @import "variables"; 7 | 8 | // Neat Settings -- uncomment if using Neat -- must be imported before Neat 9 | // @import "grid-settings"; 10 | 11 | @import "buttons"; 12 | @import "forms"; 13 | @import "lists"; 14 | @import "tables"; 15 | @import "typography"; 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/layouts/invoice_pdf.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 |
13 | <%= yield %> 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_grid-settings.scss: -------------------------------------------------------------------------------- 1 | @import "neat-helpers"; // or "../neat/neat-helpers" when not in Rails 2 | 3 | // Neat Overrides 4 | // $column: 90px; 5 | // $gutter: 30px; 6 | // $grid-columns: 12; 7 | // $max-width: 1200px; 8 | 9 | // Neat Breakpoints 10 | $medium-screen: 600px; 11 | $large-screen: 900px; 12 | 13 | $medium-screen-up: new-breakpoint(min-width $medium-screen 4); 14 | $large-screen-up: new-breakpoint(min-width $large-screen 8); 15 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | # See https://houndci.com/configuration for help. 2 | coffeescript: 3 | # config_file: .coffeescript-style.json 4 | enabled: true 5 | haml: 6 | # config_file: .haml-style.yml 7 | enabled: true 8 | javascript: 9 | # config_file: .javascript-style.json 10 | enabled: true 11 | # ignore_file: .javascript_ignore 12 | ruby: 13 | # config_file: .ruby-style.yml 14 | enabled: true 15 | scss: 16 | # config_file: .scss-style.yml 17 | enabled: true 18 | -------------------------------------------------------------------------------- /spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:suite) do 3 | DatabaseCleaner.clean_with(:deletion) 4 | end 5 | 6 | config.before(:each) do 7 | DatabaseCleaner.strategy = :transaction 8 | end 9 | 10 | config.before(:each, js: true) do 11 | DatabaseCleaner.strategy = :deletion 12 | end 13 | 14 | config.before(:each) do 15 | DatabaseCleaner.start 16 | end 17 | 18 | config.after(:each) do 19 | DatabaseCleaner.clean 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /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 | task(:default).clear 8 | task default: [:spec] 9 | 10 | if defined? RSpec 11 | task(:spec).clear 12 | RSpec::Core::RakeTask.new(:spec) do |t| 13 | t.verbose = false 14 | end 15 | end 16 | 17 | task default: "bundler:audit" 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_tables.scss: -------------------------------------------------------------------------------- 1 | table { 2 | border-collapse: collapse; 3 | font-feature-settings: "kern", "liga", "tnum"; 4 | margin: $small-spacing 0; 5 | table-layout: fixed; 6 | width: 100%; 7 | } 8 | 9 | th { 10 | border-bottom: 1px solid shade($base-border-color, 25%); 11 | font-weight: 600; 12 | padding: $small-spacing 0; 13 | text-align: left; 14 | } 15 | 16 | td { 17 | border-bottom: $base-border; 18 | padding: $small-spacing 0; 19 | } 20 | 21 | tr, 22 | td, 23 | th { 24 | vertical-align: middle; 25 | } 26 | -------------------------------------------------------------------------------- /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 = (ENV["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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/i18n_spec.rb: -------------------------------------------------------------------------------- 1 | require 'i18n/tasks' 2 | 3 | RSpec.describe 'I18n' do 4 | let(:i18n) { I18n::Tasks::BaseTask.new } 5 | let(:missing_keys) { i18n.missing_keys } 6 | let(:unused_keys) { i18n.unused_keys } 7 | 8 | it 'does not have missing keys' do 9 | expect(missing_keys).to be_empty, 10 | "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them" 11 | end 12 | 13 | it 'does not have unused keys' do 14 | expect(unused_keys).to be_empty, 15 | "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_lists.scss: -------------------------------------------------------------------------------- 1 | ul, 2 | ol { 3 | list-style-type: none; 4 | margin: 0; 5 | padding: 0; 6 | 7 | &%default-ul { 8 | list-style-type: disc; 9 | margin-bottom: $small-spacing; 10 | padding-left: $base-spacing; 11 | } 12 | 13 | &%default-ol { 14 | list-style-type: decimal; 15 | margin-bottom: $small-spacing; 16 | padding-left: $base-spacing; 17 | } 18 | } 19 | 20 | dl { 21 | margin-bottom: $small-spacing; 22 | 23 | dt { 24 | font-weight: bold; 25 | margin-top: $small-spacing; 26 | } 27 | 28 | dd { 29 | margin: 0; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @import "normalize-rails"; 4 | @import "bourbon"; 5 | @import "base/grid-settings"; 6 | @import "neat"; 7 | @import "base/base"; 8 | @import "refills/flashes"; 9 | 10 | .container { 11 | margin: 30px auto; 12 | max-width: 980px; 13 | } 14 | 15 | 16 | .line-items { 17 | .quantity { 18 | width: 15%; 19 | } 20 | 21 | .item-number { 22 | width: 15%; 23 | } 24 | 25 | .description { 26 | width: 50%; 27 | } 28 | 29 | .unit-price, .total { 30 | width: 10%; 31 | } 32 | } 33 | 34 | .download { 35 | @include button(simple) 36 | } 37 | -------------------------------------------------------------------------------- /spec/factories.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :invoice do 3 | account { "#{Random.rand(10..99)}-#{Random.rand(10000..99999)}" } 4 | number { "#{Random.rand(1..9)}-#{Random.rand(10..99)}-#{Random.rand(10000..99999)}" } 5 | date { Random.rand(1..25).days.ago } 6 | notes "Make sure to deliver between 3:15 and 3:28, otherwise Mike is likely to be on break." 7 | end 8 | 9 | factory :product do 10 | description "A multi-purpose tool" 11 | item_number "tool-124-ab2" 12 | price 29.95 13 | end 14 | 15 | factory :line_item do 16 | invoice 17 | quantity { Random.rand(1..9) } 18 | product 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/download.rb: -------------------------------------------------------------------------------- 1 | require "render_anywhere" 2 | 3 | class Download 4 | include RenderAnywhere 5 | 6 | def initialize(invoice) 7 | @invoice = invoice 8 | end 9 | 10 | def to_pdf 11 | kit = PDFKit.new(as_html) 12 | kit.to_file("tmp/invoice.pdf") 13 | end 14 | 15 | def filename 16 | "Invoice #{invoice.number}.pdf" 17 | end 18 | 19 | def render_attributes 20 | { 21 | template: "invoices/pdf", 22 | layout: "invoice_pdf", 23 | locals: { invoice: invoice } 24 | } 25 | end 26 | 27 | private 28 | 29 | attr_reader :invoice 30 | 31 | def as_html 32 | render render_attributes 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | development: &default 2 | adapter: postgresql 3 | database: invoicer_development 4 | encoding: utf8 5 | host: localhost 6 | min_messages: warning 7 | pool: <%= Integer(ENV.fetch("DB_POOL", 5)) %> 8 | reaping_frequency: <%= Integer(ENV.fetch("DB_REAPING_FREQUENCY", 10)) %> 9 | timeout: 5000 10 | 11 | test: 12 | <<: *default 13 | database: invoicer_test 14 | 15 | production: &deploy 16 | encoding: utf8 17 | min_messages: warning 18 | pool: <%= [Integer(ENV.fetch("MAX_THREADS", 5)), Integer(ENV.fetch("DB_POOL", 5))].max %> 19 | timeout: 5000 20 | url: <%= ENV.fetch("DATABASE_URL", "") %> 21 | 22 | staging: *deploy 23 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV.fetch("COVERAGE", false) 2 | require "simplecov" 3 | SimpleCov.start "rails" 4 | end 5 | 6 | require "webmock/rspec" 7 | 8 | # http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 9 | RSpec.configure do |config| 10 | config.expect_with :rspec do |expectations| 11 | expectations.syntax = :expect 12 | end 13 | 14 | config.mock_with :rspec do |mocks| 15 | mocks.syntax = :expect 16 | mocks.verify_partial_doubles = true 17 | end 18 | 19 | config.example_status_persistence_file_path = "tmp/rspec_examples.txt" 20 | config.order = :random 21 | end 22 | 23 | WebMock.disable_net_connect!(allow_localhost: true) 24 | -------------------------------------------------------------------------------- /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_tree . 16 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%# 8 | Configure default and controller-, and view-specific titles in 9 | config/locales/en.yml. For more see: 10 | https://github.com/calebthompson/title#usage 11 | %> 12 | <%= title %> 13 | <%= stylesheet_link_tag :application, media: "all" %> 14 | <%= csrf_meta_tags %> 15 | 16 | 17 |
18 | <%= render "flashes" -%> 19 | <%= yield %> 20 | <%= render "javascript" %> 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Set up Rails app. Run this script immediately after cloning the codebase. 4 | # https://github.com/thoughtbot/guides/tree/master/protocol 5 | 6 | # Exit if any subcommand fails 7 | set -e 8 | 9 | # Set up Ruby dependencies via Bundler 10 | gem install bundler --conservative 11 | bundle check || bundle install 12 | 13 | # Set up configurable environment variables 14 | if [ ! -f .env ]; then 15 | cp .sample.env .env 16 | fi 17 | 18 | # Set up database and add any development seed data 19 | bin/rake dev:prime 20 | 21 | # Add binstubs to PATH via export PATH=".git/safe/../../bin:$PATH" in ~/.zshenv 22 | mkdir -p .git/safe 23 | 24 | # Only if this isn't CI 25 | # if [ -z "$CI" ]; then 26 | # fi 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | 3 | require File.expand_path("../../config/environment", __FILE__) 4 | abort("DATABASE_URL environment variable is set") if ENV["DATABASE_URL"] 5 | 6 | require "rspec/rails" 7 | 8 | Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |file| require file } 9 | 10 | module Features 11 | # Extend this module in spec/support/features/*.rb 12 | include Formulaic::Dsl 13 | end 14 | 15 | RSpec.configure do |config| 16 | config.include Features, type: :feature 17 | config.infer_base_class_for_anonymous_controllers = false 18 | config.infer_spec_type_from_file_location! 19 | config.use_transactional_fixtures = false 20 | end 21 | 22 | ActiveRecord::Migration.maintain_test_schema! 23 | -------------------------------------------------------------------------------- /config/initializers/errors.rb: -------------------------------------------------------------------------------- 1 | require "net/http" 2 | require "net/smtp" 3 | 4 | # Example: 5 | # begin 6 | # some http call 7 | # rescue *HTTP_ERRORS => error 8 | # notify_hoptoad error 9 | # end 10 | 11 | HTTP_ERRORS = [ 12 | EOFError, 13 | Errno::ECONNRESET, 14 | Errno::EINVAL, 15 | Net::HTTPBadResponse, 16 | Net::HTTPHeaderSyntaxError, 17 | Net::ProtocolError, 18 | Timeout::Error 19 | ] 20 | 21 | SMTP_SERVER_ERRORS = [ 22 | IOError, 23 | Net::SMTPAuthenticationError, 24 | Net::SMTPServerBusy, 25 | Net::SMTPUnknownError, 26 | TimeoutError 27 | ] 28 | 29 | SMTP_CLIENT_ERRORS = [ 30 | Net::SMTPFatalError, 31 | Net::SMTPSyntaxError 32 | ] 33 | 34 | SMTP_ERRORS = SMTP_SERVER_ERRORS + SMTP_CLIENT_ERRORS 35 | -------------------------------------------------------------------------------- /app/controllers/downloads_controller.rb: -------------------------------------------------------------------------------- 1 | class DownloadsController < ApplicationController 2 | def show 3 | respond_to do |format| 4 | format.pdf { send_invoice_pdf } 5 | 6 | if Rails.env.development? 7 | format.html { render_sample_html } 8 | end 9 | end 10 | end 11 | 12 | private 13 | 14 | def invoice 15 | Invoice.find(params[:invoice_id]) 16 | end 17 | 18 | def download 19 | Download.new(invoice) 20 | end 21 | 22 | def send_invoice_pdf 23 | send_file download.to_pdf, download_attributes 24 | end 25 | 26 | def render_sample_html 27 | render download.render_attributes 28 | end 29 | 30 | def download_attributes 31 | { 32 | filename: download.filename, 33 | type: "application/pdf", 34 | disposition: "inline" 35 | } 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | config.assets.raise_runtime_errors = true 3 | config.cache_classes = true 4 | config.eager_load = false 5 | config.serve_static_files = true 6 | config.static_cache_control = 'public, max-age=3600' 7 | config.consider_all_requests_local = true 8 | config.action_controller.perform_caching = false 9 | config.action_dispatch.show_exceptions = false 10 | config.action_controller.allow_forgery_protection = false 11 | config.action_mailer.delivery_method = :test 12 | config.active_support.test_order = :random 13 | config.active_support.deprecation = :stderr 14 | config.action_view.raise_on_missing_translations = true 15 | config.action_mailer.default_url_options = { host: "www.example.com" } 16 | config.active_job.queue_adapter = :inline 17 | end 18 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | config.cache_classes = false 3 | config.eager_load = false 4 | config.consider_all_requests_local = true 5 | config.action_controller.perform_caching = false 6 | config.action_mailer.raise_delivery_errors = true 7 | config.after_initialize do 8 | Bullet.enable = true 9 | Bullet.bullet_logger = true 10 | Bullet.rails_logger = true 11 | end 12 | config.action_mailer.delivery_method = :test 13 | config.active_support.deprecation = :log 14 | config.active_record.migration_error = :page_load 15 | config.assets.debug = true 16 | config.assets.digest = true 17 | config.assets.raise_runtime_errors = true 18 | config.action_view.raise_on_missing_translations = true 19 | config.action_mailer.default_url_options = { host: "localhost:3000" } 20 | end 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/refills/_flashes.scss: -------------------------------------------------------------------------------- 1 | $base-spacing: 1.5em !default; 2 | $alert-color: #fff6bf !default; 3 | $error-color: #fbe3e4 !default; 4 | $notice-color: #e5edf8 !default; 5 | $success-color: #e6efc2 !default; 6 | 7 | @mixin flash($color) { 8 | background-color: $color; 9 | color: darken($color, 60%); 10 | display: block; 11 | font-weight: 600; 12 | margin-bottom: $base-spacing / 2; 13 | padding: $base-spacing / 2; 14 | text-align: center; 15 | 16 | a { 17 | color: darken($color, 70%); 18 | 19 | &:focus, 20 | &:hover { 21 | color: darken($color, 90%); 22 | } 23 | } 24 | } 25 | 26 | .flash-alert { 27 | @include flash($alert-color); 28 | } 29 | 30 | .flash-error { 31 | @include flash($error-color); 32 | } 33 | 34 | .flash-notice { 35 | @include flash($notice-color); 36 | } 37 | 38 | .flash-success { 39 | @include flash($success-color); 40 | } 41 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_buttons.scss: -------------------------------------------------------------------------------- 1 | #{$all-buttons} { 2 | appearance: none; 3 | background-color: $action-color; 4 | border: 0; 5 | border-radius: $base-border-radius; 6 | color: #fff; 7 | cursor: pointer; 8 | display: inline-block; 9 | font-family: $base-font-family; 10 | font-size: $base-font-size; 11 | -webkit-font-smoothing: antialiased; 12 | font-weight: 600; 13 | line-height: 1; 14 | padding: $small-spacing $base-spacing; 15 | text-decoration: none; 16 | transition: background-color $base-duration $base-timing; 17 | user-select: none; 18 | vertical-align: middle; 19 | white-space: nowrap; 20 | 21 | &:hover, 22 | &:focus { 23 | background-color: shade($action-color, 20%); 24 | color: #fff; 25 | } 26 | 27 | &:disabled { 28 | cursor: not-allowed; 29 | opacity: 0.5; 30 | 31 | &:hover { 32 | background-color: $action-color; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config/newrelic.yml: -------------------------------------------------------------------------------- 1 | common: &default_settings 2 | app_name: "invoicer" 3 | audit_log: 4 | enabled: false 5 | browser_monitoring: 6 | auto_instrument: true 7 | capture_params: false 8 | developer_mode: false 9 | error_collector: 10 | capture_source: true 11 | enabled: true 12 | ignore_errors: "ActionController::RoutingError,Sinatra::NotFound" 13 | license_key: "<%= ENV["NEW_RELIC_LICENSE_KEY"] %>" 14 | log_level: info 15 | monitor_mode: true 16 | transaction_tracer: 17 | enabled: true 18 | record_sql: obfuscated 19 | stack_trace_threshold: 0.500 20 | transaction_threshold: apdex_f 21 | development: 22 | <<: *default_settings 23 | monitor_mode: false 24 | developer_mode: true 25 | test: 26 | <<: *default_settings 27 | monitor_mode: false 28 | production: 29 | <<: *default_settings 30 | monitor_mode: true 31 | staging: 32 | <<: *default_settings 33 | app_name: "invoicer (Staging)" 34 | monitor_mode: true 35 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_typography.scss: -------------------------------------------------------------------------------- 1 | body { 2 | color: $base-font-color; 3 | font-family: $base-font-family; 4 | font-feature-settings: "kern", "liga", "pnum"; 5 | font-size: $base-font-size; 6 | line-height: $base-line-height; 7 | } 8 | 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6 { 15 | font-family: $heading-font-family; 16 | font-size: $base-font-size; 17 | line-height: $heading-line-height; 18 | margin: 0 0 $small-spacing; 19 | } 20 | 21 | p { 22 | margin: 0 0 $small-spacing; 23 | } 24 | 25 | a { 26 | color: $action-color; 27 | text-decoration: none; 28 | transition: color $base-duration $base-timing; 29 | 30 | &:active, 31 | &:focus, 32 | &:hover { 33 | color: shade($action-color, 25%); 34 | } 35 | } 36 | 37 | hr { 38 | border-bottom: $base-border; 39 | border-left: 0; 40 | border-right: 0; 41 | border-top: 0; 42 | margin: $base-spacing 0; 43 | } 44 | 45 | img, 46 | picture { 47 | margin: 0; 48 | max-width: 100%; 49 | } 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/assets/stylesheets/invoice_pdf.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @import "normalize-rails"; 4 | @import "bourbon"; 5 | @import "base/grid-settings"; 6 | @import "neat"; 7 | @import "base/base"; 8 | @import "refills/flashes"; 9 | 10 | $row-stripe: #d2d2d2; 11 | 12 | .container { 13 | margin: 30px auto; 14 | max-width: 980px; 15 | } 16 | 17 | 18 | .line-items { 19 | margin-bottom: 20px; 20 | 21 | .quantity { 22 | width: 5%; 23 | } 24 | 25 | .item-number { 26 | width: 20%; 27 | } 28 | 29 | .description { 30 | width: 55%; 31 | } 32 | 33 | .unit-price, .total { 34 | width: 10%; 35 | } 36 | 37 | tbody tr:nth-child(2n) { 38 | background: $row-stripe; 39 | } 40 | 41 | tbody td:first-child { 42 | padding-left: 10px; 43 | } 44 | } 45 | 46 | header { 47 | margin-bottom: 40px; 48 | 49 | .contact { 50 | float: right; 51 | p { 52 | margin-bottom: 0; 53 | } 54 | } 55 | 56 | .logo { 57 | max-height: 70px; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/views/invoices/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to "Download PDF", 2 | invoice_download_path(@invoice, format: "pdf"), 3 | target: "_blank", 4 | class: "download" %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <% @invoice.line_items.each do |line_item| %> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <% end %> 26 | 27 |
QtyItem #DescriptionUnit PriceTotal
<%= line_item.quantity %><%= line_item.product.item_number %><%= line_item.product.description %><%= number_to_currency(line_item.product.price) %><%= number_to_currency(line_item.product.price * line_item.quantity) %>
28 | 29 |
30 |

Notes

31 |

<%= @invoice.notes %>

32 |
33 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | require "rails" 3 | require "active_model/railtie" 4 | require "active_job/railtie" 5 | require "active_record/railtie" 6 | require "action_controller/railtie" 7 | require "action_mailer/railtie" 8 | require "action_view/railtie" 9 | require "sprockets/railtie" 10 | Bundler.require(*Rails.groups) 11 | module Invoicer 12 | class Application < Rails::Application 13 | config.i18n.enforce_available_locales = true 14 | config.quiet_assets = true 15 | config.generators do |generate| 16 | generate.helper false 17 | generate.javascript_engine false 18 | generate.request_specs false 19 | generate.routing_specs false 20 | generate.stylesheets false 21 | generate.test_framework :rspec 22 | generate.view_specs false 23 | end 24 | config.action_controller.action_on_unpermitted_parameters = :raise 25 | config.active_record.raise_in_transactional_callbacks = true 26 | config.active_job.queue_adapter = :delayed_job 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_variables.scss: -------------------------------------------------------------------------------- 1 | // Typography 2 | $base-font-family: $helvetica; 3 | $heading-font-family: $base-font-family; 4 | 5 | // Font Sizes 6 | $base-font-size: 1em; 7 | 8 | // Line height 9 | $base-line-height: 1.5; 10 | $heading-line-height: 1.2; 11 | 12 | // Other Sizes 13 | $base-border-radius: 3px; 14 | $base-spacing: $base-line-height * 1em; 15 | $small-spacing: $base-spacing / 2; 16 | $base-z-index: 0; 17 | 18 | // Colors 19 | $blue: #477dca; 20 | $dark-gray: #333; 21 | $medium-gray: #999; 22 | $light-gray: #ddd; 23 | 24 | // Font Colors 25 | $base-font-color: $dark-gray; 26 | $action-color: $blue; 27 | 28 | // Border 29 | $base-border-color: $light-gray; 30 | $base-border: 1px solid $base-border-color; 31 | 32 | // Background Colors 33 | $base-background-color: #fff; 34 | $secondary-background-color: tint($base-border-color, 75%); 35 | 36 | // Forms 37 | $form-box-shadow: inset 0 1px 3px rgba(#000, 0.06); 38 | $form-box-shadow-focus: $form-box-shadow, 0 0 5px adjust-color($action-color, $lightness: -5%, $alpha: -0.3); 39 | 40 | // Animations 41 | $base-duration: 150ms; 42 | $base-timing: ease; 43 | -------------------------------------------------------------------------------- /app/views/application/_analytics.html.erb: -------------------------------------------------------------------------------- 1 | <% if ENV["SEGMENT_KEY"] %> 2 | 7 | <% end %> 8 | -------------------------------------------------------------------------------- /spec/features/user_downloads_invoice_pdf_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | describe "User downalods PDF" do 4 | scenario "for an invoice with normal data" do 5 | product = create(:product, item_number: 'abc-123') 6 | invoice = create(:invoice) 7 | line_item = create(:line_item, product: product, invoice: invoice) 8 | 9 | visit invoice_path(invoice) 10 | click_link "Download PDF" 11 | 12 | expect(content_type).to eq("application/pdf") 13 | expect(content_disposition).to include("inline") 14 | expect(download_filename).to include(invoice.number) 15 | expect(pdf_body).to have_content(product.description) 16 | end 17 | 18 | def content_type 19 | response_headers["Content-Type"] 20 | end 21 | 22 | def content_disposition 23 | response_headers["Content-Disposition"] 24 | end 25 | 26 | def download_filename 27 | content_disposition.scan(/filename="(.*)"/).last.first 28 | end 29 | 30 | def pdf_body 31 | temp_pdf = Tempfile.new('pdf') 32 | temp_pdf << page.source.force_encoding('UTF-8') 33 | reader = PDF::Reader.new(temp_pdf) 34 | reader.pages.first.text 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require Rails.root.join("config/smtp") 2 | Rails.application.configure do 3 | config.cache_classes = true 4 | config.eager_load = true 5 | config.consider_all_requests_local = false 6 | config.action_controller.perform_caching = true 7 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 8 | config.middleware.use Rack::Deflater 9 | config.middleware.use Rack::CanonicalHost, ENV.fetch("APPLICATION_HOST") 10 | config.assets.js_compressor = :uglifier 11 | config.assets.compile = false 12 | config.assets.digest = true 13 | config.log_level = :debug 14 | config.action_controller.asset_host = ENV.fetch("ASSET_HOST", ENV.fetch("APPLICATION_HOST")) 15 | config.action_mailer.delivery_method = :smtp 16 | config.action_mailer.smtp_settings = SMTP_SETTINGS 17 | config.i18n.fallbacks = true 18 | config.active_support.deprecation = :notify 19 | config.log_formatter = ::Logger::Formatter.new 20 | config.active_record.dump_schema_after_migration = false 21 | config.action_mailer.default_url_options = { host: ENV.fetch("APPLICATION_HOST") } 22 | end 23 | Rack::Timeout.timeout = (ENV["RACK_TIMEOUT"] || 10).to_i 24 | -------------------------------------------------------------------------------- /app/views/invoices/pdf.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Ralph Industries, LLC

4 |

32 Ralph Ln

5 |

Ralphsville, IN, RALPH

6 |
7 | <%= image_tag "https://thoughtbot.com/logo-large.png", class: "logo" %> 8 |
9 | 10 |

Invoice

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <% invoice.line_items.each do |line_item| %> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | <% end %> 32 | 33 |
QtyItem #DescriptionUnit PriceTotal
<%= line_item.quantity %><%= line_item.product.item_number %><%= line_item.product.description %><%= number_to_currency(line_item.product.price) %><%= number_to_currency(line_item.product.price * line_item.quantity) %>
34 | 35 |
36 |

Notes

37 |

<%= invoice.notes %>

38 |
39 | 40 | -------------------------------------------------------------------------------- /db/migrate/20151222024559_create_delayed_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateDelayedJobs < ActiveRecord::Migration 2 | def self.up 3 | create_table :delayed_jobs, force: true do |table| 4 | table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue 5 | table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually. 6 | table.text :handler, null: false # YAML-encoded string of the object that will do work 7 | table.text :last_error # reason for last failure (See Note below) 8 | table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. 9 | table.datetime :locked_at # Set when a client is working on this object 10 | table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) 11 | table.string :locked_by # Who is working on this object (if locked) 12 | table.string :queue # The name of the queue this job is in 13 | table.timestamps null: true 14 | end 15 | 16 | add_index :delayed_jobs, [:priority, :run_at], name: "delayed_jobs_priority" 17 | end 18 | 19 | def self.down 20 | drop_table :delayed_jobs 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby "2.2.3" 4 | 5 | gem "autoprefixer-rails" 6 | gem "bourbon", "~> 4.2.0" 7 | gem "coffee-rails", "~> 4.1.0" 8 | gem "delayed_job_active_record" 9 | gem "flutie" 10 | gem "high_voltage" 11 | gem "jquery-rails" 12 | gem "neat", "~> 1.7.0" 13 | gem "newrelic_rpm", ">= 3.9.8" 14 | gem "normalize-rails", "~> 3.0.0" 15 | gem "pdfkit" 16 | gem "pg" 17 | gem "rack-canonical-host" 18 | gem "rails", "~> 4.2.0" 19 | gem "recipient_interceptor" 20 | gem "render_anywhere", require: false 21 | gem "sass-rails", "~> 5.0" 22 | gem "simple_form" 23 | gem "title" 24 | gem "uglifier" 25 | 26 | group :development do 27 | gem "quiet_assets" 28 | gem "refills" 29 | gem "spring" 30 | gem "spring-commands-rspec" 31 | gem "web-console" 32 | end 33 | 34 | group :development, :test do 35 | gem "awesome_print" 36 | gem "bullet" 37 | gem "bundler-audit", require: false 38 | gem "byebug" 39 | gem "dotenv-rails" 40 | gem "factory_girl_rails" 41 | gem "i18n-tasks" 42 | gem "pry-rails" 43 | gem "rspec-rails", "~> 3.3.0" 44 | end 45 | 46 | group :test do 47 | gem "capybara-webkit" 48 | gem "database_cleaner" 49 | gem "formulaic" 50 | gem "launchy" 51 | gem "pdf-reader" 52 | gem "shoulda-matchers" 53 | gem "simplecov", require: false 54 | gem "timecop" 55 | gem "webmock" 56 | end 57 | 58 | group :staging, :production do 59 | gem "rack-timeout" 60 | gem "wkhtmltopdf-heroku" 61 | end 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Invoicer 2 | 3 | This is the companion codebase to [The Weekly Iteration, Episode 105: 4 | Generating PDFs with Rails][]. 5 | 6 | In this episode, we walk through everything we need to work with PDFs in our 7 | Rails apps: the easiest way to generate them, how to properly serve them as 8 | responses in our controllers, and even how to test them. 9 | 10 | Invoicer is a simple application that will generate PDF receipts. 11 | 12 | [The Weekly Iteration, Episode 105: Generating PDFs with Rails]: https://upcase.com/videos/generating-pdfs-with-rails?utm_source=github&utm_medium=companion-code&utm_campaign=upcase-generating-pdfs-with-rails 13 | 14 | ## Getting Started 15 | 16 | After you have cloned this repo, run this setup script to set up your machine 17 | with the necessary dependencies to run and test this app: 18 | 19 | % ./bin/setup 20 | 21 | It assumes you have a machine equipped with Ruby, Postgres, etc. If not, set up 22 | your machine with [this script]. 23 | 24 | [this script]: https://github.com/thoughtbot/laptop 25 | 26 | After setting up, you can run the application locally with: 27 | 28 | % rails server 29 | 30 | ## Guidelines 31 | 32 | Use the following guides for getting things done, programming well, and 33 | programming in style. 34 | 35 | * [Protocol](http://github.com/thoughtbot/guides/blob/master/protocol) 36 | * [Best Practices](http://github.com/thoughtbot/guides/blob/master/best-practices) 37 | * [Style](http://github.com/thoughtbot/guides/blob/master/style) 38 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | We're sorry, but something went wrong (500) 8 | 9 | 58 | 59 | 60 | 61 |
62 |
63 |

We're sorry, but something went wrong.

64 |
65 |

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

66 |
67 | 68 | 69 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The change you wanted was rejected (422) 8 | 9 | 58 | 59 | 60 | 61 |
62 |
63 |

The change you wanted was rejected.

64 |

Maybe you tried to change something you didn't have access to.

65 |
66 |

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

67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The page you were looking for doesn't exist (404) 8 | 9 | 58 | 59 | 60 | 61 |
62 |
63 |

The page you were looking for doesn't exist.

64 |

You may have mistyped the address or the page may have moved.

65 |
66 |

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

67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_forms.scss: -------------------------------------------------------------------------------- 1 | fieldset { 2 | background-color: $secondary-background-color; 3 | border: $base-border; 4 | margin: 0 0 $small-spacing; 5 | padding: $base-spacing; 6 | } 7 | 8 | input, 9 | label, 10 | select { 11 | display: block; 12 | font-family: $base-font-family; 13 | font-size: $base-font-size; 14 | } 15 | 16 | label { 17 | font-weight: 600; 18 | margin-bottom: $small-spacing / 2; 19 | 20 | &.required::after { 21 | content: "*"; 22 | } 23 | 24 | abbr { 25 | display: none; 26 | } 27 | } 28 | 29 | #{$all-text-inputs}, 30 | select[multiple=multiple] { 31 | background-color: $base-background-color; 32 | border: $base-border; 33 | border-radius: $base-border-radius; 34 | box-shadow: $form-box-shadow; 35 | box-sizing: border-box; 36 | font-family: $base-font-family; 37 | font-size: $base-font-size; 38 | margin-bottom: $small-spacing; 39 | padding: $base-spacing / 3; 40 | transition: border-color $base-duration $base-timing; 41 | width: 100%; 42 | 43 | &:hover { 44 | border-color: shade($base-border-color, 20%); 45 | } 46 | 47 | &:focus { 48 | border-color: $action-color; 49 | box-shadow: $form-box-shadow-focus; 50 | outline: none; 51 | } 52 | 53 | &:disabled { 54 | background-color: shade($base-background-color, 5%); 55 | cursor: not-allowed; 56 | 57 | &:hover { 58 | border: $base-border; 59 | } 60 | } 61 | } 62 | 63 | textarea { 64 | resize: vertical; 65 | } 66 | 67 | input[type="search"] { 68 | appearance: none; 69 | } 70 | 71 | input[type="checkbox"], 72 | input[type="radio"] { 73 | display: inline; 74 | margin-right: $small-spacing / 2; 75 | 76 | + label { 77 | display: inline-block; 78 | } 79 | } 80 | 81 | input[type="file"] { 82 | margin-bottom: $small-spacing; 83 | width: 100%; 84 | } 85 | 86 | select { 87 | margin-bottom: $base-spacing; 88 | max-width: 100%; 89 | width: auto; 90 | } 91 | -------------------------------------------------------------------------------- /lib/tasks/dev.rake: -------------------------------------------------------------------------------- 1 | if Rails.env.development? || Rails.env.test? 2 | require "factory_girl" 3 | 4 | namespace :dev do 5 | desc "Sample data for local development environment" 6 | task prime: "db:setup" do 7 | include FactoryGirl::Syntax::Methods 8 | 9 | LineItem.destroy_all 10 | Product.destroy_all 11 | Invoice.destroy_all 12 | 13 | tool = create :product, price: 29.95, item_number: "tool-123-ab4", 14 | description: "A mutli-purpose tool" 15 | level = create :product, price: 49.50, item_number: "tool-389-b5d", 16 | description: "Thermonuclear level w/ kick stand" 17 | tacks = create :product, price: 9.15, item_number: "tool-887-bd4", 18 | description: "Thumb tacks... really good ones" 19 | saw = create :product, price: 25.00, item_number: "tool-387-saw", 20 | description: "A very sharp saw" 21 | nails = create :product, price: 1.57, item_number: "tool-845-bb2", 22 | description: "A collection of nails, about the same size" 23 | 24 | invoice = create :invoice 25 | create :line_item, invoice: invoice, product: tacks 26 | create :line_item, invoice: invoice, product: tool 27 | create :line_item, invoice: invoice, product: level 28 | 29 | other_invoice = create :invoice 30 | create :line_item, invoice: other_invoice, product: tacks 31 | create :line_item, invoice: other_invoice, product: level 32 | create :line_item, invoice: other_invoice, product: nails 33 | create :line_item, invoice: other_invoice, product: tool 34 | create :line_item, invoice: other_invoice, product: saw, quantity: 5 35 | 36 | last_invoice = create :invoice 37 | create :line_item, invoice: last_invoice, product: level 38 | create :line_item, invoice: last_invoice, product: tacks 39 | create :line_item, invoice: last_invoice, product: nails 40 | create :line_item, invoice: last_invoice, product: saw 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /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: 20151222034730) do 15 | 16 | # These are extensions that must be enabled in order to support this database 17 | enable_extension "plpgsql" 18 | 19 | create_table "delayed_jobs", force: :cascade do |t| 20 | t.integer "priority", default: 0, null: false 21 | t.integer "attempts", default: 0, null: false 22 | t.text "handler", null: false 23 | t.text "last_error" 24 | t.datetime "run_at" 25 | t.datetime "locked_at" 26 | t.datetime "failed_at" 27 | t.string "locked_by" 28 | t.string "queue" 29 | t.datetime "created_at" 30 | t.datetime "updated_at" 31 | end 32 | 33 | add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree 34 | 35 | create_table "invoices", force: :cascade do |t| 36 | t.string "account" 37 | t.string "number" 38 | t.date "date" 39 | t.text "notes" 40 | t.datetime "created_at", null: false 41 | t.datetime "updated_at", null: false 42 | end 43 | 44 | create_table "line_items", force: :cascade do |t| 45 | t.integer "invoice_id" 46 | t.integer "quantity" 47 | t.integer "product_id" 48 | t.datetime "created_at", null: false 49 | t.datetime "updated_at", null: false 50 | end 51 | 52 | add_index "line_items", ["invoice_id"], name: "index_line_items_on_invoice_id", using: :btree 53 | 54 | create_table "products", force: :cascade do |t| 55 | t.string "description" 56 | t.string "item_number" 57 | t.money "price", scale: 2 58 | t.datetime "created_at", null: false 59 | t.datetime "updated_at", null: false 60 | end 61 | 62 | add_foreign_key "line_items", "invoices" 63 | end 64 | -------------------------------------------------------------------------------- /config/initializers/simple_form.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in SimpleForm. 2 | SimpleForm.setup do |config| 3 | # Wrappers are used by the form builder to generate a 4 | # complete input. You can remove any component from the 5 | # wrapper, change the order or even add your own to the 6 | # stack. The options given below are used to wrap the 7 | # whole input. 8 | config.wrappers :default, class: :input, 9 | hint_class: :field_with_hint, error_class: :field_with_errors do |b| 10 | ## Extensions enabled by default 11 | # Any of these extensions can be disabled for a 12 | # given input by passing: `f.input EXTENSION_NAME => false`. 13 | # You can make any of these extensions optional by 14 | # renaming `b.use` to `b.optional`. 15 | 16 | # Determines whether to use HTML5 (:email, :url, ...) 17 | # and required attributes 18 | b.use :html5 19 | 20 | # Calculates placeholders automatically from I18n 21 | # You can also pass a string as f.input placeholder: "Placeholder" 22 | b.use :placeholder 23 | 24 | ## Optional extensions 25 | # They are disabled unless you pass `f.input EXTENSION_NAME => true` 26 | # to the input. If so, they will retrieve the values from the model 27 | # if any exists. If you want to enable any of those 28 | # extensions by default, you can change `b.optional` to `b.use`. 29 | 30 | # Calculates maxlength from length validations for string inputs 31 | b.optional :maxlength 32 | 33 | # Calculates pattern from format validations for string inputs 34 | b.optional :pattern 35 | 36 | # Calculates min and max from length validations for numeric inputs 37 | b.optional :min_max 38 | 39 | # Calculates readonly automatically from readonly attributes 40 | b.optional :readonly 41 | 42 | ## Inputs 43 | b.use :label_input 44 | b.use :hint, wrap_with: { tag: :span, class: :hint } 45 | b.use :error, wrap_with: { tag: :span, class: :error } 46 | 47 | ## full_messages_for 48 | # If you want to display the full error message for the attribute, you can 49 | # use the component :full_error, like: 50 | # 51 | # b.use :full_error, wrap_with: { tag: :span, class: :error } 52 | end 53 | 54 | # The default wrapper to be used by the FormBuilder. 55 | config.default_wrapper = :default 56 | 57 | # Define the way to render check boxes / radio buttons with labels. 58 | # Defaults to :nested for bootstrap config. 59 | # inline: input + label 60 | # nested: label > input 61 | config.boolean_style = :nested 62 | 63 | # Default class for buttons 64 | config.button_class = 'btn' 65 | 66 | # Method used to tidy up errors. Specify any Rails Array method. 67 | # :first lists the first message for each field. 68 | # Use :to_sentence to list all errors for each field. 69 | # config.error_method = :first 70 | 71 | # Default tag used for error notification helper. 72 | config.error_notification_tag = :div 73 | 74 | # CSS class to add for error notification helper. 75 | config.error_notification_class = 'error_notification' 76 | 77 | # ID to add for error notification helper. 78 | # config.error_notification_id = nil 79 | 80 | # Series of attempts to detect a default label method for collection. 81 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] 82 | 83 | # Series of attempts to detect a default value method for collection. 84 | # config.collection_value_methods = [ :id, :to_s ] 85 | 86 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. 87 | # config.collection_wrapper_tag = nil 88 | 89 | # You can define the class to use on all collection wrappers. Defaulting to none. 90 | # config.collection_wrapper_class = nil 91 | 92 | # You can wrap each item in a collection of radio/check boxes with a tag, 93 | # defaulting to :span. 94 | # config.item_wrapper_tag = :span 95 | 96 | # You can define a class to use in all item wrappers. Defaulting to none. 97 | # config.item_wrapper_class = nil 98 | 99 | # How the label text should be generated altogether with the required text. 100 | # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } 101 | 102 | # You can define the class to use on all labels. Default is nil. 103 | # config.label_class = nil 104 | 105 | # You can define the default class to be used on forms. Can be overriden 106 | # with `html: { :class }`. Defaulting to none. 107 | # config.default_form_class = nil 108 | 109 | # You can define which elements should obtain additional classes 110 | # config.generate_additional_classes_for = [:wrapper, :label, :input] 111 | 112 | # Whether attributes are required by default (or not). Default is true. 113 | # config.required_by_default = true 114 | 115 | # Tell browsers whether to use the native HTML5 validations (novalidate form option). 116 | # These validations are enabled in SimpleForm's internal config but disabled by default 117 | # in this configuration, which is recommended due to some quirks from different browsers. 118 | # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, 119 | # change this configuration to true. 120 | config.browser_validations = false 121 | 122 | # Collection of methods to detect if a file type was given. 123 | # config.file_methods = [ :mounted_as, :file?, :public_filename ] 124 | 125 | # Custom mappings for input types. This should be a hash containing a regexp 126 | # to match as key, and the input type that will be used when the field name 127 | # matches the regexp as value. 128 | # config.input_mappings = { /count/ => :integer } 129 | 130 | # Custom wrappers for input types. This should be a hash containing an input 131 | # type as key and the wrapper that will be used for all inputs with specified type. 132 | # config.wrapper_mappings = { string: :prepend } 133 | 134 | # Namespaces where SimpleForm should look for custom input classes that 135 | # override default inputs. 136 | # config.custom_inputs_namespaces << "CustomInputs" 137 | 138 | # Default priority for time_zone inputs. 139 | # config.time_zone_priority = nil 140 | 141 | # Default priority for country inputs. 142 | # config.country_priority = nil 143 | 144 | # When false, do not use translations for labels. 145 | # config.translate_labels = true 146 | 147 | # Automatically discover new inputs in Rails' autoload path. 148 | # config.inputs_discovery = true 149 | 150 | # Cache SimpleForm inputs discovery 151 | # config.cache_discovery = !Rails.env.development? 152 | 153 | # Default class for inputs 154 | # config.input_class = nil 155 | 156 | # Define the default class of the input wrapper of the boolean input. 157 | config.boolean_label_class = 'checkbox' 158 | 159 | # Defines if the default input wrapper class should be included in radio 160 | # collection wrappers. 161 | # config.include_default_input_wrapper_class = true 162 | 163 | # Defines which i18n scope will be used in Simple Form. 164 | # config.i18n_scope = 'simple_form' 165 | end 166 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | Ascii85 (1.0.2) 5 | actionmailer (4.2.5) 6 | actionpack (= 4.2.5) 7 | actionview (= 4.2.5) 8 | activejob (= 4.2.5) 9 | mail (~> 2.5, >= 2.5.4) 10 | rails-dom-testing (~> 1.0, >= 1.0.5) 11 | actionpack (4.2.5) 12 | actionview (= 4.2.5) 13 | activesupport (= 4.2.5) 14 | rack (~> 1.6) 15 | rack-test (~> 0.6.2) 16 | rails-dom-testing (~> 1.0, >= 1.0.5) 17 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 18 | actionview (4.2.5) 19 | activesupport (= 4.2.5) 20 | builder (~> 3.1) 21 | erubis (~> 2.7.0) 22 | rails-dom-testing (~> 1.0, >= 1.0.5) 23 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 24 | activejob (4.2.5) 25 | activesupport (= 4.2.5) 26 | globalid (>= 0.3.0) 27 | activemodel (4.2.5) 28 | activesupport (= 4.2.5) 29 | builder (~> 3.1) 30 | activerecord (4.2.5) 31 | activemodel (= 4.2.5) 32 | activesupport (= 4.2.5) 33 | arel (~> 6.0) 34 | activesupport (4.2.5) 35 | i18n (~> 0.7) 36 | json (~> 1.7, >= 1.7.7) 37 | minitest (~> 5.1) 38 | thread_safe (~> 0.3, >= 0.3.4) 39 | tzinfo (~> 1.1) 40 | addressable (2.4.0) 41 | afm (0.2.2) 42 | arel (6.0.3) 43 | ast (2.2.0) 44 | autoprefixer-rails (6.2.1) 45 | execjs 46 | json 47 | awesome_print (1.6.1) 48 | bourbon (4.2.6) 49 | sass (~> 3.4) 50 | thor (~> 0.19) 51 | builder (3.2.2) 52 | bullet (4.14.10) 53 | activesupport (>= 3.0.0) 54 | uniform_notifier (~> 1.9.0) 55 | bundler-audit (0.4.0) 56 | bundler (~> 1.2) 57 | thor (~> 0.18) 58 | byebug (8.2.1) 59 | capybara (2.5.0) 60 | mime-types (>= 1.16) 61 | nokogiri (>= 1.3.3) 62 | rack (>= 1.0.0) 63 | rack-test (>= 0.5.4) 64 | xpath (~> 2.0) 65 | capybara-webkit (1.7.1) 66 | capybara (>= 2.3.0, < 2.6.0) 67 | json 68 | coderay (1.1.0) 69 | coffee-rails (4.1.1) 70 | coffee-script (>= 2.2.0) 71 | railties (>= 4.0.0, < 5.1.x) 72 | coffee-script (2.4.1) 73 | coffee-script-source 74 | execjs 75 | coffee-script-source (1.10.0) 76 | concurrent-ruby (1.0.0) 77 | crack (0.4.3) 78 | safe_yaml (~> 1.0.0) 79 | database_cleaner (1.5.1) 80 | debug_inspector (0.0.2) 81 | delayed_job (4.1.1) 82 | activesupport (>= 3.0, < 5.0) 83 | delayed_job_active_record (4.1.0) 84 | activerecord (>= 3.0, < 5) 85 | delayed_job (>= 3.0, < 5) 86 | diff-lcs (1.2.5) 87 | docile (1.1.5) 88 | dotenv (2.0.2) 89 | dotenv-rails (2.0.2) 90 | dotenv (= 2.0.2) 91 | railties (~> 4.0) 92 | easy_translate (0.5.0) 93 | json 94 | thread 95 | thread_safe 96 | erubis (2.7.0) 97 | execjs (2.6.0) 98 | factory_girl (4.5.0) 99 | activesupport (>= 3.0.0) 100 | factory_girl_rails (4.5.0) 101 | factory_girl (~> 4.5.0) 102 | railties (>= 3.0.0) 103 | flutie (2.0.0) 104 | formulaic (0.3.0) 105 | activesupport 106 | capybara 107 | i18n 108 | globalid (0.3.6) 109 | activesupport (>= 4.1.0) 110 | hashdiff (0.2.3) 111 | hashery (2.1.1) 112 | high_voltage (2.4.0) 113 | highline (1.7.8) 114 | i18n (0.7.0) 115 | i18n-tasks (0.9.2) 116 | activesupport (>= 4.0.2) 117 | ast (>= 2.1.0) 118 | easy_translate (>= 0.5.0) 119 | erubis 120 | highline (>= 1.7.3) 121 | i18n 122 | parser (>= 2.2.3.0) 123 | term-ansicolor (>= 1.3.2) 124 | terminal-table (>= 1.5.1) 125 | jquery-rails (4.0.5) 126 | rails-dom-testing (~> 1.0) 127 | railties (>= 4.2.0) 128 | thor (>= 0.14, < 2.0) 129 | json (1.8.3) 130 | launchy (2.4.3) 131 | addressable (~> 2.3) 132 | loofah (2.0.3) 133 | nokogiri (>= 1.5.9) 134 | mail (2.6.3) 135 | mime-types (>= 1.16, < 3) 136 | method_source (0.8.2) 137 | mime-types (2.99) 138 | mini_portile2 (2.0.0) 139 | minitest (5.8.3) 140 | neat (1.7.2) 141 | bourbon (>= 4.0) 142 | sass (>= 3.3) 143 | newrelic_rpm (3.14.1.311) 144 | nokogiri (1.6.7.1) 145 | mini_portile2 (~> 2.0.0.rc2) 146 | normalize-rails (3.0.3) 147 | parser (2.2.3.0) 148 | ast (>= 1.1, < 3.0) 149 | pdf-reader (1.3.3) 150 | Ascii85 (~> 1.0.0) 151 | afm (~> 0.2.0) 152 | hashery (~> 2.0) 153 | ruby-rc4 154 | ttfunk 155 | pdfkit (0.8.2) 156 | pg (0.18.4) 157 | pry (0.10.3) 158 | coderay (~> 1.1.0) 159 | method_source (~> 0.8.1) 160 | slop (~> 3.4) 161 | pry-rails (0.3.4) 162 | pry (>= 0.9.10) 163 | quiet_assets (1.1.0) 164 | railties (>= 3.1, < 5.0) 165 | rack (1.6.4) 166 | rack-canonical-host (0.1.0) 167 | addressable 168 | rack (~> 1.0) 169 | rack-test (0.6.3) 170 | rack (>= 1.0) 171 | rack-timeout (0.3.2) 172 | rails (4.2.5) 173 | actionmailer (= 4.2.5) 174 | actionpack (= 4.2.5) 175 | actionview (= 4.2.5) 176 | activejob (= 4.2.5) 177 | activemodel (= 4.2.5) 178 | activerecord (= 4.2.5) 179 | activesupport (= 4.2.5) 180 | bundler (>= 1.3.0, < 2.0) 181 | railties (= 4.2.5) 182 | sprockets-rails 183 | rails-deprecated_sanitizer (1.0.3) 184 | activesupport (>= 4.2.0.alpha) 185 | rails-dom-testing (1.0.7) 186 | activesupport (>= 4.2.0.beta, < 5.0) 187 | nokogiri (~> 1.6.0) 188 | rails-deprecated_sanitizer (>= 1.0.1) 189 | rails-html-sanitizer (1.0.2) 190 | loofah (~> 2.0) 191 | railties (4.2.5) 192 | actionpack (= 4.2.5) 193 | activesupport (= 4.2.5) 194 | rake (>= 0.8.7) 195 | thor (>= 0.18.1, < 2.0) 196 | rake (10.4.2) 197 | recipient_interceptor (0.1.2) 198 | mail 199 | refills (0.1.0) 200 | render_anywhere (0.0.12) 201 | rails (>= 3.0.7) 202 | rspec-core (3.3.2) 203 | rspec-support (~> 3.3.0) 204 | rspec-expectations (3.3.1) 205 | diff-lcs (>= 1.2.0, < 2.0) 206 | rspec-support (~> 3.3.0) 207 | rspec-mocks (3.3.2) 208 | diff-lcs (>= 1.2.0, < 2.0) 209 | rspec-support (~> 3.3.0) 210 | rspec-rails (3.3.3) 211 | actionpack (>= 3.0, < 4.3) 212 | activesupport (>= 3.0, < 4.3) 213 | railties (>= 3.0, < 4.3) 214 | rspec-core (~> 3.3.0) 215 | rspec-expectations (~> 3.3.0) 216 | rspec-mocks (~> 3.3.0) 217 | rspec-support (~> 3.3.0) 218 | rspec-support (3.3.0) 219 | ruby-rc4 (0.1.5) 220 | safe_yaml (1.0.4) 221 | sass (3.4.20) 222 | sass-rails (5.0.4) 223 | railties (>= 4.0.0, < 5.0) 224 | sass (~> 3.1) 225 | sprockets (>= 2.8, < 4.0) 226 | sprockets-rails (>= 2.0, < 4.0) 227 | tilt (>= 1.1, < 3) 228 | shoulda-matchers (3.0.1) 229 | activesupport (>= 4.0.0) 230 | simple_form (3.2.1) 231 | actionpack (> 4, < 5.1) 232 | activemodel (> 4, < 5.1) 233 | simplecov (0.11.1) 234 | docile (~> 1.1.0) 235 | json (~> 1.8) 236 | simplecov-html (~> 0.10.0) 237 | simplecov-html (0.10.0) 238 | slop (3.6.0) 239 | spring (1.6.0) 240 | spring-commands-rspec (1.0.4) 241 | spring (>= 0.9.1) 242 | sprockets (3.5.2) 243 | concurrent-ruby (~> 1.0) 244 | rack (> 1, < 3) 245 | sprockets-rails (3.0.0) 246 | actionpack (>= 4.0) 247 | activesupport (>= 4.0) 248 | sprockets (>= 3.0.0) 249 | term-ansicolor (1.3.2) 250 | tins (~> 1.0) 251 | terminal-table (1.5.2) 252 | thor (0.19.1) 253 | thread (0.2.2) 254 | thread_safe (0.3.5) 255 | tilt (2.0.1) 256 | timecop (0.8.0) 257 | tins (1.8.1) 258 | title (0.0.5) 259 | i18n 260 | rails (>= 3.1) 261 | ttfunk (1.4.0) 262 | tzinfo (1.2.2) 263 | thread_safe (~> 0.1) 264 | uglifier (2.7.2) 265 | execjs (>= 0.3.0) 266 | json (>= 1.8.0) 267 | uniform_notifier (1.9.0) 268 | web-console (3.0.0) 269 | activemodel (>= 4.2) 270 | debug_inspector 271 | railties (>= 4.2) 272 | webmock (1.22.3) 273 | addressable (>= 2.3.6) 274 | crack (>= 0.3.2) 275 | hashdiff 276 | wkhtmltopdf-heroku (2.12.2.4) 277 | xpath (2.0.0) 278 | nokogiri (~> 1.3) 279 | 280 | PLATFORMS 281 | ruby 282 | 283 | DEPENDENCIES 284 | autoprefixer-rails 285 | awesome_print 286 | bourbon (~> 4.2.0) 287 | bullet 288 | bundler-audit 289 | byebug 290 | capybara-webkit 291 | coffee-rails (~> 4.1.0) 292 | database_cleaner 293 | delayed_job_active_record 294 | dotenv-rails 295 | factory_girl_rails 296 | flutie 297 | formulaic 298 | high_voltage 299 | i18n-tasks 300 | jquery-rails 301 | launchy 302 | neat (~> 1.7.0) 303 | newrelic_rpm (>= 3.9.8) 304 | normalize-rails (~> 3.0.0) 305 | pdf-reader 306 | pdfkit 307 | pg 308 | pry-rails 309 | quiet_assets 310 | rack-canonical-host 311 | rack-timeout 312 | rails (~> 4.2.0) 313 | recipient_interceptor 314 | refills 315 | render_anywhere 316 | rspec-rails (~> 3.3.0) 317 | sass-rails (~> 5.0) 318 | shoulda-matchers 319 | simple_form 320 | simplecov 321 | spring 322 | spring-commands-rspec 323 | timecop 324 | title 325 | uglifier 326 | web-console 327 | webmock 328 | wkhtmltopdf-heroku 329 | 330 | BUNDLED WITH 331 | 1.10.6 332 | --------------------------------------------------------------------------------