minimum_viable_product 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/minimum_viable_product/application.js.erb +38 -0
  6. data/app/assets/javascripts/minimum_viable_product/init/controllers.js +23 -0
  7. data/app/assets/stylesheets/minimum_viable_product/application.scss +23 -0
  8. data/app/assets/stylesheets/minimum_viable_product/components/buttons.scss +20 -0
  9. data/app/assets/stylesheets/minimum_viable_product/components/fonts.scss +15 -0
  10. data/app/assets/stylesheets/minimum_viable_product/components/spacing.scss +13 -0
  11. data/app/assets/stylesheets/minimum_viable_product/components/typography.scss +40 -0
  12. data/app/assets/stylesheets/minimum_viable_product/init/_reset.scss +43 -0
  13. data/app/assets/stylesheets/minimum_viable_product/init/layout.scss +42 -0
  14. data/app/assets/stylesheets/minimum_viable_product/layout/basic.scss +6 -0
  15. data/app/assets/stylesheets/minimum_viable_product/layout/carousel.scss +128 -0
  16. data/app/assets/stylesheets/minimum_viable_product/layout/cover.scss +111 -0
  17. data/app/assets/stylesheets/minimum_viable_product/pages/sample.scss +14 -0
  18. data/app/assets/stylesheets/minimum_viable_product/skin/style.scss +8 -0
  19. data/app/controllers/minimum_viable_product/analytics_controller.rb +14 -0
  20. data/app/controllers/minimum_viable_product/concerns/analytics_concern.rb +77 -0
  21. data/app/controllers/minimum_viable_product/concerns/seo_concern.rb +25 -0
  22. data/app/controllers/minimum_viable_product/concerns/session_concern.rb +40 -0
  23. data/app/controllers/minimum_viable_product/controller.rb +11 -0
  24. data/app/controllers/minimum_viable_product/seo_controller.rb +13 -0
  25. data/app/controllers/minimum_viable_product/styleguide_controller.rb +17 -0
  26. data/app/helpers/minimum_viable_product/analytics_helper.rb +4 -0
  27. data/app/helpers/minimum_viable_product/application_helper.rb +15 -0
  28. data/app/helpers/minimum_viable_product/bootstrap_helper.rb +38 -0
  29. data/app/helpers/minimum_viable_product/seo_helper.rb +4 -0
  30. data/app/helpers/minimum_viable_product/styleguide_helper.rb +4 -0
  31. data/app/models/concerns/slugification.rb +16 -0
  32. data/app/views/layouts/minimum_viable_product/_header.html.erb +48 -0
  33. data/app/views/layouts/minimum_viable_product/_instrumentation.html.erb +75 -0
  34. data/app/views/layouts/minimum_viable_product/_meta.html.erb +37 -0
  35. data/app/views/layouts/minimum_viable_product/application.html.erb +24 -0
  36. data/app/views/layouts/minimum_viable_product/basic.html.erb +10 -0
  37. data/app/views/layouts/minimum_viable_product/carousel.html.erb +13 -0
  38. data/app/views/layouts/minimum_viable_product/cover.html.erb +13 -0
  39. data/bin/mvp +68 -0
  40. data/config/initializers/canonical_host.rb +3 -0
  41. data/config/initializers/client_side_validations.rb +20 -0
  42. data/config/initializers/cloudinary.rb +6 -0
  43. data/config/initializers/geocoder.rb +9 -0
  44. data/config/initializers/iteration.rb +33 -0
  45. data/config/initializers/rollbar.rb +59 -0
  46. data/config/initializers/segment.rb +5 -0
  47. data/config/initializers/simple_form.rb +165 -0
  48. data/config/initializers/simple_form_bootstrap.rb +149 -0
  49. data/config/initializers/spoof_ip.rb +33 -0
  50. data/config/initializers/ssl.rb +3 -0
  51. data/config/routes.rb +16 -0
  52. data/lib/minimum_viable_product/engine.rb +20 -0
  53. data/lib/minimum_viable_product/ext/nil.rb +5 -0
  54. data/lib/minimum_viable_product/ext/string.rb +7 -0
  55. data/lib/minimum_viable_product/version.rb +3 -0
  56. data/lib/minimum_viable_product.rb +6 -0
  57. data/lib/tasks/sitemap.rake +42 -0
  58. data/test/dummy/README.rdoc +28 -0
  59. data/test/dummy/Rakefile +6 -0
  60. data/test/dummy/app/assets/javascripts/application.js +13 -0
  61. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  62. data/test/dummy/app/controllers/application_controller.rb +5 -0
  63. data/test/dummy/app/helpers/application_helper.rb +2 -0
  64. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  65. data/test/dummy/bin/bundle +3 -0
  66. data/test/dummy/bin/rails +4 -0
  67. data/test/dummy/bin/rake +4 -0
  68. data/test/dummy/bin/setup +29 -0
  69. data/test/dummy/config/application.rb +26 -0
  70. data/test/dummy/config/boot.rb +5 -0
  71. data/test/dummy/config/database.yml +25 -0
  72. data/test/dummy/config/environment.rb +5 -0
  73. data/test/dummy/config/environments/development.rb +41 -0
  74. data/test/dummy/config/environments/production.rb +79 -0
  75. data/test/dummy/config/environments/test.rb +42 -0
  76. data/test/dummy/config/initializers/assets.rb +11 -0
  77. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  78. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  79. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  80. data/test/dummy/config/initializers/inflections.rb +16 -0
  81. data/test/dummy/config/initializers/mime_types.rb +4 -0
  82. data/test/dummy/config/initializers/session_store.rb +3 -0
  83. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  84. data/test/dummy/config/locales/en.yml +23 -0
  85. data/test/dummy/config/routes.rb +4 -0
  86. data/test/dummy/config/secrets.yml +22 -0
  87. data/test/dummy/config.ru +4 -0
  88. data/test/dummy/public/404.html +67 -0
  89. data/test/dummy/public/422.html +67 -0
  90. data/test/dummy/public/500.html +66 -0
  91. data/test/dummy/public/favicon.ico +0 -0
  92. data/test/integration/navigation_test.rb +8 -0
  93. data/test/minimum_viable_product_test.rb +7 -0
  94. data/test/test_helper.rb +21 -0
  95. metadata +595 -0
@@ -0,0 +1,111 @@
1
+ body.body-cover {
2
+ text-align: center;
3
+
4
+ .site-wrapper {
5
+ display: table;
6
+ width: 100%;
7
+ height: 100%; /* For at least Firefox */
8
+ min-height: 100%;
9
+ }
10
+ .site-wrapper-inner {
11
+ display: table-cell;
12
+ vertical-align: top;
13
+ }
14
+ .cover-container {
15
+ margin-right: auto;
16
+ margin-left: auto;
17
+ }
18
+
19
+ /* Padding for spacing */
20
+ .inner {
21
+ padding: 30px;
22
+ }
23
+
24
+
25
+ /*
26
+ * Header
27
+ */
28
+ .masthead-brand {
29
+ margin-top: 10px;
30
+ margin-bottom: 10px;
31
+ }
32
+
33
+ .masthead-nav > li {
34
+ display: inline-block;
35
+ }
36
+ .masthead-nav > li + li {
37
+ margin-left: 20px;
38
+ }
39
+ .masthead-nav > li > a {
40
+ padding-right: 0;
41
+ padding-left: 0;
42
+ font-size: 16px;
43
+ font-weight: bold;
44
+ }
45
+ .masthead-nav > li > a:hover,
46
+ .masthead-nav > li > a:focus {
47
+ background-color: transparent;
48
+ }
49
+ .masthead-nav > .active > a,
50
+ .masthead-nav > .active > a:hover,
51
+ .masthead-nav > .active > a:focus {
52
+
53
+ }
54
+
55
+ @media (min-width: 768px) {
56
+ .masthead-brand {
57
+ float: left;
58
+ }
59
+ .masthead-nav {
60
+ float: right;
61
+ }
62
+ }
63
+
64
+
65
+ /*
66
+ * Cover
67
+ */
68
+
69
+ .cover {
70
+ padding: 0 20px;
71
+ }
72
+ .cover .btn-lg {
73
+ padding: 10px 20px;
74
+ font-weight: bold;
75
+ }
76
+
77
+
78
+ /*
79
+ * Affix and center
80
+ */
81
+
82
+ @media (min-width: 768px) {
83
+ /* Pull out the header and footer */
84
+ .masthead {
85
+ position: fixed;
86
+ top: 0;
87
+ }
88
+ .mastfoot {
89
+ position: fixed;
90
+ bottom: 0;
91
+ }
92
+ /* Start the vertical centering */
93
+ .site-wrapper-inner {
94
+ vertical-align: middle;
95
+ }
96
+ /* Handle the widths */
97
+ .masthead,
98
+ .mastfoot,
99
+ .cover-container {
100
+ width: 100%; /* Must be percentage or pixels for horizontal alignment */
101
+ }
102
+ }
103
+
104
+ @media (min-width: 992px) {
105
+ .masthead,
106
+ .mastfoot,
107
+ .cover-container {
108
+ width: 700px;
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,14 @@
1
+ .home-pages {
2
+ .topic {
3
+ position:relative;
4
+
5
+ &:hover {
6
+ cursor: pointer;
7
+ }
8
+
9
+ p {
10
+ text-transform: uppercase;
11
+ font-weight: bold;
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,8 @@
1
+ body {
2
+ background: #F3F3F3;
3
+ font-family: 'Open Sans', sans-serif;
4
+ }
5
+
6
+ h1,h2,h3,h4,h5,h6 {
7
+ font-weight: 700;
8
+ }
@@ -0,0 +1,14 @@
1
+ require_dependency "minimum_viable_product/controller"
2
+
3
+ module MinimumViableProduct
4
+ class AnalyticsController < Controller
5
+ def optout
6
+ cookies[AnalyticsConcern::INVISIBLE_SESSION_COOKIE] = {
7
+ :value => true,
8
+ :expires => 1.year.from_now,
9
+ }
10
+
11
+ redirect_to root_path
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,77 @@
1
+ module MinimumViableProduct
2
+ module AnalyticsConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ INVISIBLE_SESSION_COOKIE = 'invisible'
6
+
7
+ included do
8
+ before_action :generate_uid_if_not_set
9
+ helper_method :analytics_id
10
+ end
11
+
12
+ def track!(event, properties={})
13
+ return if session[INVISIBLE_SESSION_COOKIE].to_b == true
14
+ return unless analytics
15
+ analytics.track({
16
+ user_id: analytics_id,
17
+ event: event,
18
+ properties: properties.reverse_merge({
19
+ iteration: MVP::Iteration.version,
20
+ user: current_user.try(:name),
21
+ url: request.fullpath
22
+ })
23
+ })
24
+ end
25
+
26
+ def slack!(message)
27
+ if ENV['SLACK_WEBHOOK_URL']
28
+ begin
29
+ slack_notifier.ping message
30
+ rescue
31
+ slack_notifier.ping "Tried to use slack messaging but failure happened"
32
+ end
33
+ end
34
+ end
35
+
36
+ def page!(name, opts={})
37
+ return if sessions[INVISIBLE_SESSION_COOKIE].to_b == true
38
+ return unless analytics
39
+ analytics.page(
40
+ user_id: current_user.try(:id),
41
+ name: name,
42
+ category: opts[:category],
43
+ properties: {
44
+ url: opts[:url],
45
+ iteration: MVP::Iteration.version
46
+ }
47
+ )
48
+ end
49
+
50
+ private
51
+
52
+ def analytics
53
+ unless defined?(Analytics)
54
+ puts ""
55
+ puts "Please set SEGMENT_WRITE_KEY in .env"
56
+ puts ""
57
+ nil
58
+ else
59
+ Analytics
60
+ end
61
+ end
62
+
63
+ def slack_notifier
64
+ Slack::Notifier.new ENV['SLACK_WEBHOOK_URL'], channel: ENV['SLACK_WEBHOOK_URL'],
65
+ username: ENV['SLACK_POST_USERNAME'],
66
+ channel: ENV['SLACK_POST_CHANNEL']
67
+ end
68
+
69
+ def analytics_id
70
+ current_user.try(:id) || cookies[:uid]
71
+ end
72
+
73
+ def generate_uid_if_not_set
74
+ cookies[:uid] = SecureRandom.uuid unless cookies[:uid].present?
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,25 @@
1
+ module MinimumViableProduct
2
+ module SEOConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+
7
+ included do
8
+ before_action :force_no_trailing_slash
9
+ helper_method :page
10
+ end
11
+
12
+ def force_no_trailing_slash
13
+ return if request.path == '/'
14
+ redirect_to request.original_url.gsub(/\/$/,'') if request.original_url.match(/\/$/)
15
+ end
16
+
17
+ def page
18
+ @page ||= begin
19
+ h = Hashie::Mash.new
20
+ h.classes = []
21
+ h
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ module MinimumViableProduct
2
+ module SessionConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ COOKIE_ID = '_session'
6
+
7
+ included do
8
+ helper_method :current_user
9
+ end
10
+
11
+ def sign_in(user)
12
+ session[COOKIE_ID] = user.id
13
+ end
14
+
15
+ def current_user
16
+ @current_user ||= begin
17
+ if session[COOKIE_ID]
18
+ Conversion.find(session[COOKIE_ID])
19
+ end
20
+ rescue ActiveRecord::RecordNotFound
21
+ session[COOKIE_ID] = nil
22
+ end
23
+ end
24
+
25
+ # http://blog.laaz.org/tech/2012/12/27/rails-redirect_back_or_default/
26
+ def store_location
27
+ session[:return_to] = if request.get?
28
+ request.url
29
+ else
30
+ request.referer
31
+ end
32
+ end
33
+
34
+ # http://blog.laaz.org/tech/2012/12/27/rails-redirect_back_or_default/
35
+ def redirect_back_or_default(default = root_url)
36
+ redirect_to(session.delete(:return_to) || request.referer || default)
37
+ end
38
+ alias_method :redirect_to_back_or_default, :redirect_back_or_default
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'concerns/analytics_concern'
2
+ require_relative 'concerns/seo_concern'
3
+ require_relative 'concerns/session_concern'
4
+
5
+ module MinimumViableProduct
6
+ class Controller < ActionController::Base
7
+ include MinimumViableProduct::AnalyticsConcern
8
+ include MinimumViableProduct::SEOConcern
9
+ include MinimumViableProduct::SessionConcern
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require_dependency "minimum_viable_product/controller"
2
+
3
+ module MinimumViableProduct
4
+ class SeoController < Controller
5
+ def robots
6
+ render text: %%
7
+ User-agent: *
8
+ Disallow:
9
+ Sitemap: #{sitemap_url}
10
+ %, content_type: "text/plain"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ require_dependency "minimum_viable_product/controller"
2
+
3
+ module MinimumViableProduct
4
+ class StyleguideController < Controller
5
+ def layout_basic
6
+ render template: 'styleguide/layouts/basic', layout: 'basic'
7
+ end
8
+
9
+ def layout_cover
10
+ render template: 'styleguide/layouts/cover', layout: 'cover'
11
+ end
12
+
13
+ def layout_carousel
14
+ render template: 'styleguide/layouts/carousel', layout: 'carousel'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ module MinimumViableProduct
2
+ module AnalyticsHelper
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module MinimumViableProduct
2
+ module ApplicationHelper
3
+ def body_id
4
+ "#{controller_name}-#{action_name}-page"
5
+ end
6
+
7
+ def body_classes
8
+ klass = []
9
+ klass << "#{controller_name}-pages"
10
+ klass << page.classes if page.classes.present?
11
+ klass << "hidden-header" if page.header == false
12
+ klass.join(' ')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ module MinimumViableProduct
2
+ module BootstrapHelper
3
+ def col_groups_of(count, collection, opts={})
4
+ return if collection.blank?
5
+
6
+ html = []
7
+ collection.in_groups_of(count).each do |groups|
8
+ groups.compact.each do |obj|
9
+ html << capture do
10
+ content_tag :div, class: ["col-md-#{12/count.to_f.floor}", opts[:col_class]].compact.join(' ') do
11
+ yield obj
12
+ end
13
+ end.html_safe
14
+ end
15
+ end
16
+ concat html.join.html_safe
17
+ end
18
+
19
+ def row_groups_of(count, collection, opts={}, &block)
20
+ return if collection.blank?
21
+
22
+ html = []
23
+ subsetsize = (collection.count/count.to_f)
24
+ subsetsize.ceil.times do |i|
25
+ subset = collection[count*i...((count*i)+count)]
26
+ html << capture do
27
+ content_tag :div, class: ["row",opts[:row_class]].compact.join(' ') do
28
+ capture do
29
+ col_groups_of(count, subset, opts, &block)
30
+ end
31
+ end
32
+ end.html_safe
33
+ end
34
+
35
+ concat html.join.html_safe
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ module MinimumViableProduct
2
+ module SeoHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module MinimumViableProduct
2
+ module StyleguideHelper
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ module Slugification
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def slugify(name, opts={})
6
+ _opts = opts.reverse_merge(:if => lambda{ |obj| obj.send("#{name}_changed?".to_sym) })
7
+ before_validation -> { self.slug = slugify(self.send(name.to_sym)) }, _opts
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def slugify(str)
14
+ str.gsub(/\s/,'_').dasherize.downcase
15
+ end
16
+ end
@@ -0,0 +1,48 @@
1
+ <nav class="navbar navbar-default mt20">
2
+ <div class="container-fluid">
3
+ <!-- Brand and toggle get grouped for better mobile display -->
4
+ <div class="navbar-header">
5
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
6
+ <span class="sr-only">Toggle navigation</span>
7
+ <span class="icon-bar"></span>
8
+ <span class="icon-bar"></span>
9
+ <span class="icon-bar"></span>
10
+ </button>
11
+ <a class="navbar-brand" href="#">__PROJECT_NAME__</a>
12
+ </div>
13
+
14
+ <!-- Collect the nav links, forms, and other content for toggling -->
15
+ <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
16
+ <ul class="nav navbar-nav">
17
+ <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
18
+ <li><a href="#">Link</a></li>
19
+ <li class="dropdown">
20
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
21
+ <ul class="dropdown-menu">
22
+ <li><a href="#">Action</a></li>
23
+ <li><a href="#">Another action</a></li>
24
+ <li><a href="#">Something else here</a></li>
25
+ <li role="separator" class="divider"></li>
26
+ <li><a href="#">Separated link</a></li>
27
+ <li role="separator" class="divider"></li>
28
+ <li><a href="#">One more separated link</a></li>
29
+ </ul>
30
+ </li>
31
+ </ul>
32
+
33
+ <ul class="nav navbar-nav navbar-right">
34
+ <li><a href="#">Link</a></li>
35
+ <li class="dropdown">
36
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
37
+ <ul class="dropdown-menu">
38
+ <li><a href="#">Action</a></li>
39
+ <li><a href="#">Another action</a></li>
40
+ <li><a href="#">Something else here</a></li>
41
+ <li role="separator" class="divider"></li>
42
+ <li><a href="#">Separated link</a></li>
43
+ </ul>
44
+ </li>
45
+ </ul>
46
+ </div><!-- /.navbar-collapse -->
47
+ </div><!-- /.container-fluid -->
48
+ </nav>