devbootcamp 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4a04a3560e486b5ca8f5dd395918da8f01e4edb9
4
+ data.tar.gz: 0005bfde626f92f683f1b01cdf60d33b4ce3d8cf
5
+ SHA512:
6
+ metadata.gz: 98ae7fa1ff25973d9e1eded891d628fd5b8dc5dfdbdfe21659dfff7b4bc138649beb83d8be159f9c8ba46784841c9ff9b6883542163ee6fc683c639be5fac946
7
+ data.tar.gz: ca09b3b291b770edc30d6e39bc924b8fb9fca8fe024a48f399c85d906953b2e85dc112ac722de50b0f5376f078a6eb39da9d4c6801737f6b514e8033d06ca8d7
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in devbootcamp.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jared Grippe
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # Devbootcamp
2
+
3
+ Common code among internal devbootcamp apps
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'devbootcamp', :git => 'git@github.com:Devbootcamp/devbootcamp.gem.git'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install devbootcamp
18
+
19
+
20
+ ### Rails Installation
21
+
22
+ Add the following line to your routes file to have the standard routes for sign in, sign out and callback:
23
+
24
+ ```ruby
25
+ # config/routes.rb
26
+ Devbootcamp::Rails.draw_routes(self)
27
+ ```
28
+
29
+ See `rake routes` for more information on these routes.
30
+
31
+ To require authentication and have reference to a current user, add the following to your application controller:
32
+
33
+ ```ruby
34
+ # app/controllers/application_controller.rb
35
+ require 'devbootcamp/rails/authentication_concern'
36
+
37
+ class ApplicationController < ActionController::Base
38
+ include Devbootcamp::Rails::AuthenticationConcern
39
+
40
+ # ... your code
41
+ end
42
+ ```
43
+
44
+ Now unauthenticated requests will be redirected your sign in path.
45
+ This requires an authentication controller; create the following controller:
46
+
47
+ ```ruby
48
+ # app/controllers/authentication_controller.rb
49
+ require 'devbootcamp/rails/authentication_controller'
50
+
51
+ class AuthenticationController < Devbootcamp::Rails::AuthenticationController
52
+ end
53
+ ```
54
+
55
+ Lastly, we must configure 3 environment variables and initialize the gem.
56
+
57
+ These credentials are for our staging environment
58
+
59
+ DBC_OAUTH_APPLICATION_ID=5460fdba9c987384e2514c0ca8c4b20591e808ce2f78ebdebcc9b9f811066b23
60
+ DBC_OAUTH_SECRET=45cbe6feb64f4f482b5bc0dbf1891a3b02049bf4dbfe56e2f4a947814d6cf7e9
61
+ DBC_OAUTH_SITE="https://auth-devbootcamp-com-staging.herokuapp.com"
62
+
63
+ This authenticates against our [Socrates staging](https://dbcsocrates-staging.herokuapp.com/login) database.
64
+ Your server must be running on localhost:5000.
65
+
66
+ Create the initializer:
67
+
68
+ ```ruby
69
+ # config/initializers/oauth.rb
70
+ Devbootcamp::Rails.initialize!
71
+ ```
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'devbootcamp/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "devbootcamp"
8
+ spec.version = Devbootcamp::VERSION
9
+ spec.authors = ["Jared Grippe"]
10
+ spec.email = ["jared@devbootcamp.com"]
11
+ spec.description = %q{common code among internal devbootcamp apps}
12
+ spec.summary = %q{common code among internal devbootcamp apps}
13
+ spec.homepage = "http://devbootcamp.com"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency "activesupport", "~> 3.2"
25
+ spec.add_dependency "oauth2"
26
+ end
@@ -0,0 +1,10 @@
1
+ require "devbootcamp/version"
2
+
3
+ module Devbootcamp
4
+ end
5
+
6
+ require "devbootcamp/auth"
7
+ require "devbootcamp/user"
8
+ require "devbootcamp/cohort"
9
+ require "devbootcamp/rails" if defined?(Rails)
10
+
@@ -0,0 +1,32 @@
1
+ require 'devbootcamp/oauth'
2
+ require 'devbootcamp/user'
3
+
4
+ module Devbootcamp::Auth
5
+
6
+ Error = Class.new(StandardError)
7
+
8
+ class << self
9
+
10
+ attr_accessor :cache
11
+
12
+ def get(path, options={})
13
+ raise Error, "Devbootcamp::OAuth.token is nil" if Devbootcamp::OAuth.token.nil?
14
+
15
+ cache_for = options.delete(:cache_for)
16
+
17
+ make_request = ->(*){
18
+ Devbootcamp::OAuth.token.get(path).body
19
+ }
20
+
21
+ response = if cache && cache_for
22
+ cache.fetch("Devbootcamp::Auth::#{path}", :expires_in => cache_for, &make_request)
23
+ else
24
+ make_request.call
25
+ end
26
+
27
+ JSON.parse(response)
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,4 @@
1
+ class Devbootcamp::Auth::Client
2
+
3
+
4
+ end
@@ -0,0 +1,43 @@
1
+ module Devbootcamp::Auth::Helpers
2
+
3
+ def setup_devbootcamp_oauth!
4
+ Devbootcamp::OAuth.callback_url = to(oauth_callback_path)
5
+ Devbootcamp::OAuth.token_from_hash(session[:devbootcamp_oauth_token])
6
+ end
7
+
8
+ def authenticated?
9
+ !!session[:devbootcamp_oauth_token]
10
+ end
11
+
12
+ def oauth_sign_in_page_url
13
+ Devbootcamp::OAuth.authorize_url
14
+ end
15
+
16
+ def authenticate!
17
+ if Devbootcamp::OAuth.authorize!(params[:code])
18
+ session[:devbootcamp_oauth_token] = Devbootcamp::OAuth.token_as_hash
19
+ end
20
+ authenticated?
21
+ end
22
+
23
+ def deauthenticate!
24
+ session.delete(:devbootcamp_oauth_token)
25
+ session.delete(:current_user_attributes)
26
+ end
27
+
28
+ def current_user= current_user
29
+ @current_user = current_user
30
+ session[:current_user_attributes] = current_user.attributes
31
+ end
32
+
33
+ def current_user
34
+ return nil unless authenticated?
35
+ return @current_user if @current_user
36
+ if session[:current_user_attributes]
37
+ @current_user = Devbootcamp::User.new(session[:current_user_attributes])
38
+ else
39
+ self.current_user = Devbootcamp::User.me
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,38 @@
1
+ require 'devbootcamp/model'
2
+
3
+ class Devbootcamp::Cohort < Devbootcamp::Model
4
+
5
+ def self.all
6
+ Devbootcamp::Auth.get("/cohorts", cache_for: 1.week).map(&method(:new))
7
+ end
8
+
9
+ def self.names
10
+ all.map(&:name)
11
+ end
12
+
13
+ def sf?
14
+ location == "San Francisco"
15
+ end
16
+
17
+ def chi?
18
+ location == "Chicago"
19
+ end
20
+
21
+ def users
22
+ @users ||=Devbootcamp::Auth.get("/cohorts/#{id}/users", cache_for: 1.day).map(&Devbootcamp::User.method(:new))
23
+ end
24
+
25
+ attributes(
26
+ :id,
27
+ :name,
28
+ :created_at,
29
+ :updated_at,
30
+ :in_session,
31
+ :start_date,
32
+ :location,
33
+ :email,
34
+ :visible,
35
+ :slug
36
+ )
37
+
38
+ end
@@ -0,0 +1,28 @@
1
+ require 'active_support/core_ext/hash'
2
+ class Devbootcamp::Model
3
+
4
+ def self.attributes *attributes
5
+ @attributes ||= []
6
+ if !attributes.empty?
7
+ @attributes += attributes.map(&:to_sym).each do |attribute|
8
+ define_method(attribute){ @attributes[attribute] }
9
+ end
10
+ end
11
+ @attributes
12
+ end
13
+
14
+ def initialize(attributes={})
15
+ @attributes = attributes.with_indifferent_access
16
+ end
17
+
18
+ attr_reader :attributes
19
+
20
+ def serializable_hash(options=nil)
21
+ attributes
22
+ end
23
+
24
+ def as_json(options=nil)
25
+ attributes
26
+ end
27
+
28
+ end
@@ -0,0 +1,69 @@
1
+ require 'oauth2'
2
+
3
+ module Devbootcamp::OAuth
4
+ class << self
5
+
6
+ attr_accessor *%w(
7
+ application_id
8
+ secret
9
+ site
10
+ callback_url
11
+ token
12
+ )
13
+
14
+ def client
15
+ @client ||= OAuth2::Client.new(
16
+ application_id, secret, site: site
17
+ )
18
+ end
19
+
20
+ def site_uri
21
+ URI(client.site)
22
+ end
23
+
24
+ def authorize_url(params={})
25
+ params[:redirect_uri] ||= callback_url
26
+ raise "redirect_uri cannot be blank" if params[:redirect_uri].blank?
27
+ client.auth_code.authorize_url(params)
28
+ end
29
+
30
+ def unauthorize_url(params={})
31
+ params[:redirect_uri] ||= callback_url
32
+ raise "redirect_uri cannot be blank" if params[:redirect_uri].blank?
33
+ site_uri.tap do |uri|
34
+ uri.path = '/unauthenticate'
35
+ uri.query = params.to_param
36
+ end.to_s
37
+ end
38
+
39
+ def token_as_hash
40
+ {
41
+ token: token.token,
42
+ refresh_token: token.refresh_token,
43
+ expires_at: token.expires_at,
44
+ }
45
+ end
46
+
47
+ def token_from_hash(hash)
48
+ return unless hash
49
+ @token = OAuth2::AccessToken.new(client, hash[:token], hash)
50
+ end
51
+
52
+ def authorized?
53
+ !!token
54
+ end
55
+
56
+ def authorize!(oauth_code, options = {})
57
+ @token = client.auth_code.get_token(oauth_code,
58
+ :redirect_uri => options.fetch(:redirect_uri, callback_url)
59
+ )
60
+ authorized?
61
+ end
62
+
63
+ def refresh!
64
+ self.token = token.refresh! if token && token.refresh_token
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,33 @@
1
+ module Devbootcamp::Rails
2
+
3
+ def self.draw_routes(router)
4
+ router.instance_eval do
5
+ get 'sign_in' => 'authentication#new', as: 'sign_in'
6
+ get 'sign_out' => 'authentication#destroy', as: 'sign_out'
7
+ get 'authentication/oauth_callback' => 'authentication#oauth_callback', as: 'oauth_callback'
8
+ end
9
+ end
10
+
11
+ def self.initialize!
12
+
13
+ application_id, secret, site = ENV.values_at *%w{DBC_OAUTH_APPLICATION_ID DBC_OAUTH_SECRET DBC_OAUTH_SITE}
14
+
15
+ if [application_id, secret, site].any?(&:blank?)
16
+ abort <<-SH
17
+ ABORTING! None of these environment variables can be blank:
18
+ DBC_OAUTH_APPLICATION_ID=#{application_id}
19
+ DBC_OAUTH_SECRET=#{secret}
20
+ DBC_OAUTH_SITE=#{site}
21
+ SH
22
+ end
23
+
24
+ Devbootcamp::OAuth.application_id = application_id
25
+ Devbootcamp::OAuth.secret = secret
26
+ Devbootcamp::OAuth.site = site
27
+
28
+ if Rails.application.config.action_controller.perform_caching
29
+ Devbootcamp::Auth.cache = Rails.cache
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,51 @@
1
+ module Devbootcamp::Rails::AuthenticationConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_filter :set_oauth_token!
6
+ before_filter :ensure_authenticated!
7
+ helper_method :authenticated?
8
+ helper_method :current_user
9
+ # rescue_from OAuth2::Error, :with => :unauthenticate!
10
+ end
11
+
12
+ private
13
+
14
+ def set_oauth_token!
15
+ Devbootcamp::OAuth.callback_url = oauth_callback_url
16
+ Devbootcamp::OAuth.token_from_hash(session[:oauth_token])
17
+ end
18
+
19
+ def ensure_authenticated!
20
+ return if authenticated?
21
+ redirect_to sign_in_path(r:request.url)
22
+ end
23
+
24
+ def authenticated?
25
+ session[:oauth_token].is_a?(Hash)
26
+ end
27
+
28
+ def unauthenticate!
29
+ session.clear
30
+ end
31
+
32
+ def current_user= current_user
33
+ @current_user = current_user
34
+ session[:current_user_attributes] = current_user.attributes
35
+ end
36
+
37
+ def current_user
38
+ return nil unless authenticated?
39
+ return @current_user if @current_user
40
+ @current_user = Devbootcamp::User.new(session[:current_user_attributes])
41
+ end
42
+
43
+ def devbootcamp_oauth_authorize_url
44
+ Devbootcamp::OAuth.authorize_url
45
+ end
46
+
47
+ def devbootcamp_oauth_unauthorize_url
48
+ Devbootcamp::OAuth.unauthorize_url :redirect_uri => root_url
49
+ end
50
+
51
+ end
@@ -0,0 +1,34 @@
1
+ class Devbootcamp::Rails::AuthenticationController < ApplicationController
2
+
3
+ skip_before_filter :ensure_authenticated!, except: :destroy
4
+
5
+ def new
6
+ destination_url = params[:r] || root_url
7
+ if authenticated?
8
+ redirect_to destination_url
9
+ else
10
+ session[:post_authorization_destination_url] = destination_url
11
+ redirect_to devbootcamp_oauth_authorize_url
12
+ end
13
+ end
14
+
15
+ def oauth_callback
16
+ oauth_code = params[:code]
17
+
18
+ if Devbootcamp::OAuth.authorize!(oauth_code)
19
+ self.current_user = Devbootcamp::User.me
20
+ session[:oauth_token] = Devbootcamp::OAuth.token_as_hash
21
+ flash[:notice] = "Welcome back #{current_user.name}"
22
+ else
23
+ flash[:error] = 'Authorization Failed'
24
+ end
25
+ redirect_to session[:post_authorization_destination_url] || root_url
26
+ end
27
+
28
+ def destroy
29
+ unauthenticate!
30
+ redirect_to devbootcamp_oauth_unauthorize_url
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,22 @@
1
+ require 'devbootcamp/model'
2
+
3
+ class Devbootcamp::User < Devbootcamp::Model
4
+
5
+ def self.find(user_id)
6
+ raise ArgumentError, "user_id cannot be blank" if user_id.blank?
7
+ new Devbootcamp::Auth.get("/users/#{user_id}")
8
+ end
9
+
10
+ def self.me
11
+ new Devbootcamp::Auth.get('/me')
12
+ end
13
+
14
+ def self.staff
15
+ Devbootcamp::Auth.get('/staff', cache_for: 1.hour).map(&method(:new))
16
+ end
17
+
18
+ attributes :id, :name, :email, :admin, :cohort_id, :avatar_image_url
19
+
20
+ alias_method :admin?, :admin
21
+
22
+ end
@@ -0,0 +1,3 @@
1
+ module Devbootcamp
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,31 @@
1
+ $LOAD_PATH.unshift File.expand_path("lib")
2
+ require 'bundler'
3
+ Bundler.require
4
+ p $LOAD_PATH
5
+ require "devbootcamp"
6
+
7
+ require 'minitest/autorun'
8
+ describe Devbootcamp::Cohort do
9
+ describe "#sf?" do
10
+ it "Is true when location is San Francisco" do
11
+ cohort = Devbootcamp::Cohort.new(location: "San Francisco")
12
+ assert cohort.sf?
13
+ end
14
+ it "Is false when the location is Chicago" do
15
+ cohort = Devbootcamp::Cohort.new(location: "Chicago")
16
+ refute cohort.sf?
17
+ end
18
+ end
19
+
20
+ describe "#chi?" do
21
+ it "is true when the location is Chicago" do
22
+ cohort = Devbootcamp::Cohort.new(location: "Chicago")
23
+ assert cohort.chi?
24
+ end
25
+
26
+ it "is false when the location is SF" do
27
+ cohort = Devbootcamp::Cohort.new(location: "San Francisco")
28
+ refute cohort.chi?
29
+ end
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devbootcamp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Jared Grippe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: oauth2
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: common code among internal devbootcamp apps
70
+ email:
71
+ - jared@devbootcamp.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - devbootcamp.gemspec
82
+ - lib/devbootcamp.rb
83
+ - lib/devbootcamp/auth.rb
84
+ - lib/devbootcamp/auth/client.rb
85
+ - lib/devbootcamp/auth/helpers.rb
86
+ - lib/devbootcamp/cohort.rb
87
+ - lib/devbootcamp/model.rb
88
+ - lib/devbootcamp/oauth.rb
89
+ - lib/devbootcamp/rails.rb
90
+ - lib/devbootcamp/rails/authentication_concern.rb
91
+ - lib/devbootcamp/rails/authentication_controller.rb
92
+ - lib/devbootcamp/user.rb
93
+ - lib/devbootcamp/version.rb
94
+ - spec/models/cohort_spec.rb
95
+ homepage: http://devbootcamp.com
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.1.10
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: common code among internal devbootcamp apps
119
+ test_files:
120
+ - spec/models/cohort_spec.rb