├── .rspec ├── lib ├── locomotive │ ├── view_counter.rb │ └── view_counter │ │ ├── version.rb │ │ └── middleware.rb └── locomotivecms_view_counter.rb ├── Gemfile ├── .gitignore ├── Rakefile ├── locomotivecms_view_counter.gemspec ├── README.md ├── LICENSE ├── spec ├── middleware_spec.rb └── spec_helper.rb └── Gemfile.lock /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /lib/locomotive/view_counter.rb: -------------------------------------------------------------------------------- 1 | require_relative 'view_counter/middleware' 2 | -------------------------------------------------------------------------------- /lib/locomotive/view_counter/version.rb: -------------------------------------------------------------------------------- 1 | module Locomotive 2 | module ViewCounter 3 | VERSION = '0.0.1' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/locomotivecms_view_counter.rb: -------------------------------------------------------------------------------- 1 | require 'locomotive/steam' 2 | 3 | require_relative 'locomotive/view_counter' 4 | 5 | Locomotive::Steam.configure_extension do |config| 6 | config.middleware.insert_after Locomotive::Steam::Middlewares::TemplatizedPage, Locomotive::ViewCounter::Middleware 7 | end 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in wagon.gemspec 4 | gemspec 5 | 6 | # Development 7 | # gem 'locomotivecms_steam', github: 'locomotivecms/steam', ref: '940837b', require: false 8 | 9 | # Local development 10 | gem 'locomotivecms_steam', path: '/Users/didier/Documents/LocomotiveCMS/gems/steam', require: false 11 | 12 | group :development, :test do 13 | gem 'pry-byebug', '~> 3.1.0' 14 | end 15 | 16 | group :test do 17 | gem 'rspec', '~> 3.2.0' 18 | gem 'rack-test', '~> 0.6.3' 19 | gem 'coveralls', '~> 0.7.11', require: false 20 | end 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | ## Specific to RubyMotion: 14 | .dat* 15 | .repl_history 16 | build/ 17 | 18 | ## Documentation cache and generated files: 19 | /.yardoc/ 20 | /_yardoc/ 21 | /doc/ 22 | /rdoc/ 23 | 24 | ## Environment normalization: 25 | /.bundle/ 26 | /vendor/bundle 27 | /lib/bundler/man/ 28 | 29 | # for a library or gem, you might want to ignore these files since the code is 30 | # intended to run in multiple environments; otherwise, check them in: 31 | # Gemfile.lock 32 | # .ruby-version 33 | # .ruby-gemset 34 | 35 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 36 | .rvmrc 37 | -------------------------------------------------------------------------------- /lib/locomotive/view_counter/middleware.rb: -------------------------------------------------------------------------------- 1 | module Locomotive 2 | module ViewCounter 3 | 4 | class Middleware 5 | 6 | def initialize(app) 7 | @app = app 8 | end 9 | 10 | def call(env) 11 | if !env['steam.live_editing'] && entry = env['steam.content_entry'] 12 | increment_view_counter(entry, env.fetch('steam.services')) 13 | end 14 | 15 | @app.call(env) 16 | end 17 | 18 | private 19 | 20 | def increment_view_counter(entry, services) 21 | type = entry.content_type 22 | 23 | if type.fields_by_name[:views] 24 | repository = services.repositories.content_entry.with(type) 25 | repository.inc(entry, :views) 26 | end 27 | end 28 | 29 | end 30 | 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # encoding: utf-8 3 | 4 | require 'rubygems' 5 | require 'bundler/setup' 6 | 7 | require 'rake' 8 | require 'rspec' 9 | require 'rspec/core/rake_task' 10 | require 'rubygems/package_task' 11 | 12 | $LOAD_PATH.unshift File.expand_path('../lib', __FILE__) 13 | 14 | require 'locomotivecms_view_counter' 15 | 16 | gemspec = eval(File.read('locomotivecms_view_counter.gemspec')) 17 | Gem::PackageTask.new(gemspec) do |pkg| 18 | pkg.gem_spec = gemspec 19 | end 20 | 21 | desc 'build the gem and release it to rubygems.org' 22 | task release: :gem do 23 | sh "gem push pkg/locomotivecms_view_counter-#{gemspec.version}.gem" 24 | end 25 | 26 | require 'rspec/core/rake_task' 27 | RSpec::Core::RakeTask.new('spec') 28 | 29 | # RSpec::Core::RakeTask.new('spec:unit') do |spec| 30 | # spec.pattern = 'spec/unit/**/*_spec.rb' 31 | 32 | # end 33 | 34 | # RSpec::Core::RakeTask.new('spec:integration') do |spec| 35 | # spec.pattern = 'spec/integration/**/*_spec.rb' 36 | # end 37 | 38 | task default: :spec 39 | -------------------------------------------------------------------------------- /locomotivecms_view_counter.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'locomotive/view_counter/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = 'locomotivecms_view_counter' 8 | gem.version = Locomotive::ViewCounter::VERSION 9 | gem.authors = ['Didier Lafforgue'] 10 | gem.email = ['did@locomotivecms.com'] 11 | gem.summary = %q{Increment the number of views of a content entry} 12 | gem.description = %q{Increment the number of views every time Steam renders a content type template.} 13 | gem.homepage = 'http://locomotive.works' 14 | gem.license = 'MIT' 15 | 16 | gem.files = `git ls-files`.split($/) 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ['lib'] 19 | 20 | gem.add_development_dependency 'rake', '~> 10.0.4' 21 | gem.add_development_dependency 'locomotivecms_steam', '~> 1.0.0' 22 | end 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # View counter for Locomotive content entries 2 | 3 | ## Assumptions 4 | 5 | - you add a field named **views** in any of your content types. 6 | - your **views** field is not localized. 7 | - the view counter is incremented when browsing a content entry outside the live editing mode. 8 | - you use one of the very last commits of Steam (https://github.com/locomotivecms/steam/commit/11112284764604e3bc21ed6306605f1fa0e1f7a7). 9 | 10 | ## Installation 11 | 12 | ### Wagon 13 | 14 | In the Gemfile of your Wagon site: 15 | 16 | group :misc do 17 | # Add your extra gems here 18 | # gem 'susy', require: 'susy' 19 | # gem 'bourbon', require: 'bourbon' 20 | gem 'locomotivecms_view_counter', github: 'did/locomotive_view_counter', require: true 21 | end 22 | 23 | ### Engine 24 | 25 | gem 'locomotivecms_view_counter', github: 'did/locomotive_view_counter', require: true 26 | 27 | ## TODO 28 | 29 | - support localized views field. 30 | 31 | ## Contact 32 | 33 | Feel free to contact me at did at locomotivecms dot com. 34 | 35 | Copyright (c) 2016 NoCoffee, released under the MIT license 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Didier Lafforgue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/middleware_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Locomotive::ViewCounter::Middleware do 4 | 5 | let(:password) { nil } 6 | let(:site) { instance_double('Site', name: 'Acme Corp') } 7 | let(:url) { 'http://www.acme.com/post/hello-world' } 8 | let(:repository) { instance_double('Repository') } 9 | let(:repositories) { instance_double('Repositories', content_entry: repository) } 10 | let(:services) { instance_double('Services', repositories: repositories) } 11 | let(:content_entry) { nil } 12 | let(:live_editing) { nil } 13 | let(:app) { ->(env) { [200, env, ['app']] } } 14 | let(:middleware) { described_class.new(app) } 15 | let(:rack_env) { build_env } 16 | 17 | subject { code, env, body = middleware.call(rack_env); body.first } 18 | 19 | before { allow(repository).to receive(:with).and_return(repository) } 20 | 21 | describe 'not a content type template' do 22 | 23 | it { is_expected.to eq 'app' } 24 | 25 | end 26 | 27 | describe 'display a content type template' do 28 | 29 | let(:content_type) { instance_double('PostContentType', fields_by_name: { views: true }) } 30 | let(:content_entry) { instance_double('Post', title: 'Hello world', content_type: content_type, views: 0) } 31 | 32 | it 'increments the number of views' do 33 | expect(repository).to receive(:inc).with(content_entry, :views) 34 | is_expected.to eq 'app' 35 | end 36 | 37 | context 'live preview on' do 38 | 39 | let(:live_editing) { true } 40 | 41 | it "doesn't increment the number of views" do 42 | expect(repository).not_to receive(:inc).with(content_entry, :views) 43 | is_expected.to eq 'app' 44 | end 45 | 46 | end 47 | 48 | context 'no views attribute' do 49 | 50 | let(:content_type) { instance_double('PostContentType', fields_by_name: {}) } 51 | let(:content_entry) { instance_double('Post', title: 'Hello world', content_type: content_type) } 52 | it { is_expected.to eq 'app' } 53 | 54 | end 55 | 56 | end 57 | 58 | def build_env 59 | env_for(url).tap do |env| 60 | env['steam.site'] = site 61 | env['steam.request'] = Rack::Request.new(env) 62 | env['steam.services'] = services 63 | env['steam.content_entry'] = content_entry 64 | env['steam.live_editing'] = live_editing 65 | end 66 | end 67 | 68 | def env_for(url, opts = {}) 69 | Rack::MockRequest.env_for(url, opts) 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | require 'locomotivecms_view_counter' 3 | require 'locomotive/steam/server' 4 | 5 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 6 | RSpec.configure do |config| 7 | # rspec-expectations config goes here. You can use an alternate 8 | # assertion/expectation library such as wrong or the stdlib/minitest 9 | # assertions if you prefer. 10 | config.expect_with :rspec do |expectations| 11 | # This option will default to `true` in RSpec 4. It makes the `description` 12 | # and `failure_message` of custom matchers include text for helper methods 13 | # defined using `chain`, e.g.: 14 | # be_bigger_than(2).and_smaller_than(4).description 15 | # # => "be bigger than 2 and smaller than 4" 16 | # ...rather than: 17 | # # => "be bigger than 2" 18 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 19 | end 20 | 21 | # rspec-mocks config goes here. You can use an alternate test double 22 | # library (such as bogus or mocha) by changing the `mock_with` option here. 23 | config.mock_with :rspec do |mocks| 24 | # Prevents you from mocking or stubbing a method that does not exist on 25 | # a real object. This is generally recommended, and will default to 26 | # `true` in RSpec 4. 27 | mocks.verify_partial_doubles = true 28 | end 29 | 30 | # The settings below are suggested to provide a good initial experience 31 | # with RSpec, but feel free to customize to your heart's content. 32 | =begin 33 | # These two settings work together to allow you to limit a spec run 34 | # to individual examples or groups you care about by tagging them with 35 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 36 | # get run. 37 | config.filter_run :focus 38 | config.run_all_when_everything_filtered = true 39 | 40 | # Limits the available syntax to the non-monkey patched syntax that is 41 | # recommended. For more details, see: 42 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 43 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 44 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 45 | config.disable_monkey_patching! 46 | 47 | # This setting enables warnings. It's recommended, but in some cases may 48 | # be too noisy due to issues in dependencies. 49 | config.warnings = true 50 | 51 | # Many RSpec users commonly either run the entire suite or an individual 52 | # file, and it's useful to allow more verbose output when running an 53 | # individual spec file. 54 | if config.files_to_run.one? 55 | # Use the documentation formatter for detailed output, 56 | # unless a formatter has already been configured 57 | # (e.g. via a command-line flag). 58 | config.default_formatter = 'doc' 59 | end 60 | 61 | # Print the 10 slowest examples and example groups at the 62 | # end of the spec run, to help surface which specs are running 63 | # particularly slow. 64 | config.profile_examples = 10 65 | 66 | # Run specs in random order to surface order dependencies. If you find an 67 | # order dependency and want to debug it, you can fix the order by providing 68 | # the seed, which is printed after each run. 69 | # --seed 1234 70 | config.order = :random 71 | 72 | # Seed global randomization in this process using the `--seed` CLI option. 73 | # Setting this allows you to use `--seed` to deterministically reproduce 74 | # test failures related to randomization by passing the same `--seed` value 75 | # as the one that triggered the failure. 76 | Kernel.srand config.seed 77 | =end 78 | end 79 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | locomotivecms_view_counter (0.0.1) 5 | 6 | PATH 7 | remote: /Users/didier/Documents/LocomotiveCMS/gems/steam 8 | specs: 9 | locomotivecms_steam (1.0.0) 10 | RedCloth (~> 4.2.9) 11 | autoprefixer-rails (~> 6.2.3) 12 | chronic (~> 0.10.2) 13 | coffee-script (~> 2.4.1) 14 | compass (~> 1.0.3) 15 | dragonfly (~> 1.0.12) 16 | haml (~> 4.0.6) 17 | httparty (~> 0.13.6) 18 | kramdown (~> 1.9.0) 19 | locomotivecms-solid (~> 4.0.1) 20 | locomotivecms_common (~> 0.0.5) 21 | mime-types (~> 2.6.1) 22 | mimetype-fu (~> 0.1.2) 23 | moneta (~> 0.8.0) 24 | morphine (~> 0.1.1) 25 | nokogiri (~> 1.6.7.1) 26 | rack-cache (~> 1.2) 27 | rack-rewrite (~> 1.5.1) 28 | rack_csrf (~> 2.5.0) 29 | sanitize (~> 4.0.1) 30 | sass (~> 3.4.21) 31 | sprockets (~> 3.5.2) 32 | 33 | GEM 34 | remote: https://rubygems.org/ 35 | specs: 36 | RedCloth (4.2.9) 37 | activesupport (4.2.5) 38 | i18n (~> 0.7) 39 | json (~> 1.7, >= 1.7.7) 40 | minitest (~> 5.1) 41 | thread_safe (~> 0.3, >= 0.3.4) 42 | tzinfo (~> 1.1) 43 | addressable (2.4.0) 44 | attr_extras (4.4.0) 45 | autoprefixer-rails (6.2.3) 46 | execjs 47 | json 48 | byebug (4.0.5) 49 | columnize (= 0.9.0) 50 | chronic (0.10.2) 51 | chunky_png (1.3.5) 52 | coderay (1.1.0) 53 | coffee-script (2.4.1) 54 | coffee-script-source 55 | execjs 56 | coffee-script-source (1.10.0) 57 | colorize (0.7.7) 58 | columnize (0.9.0) 59 | compass (1.0.3) 60 | chunky_png (~> 1.2) 61 | compass-core (~> 1.0.2) 62 | compass-import-once (~> 1.0.5) 63 | rb-fsevent (>= 0.9.3) 64 | rb-inotify (>= 0.9) 65 | sass (>= 3.3.13, < 3.5) 66 | compass-core (1.0.3) 67 | multi_json (~> 1.0) 68 | sass (>= 3.3.0, < 3.5) 69 | compass-import-once (1.0.5) 70 | sass (>= 3.2, < 3.5) 71 | concurrent-ruby (1.0.0) 72 | coveralls (0.7.12) 73 | multi_json (~> 1.10) 74 | rest-client (>= 1.6.8, < 2) 75 | simplecov (~> 0.9.1) 76 | term-ansicolor (~> 1.3) 77 | thor (~> 0.19.1) 78 | crass (1.0.2) 79 | diff-lcs (1.2.5) 80 | docile (1.1.5) 81 | domain_name (0.5.25) 82 | unf (>= 0.0.5, < 1.0.0) 83 | dragonfly (1.0.12) 84 | addressable (~> 2.3) 85 | multi_json (~> 1.0) 86 | rack (>= 1.3.0) 87 | execjs (2.6.0) 88 | ffi (1.9.10) 89 | haml (4.0.7) 90 | tilt 91 | http-cookie (1.0.2) 92 | domain_name (~> 0.5) 93 | httparty (0.13.7) 94 | json (~> 1.8) 95 | multi_xml (>= 0.5.2) 96 | i18n (0.7.0) 97 | json (1.8.3) 98 | kramdown (1.9.0) 99 | locomotivecms-liquid (4.0.0) 100 | locomotivecms-solid (4.0.1) 101 | locomotivecms-liquid (~> 4.0.0) 102 | locomotivecms_common (0.0.5) 103 | activesupport (~> 4.2.1) 104 | attr_extras (~> 4.4.0) 105 | colorize 106 | stringex (~> 2.5.2) 107 | method_source (0.8.2) 108 | mime-types (2.6.2) 109 | mimetype-fu (0.1.2) 110 | mini_portile2 (2.0.0) 111 | minitest (5.8.3) 112 | moneta (0.8.0) 113 | morphine (0.1.1) 114 | multi_json (1.11.2) 115 | multi_xml (0.5.5) 116 | netrc (0.11.0) 117 | nokogiri (1.6.7.1) 118 | mini_portile2 (~> 2.0.0.rc2) 119 | nokogumbo (1.4.7) 120 | nokogiri 121 | pry (0.10.3) 122 | coderay (~> 1.1.0) 123 | method_source (~> 0.8.1) 124 | slop (~> 3.4) 125 | pry-byebug (3.1.0) 126 | byebug (~> 4.0) 127 | pry (~> 0.10) 128 | rack (1.6.4) 129 | rack-cache (1.5.1) 130 | rack (>= 0.4) 131 | rack-rewrite (1.5.1) 132 | rack-test (0.6.3) 133 | rack (>= 1.0) 134 | rack_csrf (2.5.0) 135 | rack (>= 1.1.0) 136 | rake (10.0.4) 137 | rb-fsevent (0.9.7) 138 | rb-inotify (0.9.5) 139 | ffi (>= 0.5.0) 140 | rest-client (1.8.0) 141 | http-cookie (>= 1.0.2, < 2.0) 142 | mime-types (>= 1.16, < 3.0) 143 | netrc (~> 0.7) 144 | rspec (3.2.0) 145 | rspec-core (~> 3.2.0) 146 | rspec-expectations (~> 3.2.0) 147 | rspec-mocks (~> 3.2.0) 148 | rspec-core (3.2.3) 149 | rspec-support (~> 3.2.0) 150 | rspec-expectations (3.2.1) 151 | diff-lcs (>= 1.2.0, < 2.0) 152 | rspec-support (~> 3.2.0) 153 | rspec-mocks (3.2.1) 154 | diff-lcs (>= 1.2.0, < 2.0) 155 | rspec-support (~> 3.2.0) 156 | rspec-support (3.2.2) 157 | sanitize (4.0.1) 158 | crass (~> 1.0.2) 159 | nokogiri (>= 1.4.4) 160 | nokogumbo (~> 1.4.1) 161 | sass (3.4.21) 162 | simplecov (0.9.2) 163 | docile (~> 1.1.0) 164 | multi_json (~> 1.0) 165 | simplecov-html (~> 0.9.0) 166 | simplecov-html (0.9.0) 167 | slop (3.6.0) 168 | sprockets (3.5.2) 169 | concurrent-ruby (~> 1.0) 170 | rack (> 1, < 3) 171 | stringex (2.5.2) 172 | term-ansicolor (1.3.2) 173 | tins (~> 1.0) 174 | thor (0.19.1) 175 | thread_safe (0.3.5) 176 | tilt (2.0.2) 177 | tins (1.8.1) 178 | tzinfo (1.2.2) 179 | thread_safe (~> 0.1) 180 | unf (0.1.4) 181 | unf_ext 182 | unf_ext (0.0.7.1) 183 | 184 | PLATFORMS 185 | ruby 186 | 187 | DEPENDENCIES 188 | coveralls (~> 0.7.11) 189 | locomotivecms_steam! 190 | locomotivecms_view_counter! 191 | pry-byebug (~> 3.1.0) 192 | rack-test (~> 0.6.3) 193 | rake (~> 10.0.4) 194 | rspec (~> 3.2.0) 195 | 196 | BUNDLED WITH 197 | 1.11.2 198 | --------------------------------------------------------------------------------