├── test ├── dummy │ ├── log │ │ └── .gitkeep │ ├── app │ │ ├── mailers │ │ │ └── .gitkeep │ │ ├── models │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ ├── application_helper.rb │ │ │ └── advert_selector_helper.rb │ │ ├── controllers │ │ │ ├── examples_controller.rb │ │ │ └── application_controller.rb │ │ ├── views │ │ │ ├── layouts │ │ │ │ └── application.html.erb │ │ │ └── examples │ │ │ │ └── index.html.erb │ │ └── assets │ │ │ ├── stylesheets │ │ │ ├── application.css │ │ │ └── scaffold.css │ │ │ └── javascripts │ │ │ └── application.js │ ├── lib │ │ ├── assets │ │ │ └── .gitkeep │ │ └── templates │ │ │ └── erb │ │ │ └── scaffold │ │ │ └── _form.html.erb │ ├── public │ │ ├── favicon.ico │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── test │ │ ├── fixtures │ │ └── unit │ │ │ └── helpers │ │ │ └── examples_helper_test.rb │ ├── config.ru │ ├── config │ │ ├── environment.rb │ │ ├── locales │ │ │ ├── en.yml │ │ │ └── simple_form.en.yml │ │ ├── initializers │ │ │ ├── mime_types.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── session_store.rb │ │ │ ├── wrap_parameters.rb │ │ │ ├── inflections.rb │ │ │ ├── secret_token.rb │ │ │ └── simple_form.rb │ │ ├── routes.rb │ │ ├── boot.rb │ │ ├── database.yml │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── test.rb │ │ │ └── production.rb │ │ └── application.rb │ ├── Rakefile │ ├── script │ │ └── rails │ ├── db │ │ └── schema.rb │ └── README.rdoc ├── unit │ ├── helpers │ │ └── advert_selector │ │ │ ├── banners_helper_test.rb │ │ │ └── placements_helper_test.rb │ └── advert_selector │ │ ├── helper_item_test.rb │ │ ├── placement_test.rb │ │ └── banner_test.rb ├── advert_selector_test.rb ├── integration │ ├── navigation_test.rb │ └── banner_shows_test.rb ├── functional │ ├── examples_controller_test.rb │ └── advert_selector │ │ ├── main_controller_test.rb │ │ ├── placements_controller_test.rb │ │ └── banners_controller_test.rb ├── fixtures │ └── advert_selector │ │ ├── placements.yml │ │ ├── helper_items.yml │ │ └── banners.yml └── test_helper.rb ├── app ├── assets │ ├── images │ │ └── advert_selector │ │ │ └── .gitkeep │ ├── javascripts │ │ └── advert_selector │ │ │ └── application.js │ └── stylesheets │ │ └── advert_selector │ │ └── application.css ├── views │ ├── advert_selector │ │ ├── banners │ │ │ ├── new.html.erb │ │ │ ├── show.html.erb │ │ │ ├── index.html.erb │ │ │ ├── edit.html.erb │ │ │ └── _form.html.erb │ │ ├── placements │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── show.html.erb │ │ │ ├── index.html.erb │ │ │ └── _form.html.erb │ │ ├── helper_items │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── show.html.erb │ │ │ ├── index.html.erb │ │ │ └── _form.html.erb │ │ └── main │ │ │ └── index.html.erb │ └── layouts │ │ └── advert_selector │ │ └── application.html.erb ├── controllers │ └── advert_selector │ │ ├── main_controller.rb │ │ ├── application_controller.rb │ │ ├── placements_controller.rb │ │ └── banners_controller.rb ├── models │ └── advert_selector │ │ ├── helper_item.rb │ │ ├── placement.rb │ │ └── banner.rb └── helpers │ └── advert_selector │ └── application_helper.rb ├── lib ├── advert_selector │ ├── version.rb │ ├── errors_cache.rb │ └── engine.rb ├── tasks │ └── advert_selector_tasks.rake └── advert_selector.rb ├── .travis.yml ├── script ├── package ├── rails └── release ├── .gitignore ├── CHANGELOG.md ├── db └── migrate │ ├── 20120924165938_create_advert_selector_placements.rb │ ├── 20120926132649_create_advert_selector_helper_items.rb │ └── 20120925122858_create_advert_selector_banners.rb ├── config └── routes.rb ├── Rakefile ├── Gemfile ├── MIT-LICENSE ├── advert_selector.gemspec └── README.md /test/dummy/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/advert_selector/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures: -------------------------------------------------------------------------------- 1 | ../../fixtures/ -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /lib/advert_selector/version.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelector 2 | VERSION = "3.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/examples_controller.rb: -------------------------------------------------------------------------------- 1 | class ExamplesController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/test/unit/helpers/examples_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ExamplesHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/views/advert_selector/banners/new.html.erb: -------------------------------------------------------------------------------- 1 |

New banner

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', banners_path %> 6 | -------------------------------------------------------------------------------- /lib/tasks/advert_selector_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :advert_selector do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | end 5 | -------------------------------------------------------------------------------- /app/views/advert_selector/placements/new.html.erb: -------------------------------------------------------------------------------- 1 |

New placement

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', placements_path %> 6 | -------------------------------------------------------------------------------- /app/views/advert_selector/helper_items/new.html.erb: -------------------------------------------------------------------------------- 1 |

New helper_item

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', helper_items_path %> 6 | -------------------------------------------------------------------------------- /test/unit/helpers/advert_selector/banners_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module AdvertSelector 4 | class BannersHelperTest < ActionView::TestCase 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/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 Dummy::Application 5 | -------------------------------------------------------------------------------- /test/unit/helpers/advert_selector/placements_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module AdvertSelector 4 | class PlacementsHelperTest < ActionView::TestCase 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/views/advert_selector/placements/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing placement

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @placement %> | 6 | <%= link_to 'Back', placements_path %> 7 | -------------------------------------------------------------------------------- /test/advert_selector_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AdvertSelectorTest < ActiveSupport::TestCase 4 | test "truth" do 5 | assert_kind_of Module, AdvertSelector 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /app/views/advert_selector/helper_items/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing helper_item

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @helper_item %> | 6 | <%= link_to 'Back', helper_items_path %> 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 2.4.3 3 | # - 2.5.0 4 | before_script: 5 | - 'RAILS_ENV=test bundle exec rake db:create db:schema:load --trace' 6 | env: 7 | - "RAILS_VERSION=5.0.6" 8 | - "RAILS_VERSION=5.1.2" 9 | -------------------------------------------------------------------------------- /script/package: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: script/package 3 | # Updates the gemspec and builds a new gem in the pkg directory. 4 | 5 | mkdir -p pkg 6 | chmod -R a+r * 7 | gem build *.gemspec 8 | mv *.gem pkg 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | test/dummy/db/*.sqlite3 5 | test/dummy/log/*.log 6 | test/dummy/tmp/ 7 | test/dummy/.sass-cache 8 | 9 | .idea 10 | .rvmrc 11 | 12 | *.gem 13 | .bundle 14 | Gemfile.lock 15 | -------------------------------------------------------------------------------- /test/integration/navigation_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NavigationTest < ActionDispatch::IntegrationTest 4 | # fixtures :all 5 | 6 | # test "the truth" do 7 | # assert true 8 | # end 9 | end 10 | 11 | -------------------------------------------------------------------------------- /test/unit/advert_selector/helper_item_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module AdvertSelector 4 | class HelperItemTest < ActiveSupport::TestCase 5 | # test "the truth" do 6 | # assert true 7 | # end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/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_relative 'config/application' 5 | 6 | Rails.application.load_tasks -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /test/dummy/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 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | #get "examples/index" 3 | 4 | mount AdvertSelector::Engine => "/advert_selector" 5 | 6 | # match '(:action)' => 'examples', via: [:get, :post] 7 | match '/' => 'examples#index', via: [:get, :post] 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gemfile = File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | if File.exist?(gemfile) 5 | ENV['BUNDLE_GEMFILE'] = gemfile 6 | require 'bundler' 7 | Bundler.setup 8 | end 9 | 10 | $:.unshift File.expand_path('../../../../lib', __FILE__) -------------------------------------------------------------------------------- /test/functional/examples_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../test_helper' 2 | 3 | class ExamplesControllerTest < ActionController::TestCase 4 | 5 | test "render video once per session" do 6 | get :index 7 | assert_response :success 8 | 9 | #binding.pry 10 | 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.1 (2018-03-20) 2 | 3 | - Rails 5 support, dropped Rails 4 support 4 | 5 | ## 2.0.1 (2015-05-15) 6 | 7 | - Gemspec updates, see https://github.com/holli/advert_selector/issues/8 and https://github.com/holli/advert_selector/pull/10 8 | 9 | ## 2.0.0 (2014-07-29) 10 | 11 | - Rails 4 support (dropped Rails 3 officially) 12 | 13 | 14 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | ENGINE_ROOT = File.expand_path('../..', __FILE__) 5 | ENGINE_PATH = File.expand_path('../../lib/advert_selector/engine', __FILE__) 6 | 7 | require 'rails/all' 8 | require 'rails/engine/commands' 9 | -------------------------------------------------------------------------------- /app/controllers/advert_selector/main_controller.rb: -------------------------------------------------------------------------------- 1 | require_dependency "advert_selector/application_controller" 2 | 3 | module AdvertSelector 4 | class MainController < ApplicationController 5 | def index 6 | end 7 | 8 | def clear_errors_log 9 | AdvertSelector::ErrorsCache.clear 10 | redirect_to :action => :index 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20120924165938_create_advert_selector_placements.rb: -------------------------------------------------------------------------------- 1 | class CreateAdvertSelectorPlacements < ActiveRecord::Migration 2 | def change 3 | create_table :advert_selector_placements do |t| 4 | t.string :name, :null => false 5 | t.boolean :only_once_per_session 6 | t.text :conflicting_placements_array 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/fixtures/advert_selector/placements.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html 2 | 3 | parade: 4 | name: Parade 5 | conflicting_placements_array: leaderboard,video 6 | 7 | leaderboard: 8 | name: Leaderboard 9 | conflicting_placements_array: parade 10 | 11 | video: 12 | name: Video 13 | only_once_per_session: true 14 | conflicting_placements_array: parade 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/dummy/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 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | AdvertSelector::Engine.routes.draw do 2 | 3 | 4 | get "/" => "main#index", :as => :main 5 | get "/clear_errors_log" => "main#clear_errors_log", :as => :clear_errors_log 6 | 7 | 8 | resources :banners do 9 | #resources :helper_items 10 | put 'update_running_view_count', :on => :member 11 | end 12 | 13 | resources :placements 14 | 15 | 16 | #match '/' => redirect {|params, request| '/advert_selector/placements' } 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy/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 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, :key => '_dummy_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Dummy::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /app/views/advert_selector/placements/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

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

7 | 8 |

9 | Development code: 10 | <%= @placement.development_code %> 11 |

12 | 13 |

14 | Conflicting placements array: 15 | <%= @placement.conflicting_placements_array %> 16 |

17 | 18 | 19 | <%= link_to 'Edit', edit_placement_path(@placement) %> | 20 | <%= link_to 'Back', placements_path %> 21 | -------------------------------------------------------------------------------- /lib/advert_selector/errors_cache.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelector 2 | class ErrorsCache 3 | def self.cache_key 4 | 'advert_selector_errors' 5 | end 6 | 7 | def self.errors 8 | arr = Rails.cache.read(cache_key) 9 | arr.blank? ? [] : arr.first(10) 10 | end 11 | 12 | def self.add(str) 13 | Rails.cache.write(cache_key, errors.push(str)) 14 | end 15 | 16 | def self.clear() 17 | Rails.cache.write(cache_key, []) 18 | end 19 | 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/advert_selector/engine.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelector 2 | class Engine < ::Rails::Engine 3 | isolate_namespace AdvertSelector 4 | 5 | config.to_prepare do 6 | ::ApplicationController.helper(AdvertSelector::ApplicationHelper) 7 | end 8 | 9 | #initializer 'advert_selector.action_controller' do |app| 10 | # ActiveSupport.on_load :action_controller do 11 | # helper AdvertSelector::PlacementsHelper 12 | # end 13 | #end 14 | end 15 | end 16 | 17 | #ActionView::Base.send :include, AdvertSelector::PlacementsHelper 18 | 19 | -------------------------------------------------------------------------------- /test/dummy/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] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | 6 | 7 | 8 | 9 | <%= stylesheet_link_tag "application", :media => "all" %> 10 | <%= javascript_include_tag "application" %> 11 | <%= csrf_meta_tags %> 12 | 13 | 14 | 15 | 16 | 17 | <%= yield %> 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /db/migrate/20120926132649_create_advert_selector_helper_items.rb: -------------------------------------------------------------------------------- 1 | class CreateAdvertSelectorHelperItems < ActiveRecord::Migration 2 | def change 3 | create_table :advert_selector_helper_items do |t| 4 | t.integer :banner_id 5 | 6 | #t.references :master, :polymorphic => true 7 | 8 | t.integer :position 9 | t.string :name 10 | t.boolean :content_for 11 | t.text :content 12 | 13 | t.timestamps 14 | end 15 | 16 | add_index(:advert_selector_helper_items, [:banner_id, :position], :name => 'index_banner_position') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/advert_selector_helper.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelectorHelper 2 | 3 | def advert_selector_targeting_age?(age_string) 4 | age_string == 'adult' 5 | #age_string == 'senior' 6 | end 7 | 8 | def advert_selector_raise_error(placement) 9 | 1/0 10 | end 11 | 12 | def advert_selector_leaderboard_testing(placement) 13 | content_for(:content_test) do 14 | "advert_selector_leaderboard_testing_content" 15 | end 16 | return true 17 | end 18 | 19 | def advert_selector_always_false(placement) 20 | return false 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /app/views/advert_selector/helper_items/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | Master: 5 | <%= @helper_item.master_id %> 6 |

7 | 8 |

9 | Master type: 10 | <%= @helper_item.master_type %> 11 |

12 | 13 |

14 | Name: 15 | <%= @helper_item.name %> 16 |

17 | 18 |

19 | Content for: 20 | <%= @helper_item.content_for %> 21 |

22 | 23 |

24 | Content: 25 | <%= @helper_item.content %> 26 |

27 | 28 | 29 | <%= link_to 'Edit', edit_helper_item_path(@helper_item) %> | 30 | <%= link_to 'Back', helper_items_path %> 31 | -------------------------------------------------------------------------------- /app/models/advert_selector/helper_item.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelector 2 | class HelperItem < ActiveRecord::Base 3 | # attr_accessible :master_id, :master_type, :name, :position, :content_for, :content 4 | 5 | #belongs_to :master, :polymorphic => true 6 | #acts_as_list :scope => [:master_id, :master_type] 7 | 8 | belongs_to :banner, :class_name => 'AdvertSelector::Banner', inverse_of: :helper_items 9 | acts_as_list :scope => :banner_id 10 | 11 | 12 | def name_sym 13 | @name_sym ||= name.downcase.to_sym 14 | end 15 | 16 | def blank? 17 | name.blank? 18 | end 19 | 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/dummy/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 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | # 12 | # These inflection rules are supported but not enabled by default: 13 | # ActiveSupport::Inflector.inflections do |inflect| 14 | # inflect.acronym 'RESTful' 15 | # end 16 | -------------------------------------------------------------------------------- /script/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: script/release 3 | # Build the package, tag a commit, push it to origin, and then release the 4 | # package publicly. 5 | 6 | set -e 7 | 8 | version="$(script/package | grep Version: | awk '{print $2}')" 9 | [ -n "$version" ] || exit 1 10 | 11 | echo $version 12 | git commit --allow-empty -a -m "Release $version" 13 | git tag "v$version" 14 | git push origin 15 | git push origin "v$version" 16 | echo "NOT YET PUSHING GEM AUTOMATICALLY, RUN: gem push pkg/*-${version}.gem" 17 | echo "NOT YET PUSHING GEM AUTOMATICALLY, RUN: gem push pkg/*-${version}.gem" 18 | # gem push pkg/*-${version}.gem 19 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | -------------------------------------------------------------------------------- /app/views/advert_selector/main/index.html.erb: -------------------------------------------------------------------------------- 1 |

Advert Selector Main

2 | 3 | <% unless AdvertSelector::ErrorsCache.errors.blank? %> 4 |
5 |

Latest errors - <%= link_to 'Clear errors log.', clear_errors_log_url %>

6 | 11 |
12 | <% else %> 13 |
14 | No errors currently. 15 |
16 | 17 | <% end %> 18 | 19 | 20 |

Find me in app/views/advert_selector/main/index.html.erb

21 | -------------------------------------------------------------------------------- /test/fixtures/advert_selector/helper_items.yml: -------------------------------------------------------------------------------- 1 | coke_content: 2 | banner: coke 3 | name: banner_leaderboard 4 | content_for: true 5 | content: "" 6 | position: 1 7 | 8 | pepsi_only_param_true: 9 | banner: pepsi 10 | name: request_params_include? 11 | content_for: false 12 | content: pepsi=true 13 | 14 | pepsi_content: 15 | banner: pepsi 16 | position: 2 17 | name: banner_leaderboard 18 | content_for: true 19 | content: "" 20 | 21 | parade_content: 22 | banner: parade_banner 23 | position: 1 24 | name: banner_parade 25 | content_for: true 26 | content: "" 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/dummy/app/views/examples/index.html.erb: -------------------------------------------------------------------------------- 1 |

Examples#index

2 | 3 |

Find me in app/views/examples/index.html.erb

4 | 5 | <%= link_to 'nopriority', '/' %> - <%= link_to 'priority=true', '/?priority=true' %> 6 | 7 | 8 |
9 | 10 | <%= advert_selector_initialize %> 11 | 12 |
13 | 14 |

content_for :banner_leaderboard

15 | 16 | <%= content_for(:banner_leaderboard) %> 17 | 18 | 19 |

content_for :banner_parade

20 | 21 | <%= content_for(:banner_parade) %> 22 | 23 |
24 | 25 |

content_for :content_test

26 | 27 | <%= content_for(:content_test) %> 28 | 29 |
30 | 31 | 32 | 33 | 34 | <%= advert_selector_force_test_infos %> -------------------------------------------------------------------------------- /app/assets/javascripts/advert_selector/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 vendor/assets/javascripts of plugins, if any, 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 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | // 14 | //= require_tree . 15 | -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /app/controllers/advert_selector/application_controller.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelector 2 | class ApplicationController < ActionController::Base 3 | 4 | before_action :admin_access_only 5 | before_action :set_time_zone 6 | before_action :set_locale 7 | 8 | def admin_access_only 9 | if AdvertSelector.admin_access_class.send(:admin_access, self) 10 | return true 11 | else 12 | render :plain => "Forbidden, only for admins", :status => 403 13 | return false 14 | end 15 | end 16 | 17 | def set_time_zone 18 | Time.zone = AdvertSelector.default_time_zone 19 | end 20 | 21 | def set_locale 22 | I18n.locale = :en 23 | end 24 | 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Dummy::Application.config.secret_token = '42fb79fe9baf04a90a6caf05f98d4950b157d77f5ca4079104f4c5378607c705f60ac5421670f2384948a0860a6a5d7c41ce5e8015f7830c0d3290726d716808' 8 | Dummy::Application.config.secret_key_base = '7d5bb216d35a2613ff87254b811a64ccb8debcf172677f8111873da5b4b02b8169aba8fedf900cf21bf8d988914cf114d5848fbcf132484aef59f9a910d3d48c' 9 | -------------------------------------------------------------------------------- /test/dummy/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 vendor/assets/javascripts of plugins, if any, 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 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /app/views/advert_selector/placements/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing placements

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <% @placements.each do |placement| %> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% end %> 21 |
NameConflicting placements array
<%= placement.name %><%= placement.conflicting_placements_array %><%= link_to 'Show', placement %><%= link_to 'Edit', edit_placement_path(placement) %><%= link_to 'Destroy', placement, :method => :delete, :data => { :confirm => 'Are you sure?' } %>
22 | 23 |
24 | 25 | <%= link_to 'New Placement', new_placement_path %> 26 | -------------------------------------------------------------------------------- /lib/advert_selector.rb: -------------------------------------------------------------------------------- 1 | require 'acts_as_list' 2 | require 'simple_form' 3 | 4 | require "advert_selector/engine" 5 | require "advert_selector/errors_cache" 6 | 7 | module AdvertSelector 8 | 9 | mattr_accessor :default_banner_test_url 10 | mattr_accessor :default_time_zone 11 | mattr_accessor :admin_access_class 12 | 13 | self.default_banner_test_url = "http://localhost:3000/?" 14 | self.default_time_zone = 'UTC' 15 | 16 | class AdminAccessClassDefault 17 | def self.admin_access(controller) 18 | Rails.env.development? 19 | end 20 | end 21 | class AdminAccessClassAlwaysTrue 22 | def self.admin_access(controller) 23 | true 24 | end 25 | end 26 | 27 | self.admin_access_class = AdminAccessClassDefault 28 | 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

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

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError 4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 5 | end 6 | 7 | require 'rdoc/task' 8 | 9 | RDoc::Task.new(:rdoc) do |rdoc| 10 | rdoc.rdoc_dir = 'rdoc' 11 | rdoc.title = 'AdvertSelector' 12 | rdoc.options << '--line-numbers' 13 | rdoc.rdoc_files.include('README.rdoc') 14 | rdoc.rdoc_files.include('lib/**/*.rb') 15 | end 16 | 17 | APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) 18 | load 'rails/tasks/engine.rake' 19 | 20 | 21 | load 'rails/tasks/statistics.rake' 22 | 23 | 24 | 25 | require 'bundler/gem_tasks' 26 | 27 | require 'rake/testtask' 28 | 29 | Rake::TestTask.new(:test) do |t| 30 | t.libs << 'test' 31 | t.pattern = 'test/**/*_test.rb' 32 | t.verbose = false 33 | end 34 | 35 | 36 | task default: :test 37 | -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

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

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/dummy/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 | # Labels and hints 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 | 27 | -------------------------------------------------------------------------------- /app/views/advert_selector/banners/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

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

7 | 8 |

9 | Start time: 10 | <%= @banner.start_time %> 11 |

12 | 13 |

14 | End time: 15 | <%= @banner.end_time %> 16 |

17 | 18 |

19 | Target view count: 20 | <%= @banner.target_view_count %> 21 |

22 | 23 |

24 | Frequency: 25 | <%= @banner.frequency %> 26 |

27 | 28 |

29 | Delay requests: 30 | <%= @banner.delay_requests %> 31 |

32 | 33 |

34 | Comment: 35 | <%= @banner.comment %> 36 |

37 | 38 |

39 | Confirmed: 40 | <%= @banner.confirmed %> 41 |

42 | 43 |

44 | Placement: 45 | <%= @banner.placement_id %> 46 |

47 | 48 | 49 | <%= link_to 'Edit', edit_banner_path(@banner) %> | 50 | <%= link_to 'Back', banners_path %> 51 | -------------------------------------------------------------------------------- /app/assets/stylesheets/advert_selector/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | 15 | 16 | 17 | body { 18 | padding-top: 60px; 19 | padding-bottom: 40px; 20 | } 21 | .sidebar-nav { 22 | padding: 9px 0; 23 | } 24 | 25 | .form-horizontal .control-group { margin-bottom: 10px; } 26 | 27 | label, input, button, select, textarea { font-size: 12px !important; } 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/views/advert_selector/helper_items/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing helper_items

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% @helper_items.each do |helper_item| %> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <% end %> 27 |
MasterMaster typeNameContent forContent
<%= helper_item.master_id %><%= helper_item.master_type %><%= helper_item.name %><%= helper_item.content_for %><%= helper_item.content %><%= link_to 'Show', helper_item %><%= link_to 'Edit', edit_helper_item_path(helper_item) %><%= link_to 'Destroy', helper_item, :method => :delete, :data => { :confirm => 'Are you sure?' } %>
28 | 29 |
30 | 31 | <%= link_to 'New Helper item', new_helper_item_path %> 32 | -------------------------------------------------------------------------------- /db/migrate/20120925122858_create_advert_selector_banners.rb: -------------------------------------------------------------------------------- 1 | class CreateAdvertSelectorBanners < ActiveRecord::Migration 2 | def change 3 | create_table :advert_selector_banners do |t| 4 | t.string :name, :null => false 5 | t.datetime :start_time 6 | t.datetime :end_time 7 | t.integer :priority, :null => false, :default => 0 8 | t.integer :target_view_count 9 | t.integer :running_view_count, :null => false, :default => 0 10 | t.integer :frequency 11 | t.boolean :fast_mode, :null => false, :default => false 12 | t.text :comment 13 | t.boolean :confirmed, :null => false, :default => false 14 | t.integer :placement_id, :null => false 15 | #t.boolean :in_serve_cache, :null => false, :default => false 16 | 17 | t.timestamps 18 | end 19 | 20 | #add_index(:advert_selector_banners, [:in_serve_cache, :priority]) 21 | add_index(:advert_selector_banners, [:end_time]) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/functional/advert_selector/main_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module AdvertSelector 4 | class MainControllerTest < ActionController::TestCase 5 | 6 | setup do 7 | AdvertSelector.admin_access_class = AdvertSelector::AdminAccessClassAlwaysTrue 8 | @routes = AdvertSelector::Engine.routes # This would be same as calling get :index, :use_route => :advert_selector 9 | end 10 | 11 | test "should get index" do 12 | get :index 13 | assert_response :success 14 | end 15 | 16 | test "should clear errors log" do 17 | AdvertSelector::ErrorsCache.add('str') 18 | get :clear_errors_log 19 | assert_response :redirect 20 | assert AdvertSelector::ErrorsCache.errors.blank? 21 | end 22 | 23 | test "forbidden with default access" do 24 | AdvertSelector.admin_access_class = AdvertSelector::AdminAccessClassDefault 25 | get :index 26 | assert_response 403 27 | end 28 | 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Declare your gem's dependencies in advert_selector.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # jquery-rails is used by the dummy application 9 | # gem "jquery-rails" 10 | 11 | # Declare any dependencies that are still in development here instead of in 12 | # your gemspec. These might include edge Rails or gems from your path or 13 | # Git. Remember to move these dependencies to your gemspec before releasing 14 | # your gem to rubygems.org. 15 | 16 | #gem 'pry' 17 | gem 'timecop' 18 | gem 'mocha', :require => false 19 | gem 'rails-controller-testing' 20 | 21 | 22 | # For travis testing 23 | # http://schneems.com/post/50991826838/testing-against-multiple-rails-versions 24 | rails_version = ENV["RAILS_VERSION"] || "default" 25 | 26 | case rails_version 27 | when "default" 28 | gem "rails" 29 | else 30 | gem "rails", "~> #{rails_version}" 31 | end 32 | 33 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 YOURNAME 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /app/views/advert_selector/banners/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing banners

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | <% @banners.each do |banner| %> 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | <% end %> 34 |
NameStart timeEnd timeTarget view countFrequencyDelay requestsCommentConfirmedPlacement
<%= banner.name %><%= banner.start_time %><%= banner.end_time %><%= banner.target_view_count %><%= banner.frequency %><%= banner.comment %><%= banner.confirmed %><%= banner.placement_id %><%= link_to 'Show', banner %><%= link_to 'Edit', edit_banner_path(banner) %><%= link_to 'Destroy', banner, :method => :delete, :data => { :confirm => 'Are you sure?' } %>
35 | 36 |
37 | 38 | <%= link_to 'New Banner', new_banner_path %> 39 | -------------------------------------------------------------------------------- /app/views/advert_selector/helper_items/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@helper_item) do |f| %> 2 | <% if @helper_item.errors.any? %> 3 |
4 |

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

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :master_id %>
16 | <%= f.number_field :master_id %> 17 |
18 |
19 | <%= f.label :master_type %>
20 | <%= f.text_field :master_type %> 21 |
22 |
23 | <%= f.label :name %>
24 | <%= f.text_field :name %> 25 |
26 |
27 | <%= f.label :content_for %>
28 | <%= f.check_box :content_for %> 29 |
30 |
31 | <%= f.label :content %>
32 | <%= f.text_area :content %> 33 |
34 |
35 | <%= f.submit %> 36 |
37 | <% end %> 38 | -------------------------------------------------------------------------------- /app/views/advert_selector/placements/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for(@placement, :html => {:class => 'form-horizontal'}) do |f| %> 2 | 3 | <%= f.error_notification %> 4 | 5 |
6 |
7 | <%= f.input :name %> 8 | 9 | <%= f.input :only_once_per_session %> 10 | 11 | <%= f.input :conflicting_placements_array, :input_html => {:rows => 5} %> 12 | 13 |
14 | Conflicting links: 15 |
    16 | <% @placement.conflicting_placements.each do |name| %> 17 |
  • 18 | <% if place = AdvertSelector::Placement.by_name(name).first %> 19 | <%= link_to place.name, place %> 20 | <% else %> 21 | <%= name %> - NOT FOUND 22 | <% end %> 23 |
  • 24 | <% end %> 25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 | 33 |
34 | 35 |
36 | <%= f.button :submit %> 37 |
38 | 39 | <% end %> 40 | 41 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Show full error reports and disable caching 10 | config.consider_all_requests_local = true 11 | config.action_controller.perform_caching = false 12 | 13 | # Don't care if the mailer can't send 14 | config.action_mailer.raise_delivery_errors = false 15 | 16 | # Print deprecation notices to the Rails logger 17 | config.active_support.deprecation = :stderr 18 | 19 | # Only use best-standards-support built into browsers 20 | config.action_dispatch.best_standards_support = :builtin 21 | 22 | # Do not compress assets 23 | config.assets.compress = false 24 | 25 | # Expands the lines which load the assets 26 | config.assets.debug = true 27 | 28 | config.eager_load = false 29 | end 30 | -------------------------------------------------------------------------------- /test/fixtures/advert_selector/banners.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html 2 | 3 | coke: 4 | placement: leaderboard 5 | name: Coke 6 | start_time: <%= 5.days.ago.at_beginning_of_day.to_s(:db) %> 7 | end_time: <%= 5.days.from_now.at_beginning_of_day.to_s(:db) %> 8 | target_view_count: 1000 9 | running_view_count: 100 10 | frequency: nil 11 | fast_mode: false 12 | comment: "Coke banner" 13 | confirmed: true 14 | priority: 50 15 | 16 | pepsi: 17 | placement: leaderboard 18 | name: Pepsi 19 | start_time: <%= Time.now.at_beginning_of_day.to_s(:db) %> 20 | end_time: <%= 10.days.from_now.at_beginning_of_day.to_s(:db) %> 21 | target_view_count: 1000 22 | frequency: nil 23 | comment: "Pepsi banner" 24 | fast_mode: true 25 | confirmed: true 26 | priority: 100 27 | 28 | parade_banner: 29 | placement: parade 30 | name: parade_banner 31 | start_time: <%= Time.now.at_beginning_of_day.to_s(:db) %> 32 | end_time: <%= 10.days.from_now.at_beginning_of_day.to_s(:db) %> 33 | target_view_count: 1000 34 | frequency: 1 35 | fast_mode: true 36 | confirmed: false 37 | priority: 1000 38 | 39 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/scaffold.css: -------------------------------------------------------------------------------- 1 | body { background-color: #fff; color: #333; } 2 | 3 | body, p, ol, ul, td { 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | pre { 10 | background-color: #eee; 11 | padding: 10px; 12 | font-size: 11px; 13 | } 14 | 15 | a { color: #000; } 16 | a:visited { color: #666; } 17 | a:hover { color: #fff; background-color:#000; } 18 | 19 | div.field, div.actions { 20 | margin-bottom: 10px; 21 | } 22 | 23 | #notice { 24 | color: green; 25 | } 26 | 27 | .field_with_errors { 28 | padding: 2px; 29 | background-color: red; 30 | display: table; 31 | } 32 | 33 | #error_explanation { 34 | width: 450px; 35 | border: 2px solid red; 36 | padding: 7px; 37 | padding-bottom: 0; 38 | margin-bottom: 20px; 39 | background-color: #f0f0f0; 40 | } 41 | 42 | #error_explanation h2 { 43 | text-align: left; 44 | font-weight: bold; 45 | padding: 5px 5px 5px 15px; 46 | font-size: 12px; 47 | margin: -7px; 48 | margin-bottom: 0px; 49 | background-color: #c00; 50 | color: #fff; 51 | } 52 | 53 | #error_explanation ul li { 54 | font-size: 12px; 55 | list-style: square; 56 | } 57 | -------------------------------------------------------------------------------- /app/views/advert_selector/banners/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Banner <%= @banner.name %> - <%= link_to('Duplicate banner', new_banner_url(:duplicate_id => @banner.id)) %>

2 | 3 | <% unless @banner.target_view_count.nil? %> 4 | Current view count: <%= @banner.running_view_count %> / <%= @banner.target_view_count %> 5 |   |   6 | <% end %> 7 | 8 |
9 | 10 | Test banner in url : 11 | 12 | "> 13 | 14 | 15 |
16 | 17 | <%= render 'form' %> 18 | 19 | 20 |
21 | 22 | <%= simple_form_for(@banner, :url => update_running_view_count_banner_url(@banner), :html => {:class => 'form-inline'}) do |f| %> 23 | 24 | Running view count: 25 | 26 | 27 | <%= f.button :submit, 'Change running view count' %> 28 | 29 | <% end %> 30 | -------------------------------------------------------------------------------- /advert_selector.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "advert_selector/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "advert_selector" 9 | s.version = AdvertSelector::VERSION 10 | s.authors = ["Olli Huotari"] 11 | s.email = ["olli.huotari@iki.fi"] 12 | s.homepage = "https://github.com/holli/advert_selector/" 13 | s.licenses = ['MIT'] 14 | s.summary = "Rails adserver tool for selecting a smaller subset of banners from all possible banners with differing banner placement combinations." 15 | s.description = "Rails adserver tool for selecting a smaller subset of banners from all possible banners with differing banner placement combinations. Gem includes admin tools for handling banners in live environment. Includes basic targeting, viewcount, frequency etc setups." 16 | 17 | s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"] 18 | s.test_files = Dir["test/**/*"] 19 | 20 | s.add_dependency "rails", ">= 5" 21 | s.add_dependency "simple_form", ">= 3" 22 | s.add_dependency "acts_as_list", ">= 0.6" 23 | 24 | s.add_development_dependency "sqlite3", ">= 1.3" 25 | #s.add_development_dependency 'rails-controller-testing' 26 | #s.add_development_dependency "mocha" 27 | #s.add_development_dependency "timecop" 28 | 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.public_file_server.enabled = false 12 | 13 | config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' } 14 | 15 | # Show full error reports and disable caching 16 | config.consider_all_requests_local = true 17 | config.action_controller.perform_caching = false 18 | 19 | # Raise exceptions instead of rendering exception templates 20 | config.action_dispatch.show_exceptions = false 21 | 22 | # Disable request forgery protection in test environment 23 | config.action_controller.allow_forgery_protection = false 24 | 25 | # Tell Action Mailer not to deliver emails to the real world. 26 | # The :test delivery method accumulates sent emails in the 27 | # ActionMailer::Base.deliveries array. 28 | config.action_mailer.delivery_method = :test 29 | 30 | # Print deprecation notices to the stderr 31 | # config.active_support.deprecation = :stderr 32 | # config.active_support.deprecation = :debug 33 | config.active_support.deprecation = :log 34 | 35 | config.eager_load = false 36 | config.active_support.test_order = :random 37 | 38 | end 39 | -------------------------------------------------------------------------------- /app/views/advert_selector/banners/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for(@banner, :html => {:class => 'form-horizontal'}) do |f| %> 2 | <%= f.error_notification %> 3 | 4 |
5 |
6 | 7 | <%= f.input :name %> 8 | 9 | <%= f.input :start_time, :as => 'string', :input_html => {:value => (@banner.start_time ? @banner.start_time.iso8601 : '')} %> 10 | <%= f.input :end_time, :as => 'string', :input_html => {:value => (@banner.end_time ? @banner.end_time.iso8601 : '')} %> 11 | <%= f.input :priority %> 12 | <%= f.input :target_view_count %> 13 | <%= f.input :fast_mode %> 14 | <%= f.input :frequency %> 15 | <%= f.input :comment, :input_html => {:rows => 5} %> 16 | <%= f.input :confirmed, :label_html => {:style => ((@banner.new_record? || @banner.confirmed?) ? '' : 'color: red; font-weight: bold')} %> 17 | <%= f.association :placement %> 18 | 19 |
20 | 21 |
22 | <% @banner.helper_items.build 23 | @banner.helper_items.build if @banner.helper_items.size < 2 24 | %> 25 | <%= f.fields_for :helper_items do |f_hi| %> 26 |
27 | <%= f_hi.input :name, :label => 'HelperItem Name' %> 28 | <%= f_hi.input :content_for %> 29 | <%= f_hi.input :position %> 30 | <%= f_hi.input :content, :input_html => {:rows => (f_hi.object.content_for? ? 5 : 1) } %> 31 |
32 | <% end %> 33 |
34 | 35 |
36 | 37 |
38 | <%= f.button :submit %> 39 |
40 | 41 | 42 | <% end %> 43 | -------------------------------------------------------------------------------- /test/unit/advert_selector/placement_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../test_helper' 2 | #require 'test_helper' 3 | 4 | module AdvertSelector 5 | class PlacementTest < ActiveSupport::TestCase 6 | 7 | setup do 8 | @parade = advert_selector_placements(:parade) 9 | @leaderboard = advert_selector_placements(:leaderboard) 10 | end 11 | 12 | test "name_sym" do 13 | assert_equal :parade, @parade.name_sym 14 | end 15 | 16 | test "conflicting_placements" do 17 | assert_equal 'leaderboard,video', @parade.conflicting_placements_array 18 | assert_equal [:parade, :leaderboard, :video], @parade.conflicting_placements 19 | end 20 | 21 | test "conflicting_placements string handling" do 22 | arr = Placement.conflicting_placements("a, b c,a,d") 23 | assert_equal [:a,:b,:c,:d], arr 24 | end 25 | 26 | test "conflicting_with" do 27 | assert @parade.conflicting_with?(@parade), "should conflict with itself" 28 | 29 | assert @parade.conflicting_placements_array.include?("leaderboard"), "wrong setup" 30 | assert @parade.conflicting_with?(@leaderboard) 31 | 32 | dummy = Placement.new(:name => 'test1') 33 | assert !@parade.conflicting_with?([dummy]) 34 | assert @parade.conflicting_with?([dummy, @leaderboard]) 35 | end 36 | 37 | test "conflicting_placements_array=" do 38 | assert_equal 'leaderboard,video', @parade.conflicting_placements_array 39 | @parade.conflicting_placements_array = 'video,not_found' 40 | assert @parade.save 41 | 42 | new_parade = Placement.find(@parade.id) 43 | assert_equal [:parade, :not_found, :video], new_parade.conflicting_placements 44 | 45 | new_leaderboard = Placement.find(@leaderboard.id) 46 | assert new_leaderboard.conflicting_placements_array.blank? 47 | end 48 | 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | 2 | # Configure Rails Environment 3 | ENV["RAILS_ENV"] = "test" 4 | require File.expand_path("../../test/dummy/config/environment.rb", __FILE__) 5 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../test/dummy/db/migrate", __FILE__)] 6 | ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) 7 | require "rails/test_help" 8 | require "mocha/setup" 9 | 10 | 11 | # Filter out Minitest backtrace while allowing backtrace from other libraries 12 | # to be shown. 13 | Minitest.backtrace_filter = Minitest::BacktraceFilter.new 14 | 15 | 16 | 17 | 18 | # Load fixtures from the engine 19 | if ActiveSupport::TestCase.respond_to?(:fixture_path=) 20 | ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) 21 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path 22 | ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" 23 | ActiveSupport::TestCase.fixtures :all 24 | end 25 | 26 | 27 | 28 | class ActiveSupport::TestCase 29 | 30 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 31 | # -- they do not yet inherit this setting 32 | fixtures :all 33 | 34 | setup do 35 | 36 | $advert_selector_banners_load_time = nil # force reload of banners in every tests 37 | @coke = advert_selector_banners(:coke) 38 | @pepsi = advert_selector_banners(:pepsi) 39 | @parade_banner = advert_selector_banners(:parade_banner) 40 | 41 | Timecop.return 42 | Timecop.travel( Time.now.at_midnight + 12.hours ) unless [6..20].include?(Time.now.hour) 43 | end 44 | 45 | teardown do 46 | Timecop.return 47 | $advert_selector_avoid_cache = false 48 | @coke.reset_cache 49 | @pepsi.reset_cache 50 | @parade_banner.reset_cache 51 | AdvertSelector::ErrorsCache.clear 52 | end 53 | 54 | end -------------------------------------------------------------------------------- /test/functional/advert_selector/placements_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../test_helper' 2 | 3 | module AdvertSelector 4 | class PlacementsControllerTest < ActionController::TestCase 5 | # fixtures :all 6 | 7 | setup do 8 | AdvertSelector.admin_access_class = AdvertSelector::AdminAccessClassAlwaysTrue 9 | @routes = AdvertSelector::Engine.routes # This would be same as calling get :index, :use_route => :advert_selector 10 | 11 | @placement = advert_selector_placements(:leaderboard) 12 | end 13 | 14 | test "should get index" do 15 | get :index 16 | assert_response :success 17 | assert_not_nil assigns(:placements) 18 | end 19 | 20 | test "should get new" do 21 | get :new 22 | assert_response :success 23 | end 24 | 25 | test "should create placement" do 26 | assert_difference('Placement.count') do 27 | #post :create, :placement => { :conflicting_placements_array => @placement.conflicting_placements_array, :name => @placement.name } 28 | post :create, params: {:placement => { :name => "new placement" }} 29 | end 30 | 31 | assert_redirected_to placement_path(assigns(:placement)) 32 | end 33 | 34 | test "should show placement" do 35 | get :show, params: {:id => @placement} 36 | assert_response :redirect 37 | end 38 | 39 | test "should get edit" do 40 | get :edit, params: {:id => @placement} 41 | assert_response :success 42 | end 43 | 44 | test "should update placement" do 45 | put :update, params: {:id => @placement, :placement => { :name => @placement.name }} 46 | #assert_redirected_to placement_path(assigns(:placement)) 47 | assert_redirected_to placement_path(assigns(:placement)) 48 | end 49 | 50 | test "should destroy placement" do 51 | assert_difference('Placement.count', -1) do 52 | delete :destroy, params: {:id => @placement} 53 | end 54 | 55 | assert_redirected_to placements_path 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/functional/advert_selector/banners_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../test_helper' 2 | 3 | module AdvertSelector 4 | class BannersControllerTest < ActionController::TestCase 5 | setup do 6 | AdvertSelector.admin_access_class = AdvertSelector::AdminAccessClassAlwaysTrue 7 | @routes = AdvertSelector::Engine.routes # This would be same as calling get :index, :use_route => :advert_selector 8 | 9 | @banner = advert_selector_banners(:pepsi) 10 | end 11 | 12 | test "should get index" do 13 | get :index 14 | assert_response :success 15 | assert_not_nil assigns(:banners) 16 | end 17 | 18 | test "should get new" do 19 | get :new 20 | assert_response :success 21 | end 22 | 23 | test "should create banner" do 24 | assert_difference('Banner.count') do 25 | post :create, params: {:banner => { :comment => @banner.comment, :end_time => @banner.end_time, :frequency => @banner.frequency, :name => @banner.name, :placement_id => @banner.placement_id, :start_time => @banner.start_time, :target_view_count => @banner.target_view_count }} 26 | end 27 | 28 | assert_redirected_to banner_path(assigns(:banner)) 29 | end 30 | 31 | test "should show banner" do 32 | get :show, params: {:id => @banner} 33 | assert_response :redirect 34 | end 35 | 36 | test "should get edit" do 37 | get :edit, params: {:id => @banner} 38 | assert_response :success 39 | end 40 | 41 | test "should update banner" do 42 | put :update, params: {:id => @banner, :banner => { :comment => @banner.comment }} 43 | assert_redirected_to banner_path(assigns(:banner)) 44 | end 45 | 46 | test "should update banner running view count" do 47 | @banner.add_one_viewcount 48 | @banner.save 49 | put :update_running_view_count, params: {:id => @banner, :banner => { :running_view_count => 99 }} 50 | assert_response :redirect 51 | assert_equal 99, @banner.running_view_count 52 | assert_equal 99, AdvertSelector::Banner.find(@banner.id)[:running_view_count] 53 | 54 | end 55 | 56 | test "should destroy banner" do 57 | assert_difference('Banner.count', -1) do 58 | delete :destroy, params: {:id => @banner} 59 | end 60 | 61 | assert_redirected_to banners_path 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | Bundler.require 6 | require "advert_selector" 7 | 8 | module Dummy 9 | class Application < Rails::Application 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration should go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded. 13 | 14 | # Custom directories with classes and modules you want to be autoloadable. 15 | # config.autoload_paths += %W(#{config.root}/extras) 16 | 17 | # Only load the plugins named here, in the order given (default is alphabetical). 18 | # :all can be used as a placeholder for all plugins not explicitly named. 19 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 20 | 21 | # Activate observers that should always be running. 22 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 23 | 24 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 25 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 26 | # config.time_zone = 'Central Time (US & Canada)' 27 | 28 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 29 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 30 | # config.i18n.default_locale = :de 31 | 32 | # Configure the default encoding used in templates for Ruby 1.9. 33 | config.encoding = "utf-8" 34 | 35 | # Configure sensitive parameters which will be filtered from the log file. 36 | config.filter_parameters += [:password] 37 | 38 | # Enable escaping HTML in JSON. 39 | config.active_support.escape_html_entities_in_json = true 40 | 41 | # Use SQL instead of Active Record's schema dumper when creating the database. 42 | # This is necessary if your schema can't be completely dumped by the schema dumper, 43 | # like if you have constraints or database-specific column types 44 | # config.active_record.schema_format = :sql 45 | 46 | # Enable the asset pipeline 47 | config.assets.enabled = true 48 | 49 | # Version of your assets, change this if you want to expire all your assets 50 | config.assets.version = '1.0' 51 | end 52 | end 53 | 54 | -------------------------------------------------------------------------------- /app/models/advert_selector/placement.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelector 2 | class Placement < ActiveRecord::Base 3 | #attr_accessible :conflicting_placements_array, :name, :comment, :request_delay, :only_once_per_session 4 | 5 | has_many :banners, :inverse_of => :placement 6 | 7 | scope :by_name, lambda {|name| where("LOWER(name) = ?", name.to_s.downcase)} 8 | 9 | def name_sym 10 | @name_sym ||= name.to_s.downcase.to_sym unless name.blank? 11 | end 12 | 13 | 14 | def conflicting_with?(placement_syms) 15 | placement_syms = [placement_syms.name_sym] if placement_syms.is_a?(Placement) 16 | placement_syms = placement_syms.collect{|plac| plac.name_sym} if placement_syms.first.is_a?(Placement) 17 | 18 | !(placement_syms & conflicting_placements).empty? 19 | end 20 | 21 | def self.conflicting_placements(string) 22 | string.to_s.split(/[ ,]/).collect{|name| name.strip}.reject{|name| name.blank?}.collect{|name| name.to_sym}.sort_by{|sym| sym.to_s}.uniq 23 | end 24 | 25 | def conflicting_placements 26 | @conflicting_placements ||= ([self.name_sym] + Placement.conflicting_placements(conflicting_placements_array)) 27 | end 28 | 29 | def conflicting_placements_array=(string) 30 | string = string.join(",") if string.is_a?(Array) 31 | arr = Placement.conflicting_placements( string ) 32 | arr.delete(name_sym) 33 | self[:conflicting_placements_array] = arr.join(",") 34 | end 35 | 36 | after_save :after_save_update_conflicting_placements_info 37 | def after_save_update_conflicting_placements_info 38 | if conflicting_placements_array_changed? 39 | saved_org = Placement.conflicting_placements(conflicting_placements_array_change.first) 40 | saved_new = Placement.conflicting_placements(conflicting_placements_array_change.last) 41 | 42 | (saved_org-saved_new).each do |name| 43 | if placement = Placement.by_name(name).first 44 | placement.conflicting_placements_array = placement.conflicting_placements.reject{|var| var == name_sym} 45 | placement.save if placement.conflicting_placements_array_changed? 46 | end 47 | end 48 | saved_new.each do |name| 49 | if placement = Placement.where(:name => name).first 50 | placement.conflicting_placements_array = placement.conflicting_placements.push(name_sym) 51 | placement.save if placement.conflicting_placements_array_changed? 52 | end 53 | end 54 | end 55 | end 56 | 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/dummy/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 to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(:version => 20120926132649) do 15 | 16 | create_table "advert_selector_banners", :force => true do |t| 17 | t.string "name", :null => false 18 | t.datetime "start_time" 19 | t.datetime "end_time" 20 | t.integer "priority", :default => 0, :null => false 21 | t.integer "target_view_count" 22 | t.integer "running_view_count", :default => 0, :null => false 23 | t.integer "frequency" 24 | t.boolean "fast_mode", :default => false, :null => false 25 | t.text "comment" 26 | t.boolean "confirmed", :default => false, :null => false 27 | t.integer "placement_id", :null => false 28 | t.datetime "created_at", :null => false 29 | t.datetime "updated_at", :null => false 30 | end 31 | 32 | add_index "advert_selector_banners", ["end_time"], :name => "index_advert_selector_banners_on_end_time" 33 | 34 | create_table "advert_selector_helper_items", :force => true do |t| 35 | t.integer "banner_id" 36 | t.integer "position" 37 | t.string "name" 38 | t.boolean "content_for" 39 | t.text "content" 40 | t.datetime "created_at", :null => false 41 | t.datetime "updated_at", :null => false 42 | end 43 | 44 | add_index "advert_selector_helper_items", ["banner_id", "position"], :name => "index_banner_position" 45 | 46 | create_table "advert_selector_placements", :force => true do |t| 47 | t.string "name", :null => false 48 | t.boolean "only_once_per_session" 49 | t.text "conflicting_placements_array" 50 | t.datetime "created_at", :null => false 51 | t.datetime "updated_at", :null => false 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | 17 | # Don't fallback to assets pipeline if a precompiled asset is missed 18 | config.assets.compile = false 19 | 20 | # Generate digests for assets URLs 21 | config.assets.digest = true 22 | 23 | # Defaults to nil and saved in location specified by config.assets.prefix 24 | # config.assets.manifest = YOUR_PATH 25 | 26 | # Specifies the header that your server uses for sending files 27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | # config.force_ssl = true 32 | 33 | # See everything in the log (default is :info) 34 | # config.log_level = :debug 35 | 36 | # Prepend all log lines with the following tags 37 | # config.log_tags = [ :subdomain, :uuid ] 38 | 39 | # Use a different logger for distributed setups 40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 41 | 42 | # Use a different cache store in production 43 | # config.cache_store = :mem_cache_store 44 | 45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 46 | # config.action_controller.asset_host = "http://assets.example.com" 47 | 48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 49 | # config.assets.precompile += %w( search.js ) 50 | 51 | # Disable delivery errors, bad email addresses will be ignored 52 | # config.action_mailer.raise_delivery_errors = false 53 | 54 | # Enable threaded mode 55 | # config.threadsafe! 56 | 57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 58 | # the I18n.default_locale when a translation can not be found) 59 | config.i18n.fallbacks = true 60 | 61 | # Send deprecation notices to registered listeners 62 | config.active_support.deprecation = :notify 63 | 64 | # Log the query plan for queries taking more than this (works 65 | # with SQLite, MySQL, and PostgreSQL) 66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5 67 | 68 | config.eager_load = true 69 | end 70 | -------------------------------------------------------------------------------- /app/controllers/advert_selector/placements_controller.rb: -------------------------------------------------------------------------------- 1 | require_dependency "advert_selector/application_controller" 2 | 3 | module AdvertSelector 4 | class PlacementsController < ApplicationController 5 | # GET /placements 6 | # GET /placements.json 7 | def index 8 | @placements = Placement.all 9 | 10 | respond_to do |format| 11 | format.html # index.html.erb 12 | format.json { render :json => @placements } 13 | end 14 | end 15 | 16 | # GET /placements/1 17 | # GET /placements/1.json 18 | def show 19 | redirect_to edit_placement_url(params[:id]) 20 | 21 | #@placement = Placement.find(params[:id]) 22 | # 23 | #respond_to do |format| 24 | # format.html # show.html.erb 25 | # format.json { render :json => @placement } 26 | #end 27 | end 28 | 29 | # GET /placements/new 30 | # GET /placements/new.json 31 | def new 32 | @placement = Placement.new 33 | 34 | respond_to do |format| 35 | format.html # new.html.erb 36 | format.json { render :json => @placement } 37 | end 38 | end 39 | 40 | # GET /placements/1/edit 41 | def edit 42 | @placement = Placement.find(params[:id]) 43 | end 44 | 45 | # POST /placements 46 | # POST /placements.json 47 | def create 48 | @placement = Placement.new(placement_params) 49 | 50 | respond_to do |format| 51 | if @placement.save 52 | format.html { redirect_to @placement, :notice => 'Placement was successfully created.' } 53 | format.json { render :json => @placement, :status => :created, :location => @placement } 54 | else 55 | format.html { render :action => "new" } 56 | format.json { render :json => @placement.errors, :status => :unprocessable_entity } 57 | end 58 | end 59 | end 60 | 61 | # PUT /placements/1 62 | # PUT /placements/1.json 63 | def update 64 | @placement = Placement.find(params[:id]) 65 | 66 | respond_to do |format| 67 | if @placement.update(placement_params) 68 | format.html { redirect_to @placement, :notice => 'Placement was successfully updated.' } 69 | format.json { head :no_content } 70 | else 71 | format.html { render :action => "edit" } 72 | format.json { render :json => @placement.errors, :status => :unprocessable_entity } 73 | end 74 | end 75 | end 76 | 77 | # DELETE /placements/1 78 | # DELETE /placements/1.json 79 | def destroy 80 | @placement = Placement.find(params[:id]) 81 | @placement.destroy 82 | 83 | respond_to do |format| 84 | format.html { redirect_to placements_url } 85 | format.json { head :no_content } 86 | end 87 | end 88 | 89 | private 90 | def placement_params 91 | params.require(:placement).permit(:conflicting_placements_array, :name, :comment, :request_delay, :only_once_per_session) 92 | end 93 | 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /app/views/layouts/advert_selector/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AdvertSelector 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <%= stylesheet_link_tag "advert_selector/application", :media => "all" %> 15 | <%= javascript_include_tag "advert_selector/application" %> 16 | <%= csrf_meta_tags %> 17 | 18 | 19 | 20 | 21 | 46 | 47 | 48 |
49 |
50 |
51 | 70 |
71 | 72 | <% unless AdvertSelector::ErrorsCache.errors.blank? %> 73 |
74 |

Advertisement causes errors. <%= link_to 'See all', main_url %>

75 | <%= simple_format(AdvertSelector::ErrorsCache.errors.last) %> 76 |
77 | <% end %> 78 | 79 | 80 |
81 | <%= yield %> 82 |
83 | 84 | 85 |
86 |
87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/models/advert_selector/banner.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelector 2 | class Banner < ActiveRecord::Base 3 | #attr_accessible :comment, :confirmed, :start_time, :end_time, 4 | # :frequency, :name, :placement_id, :target_view_count, :priority, 5 | # :fast_mode, :helper_items_attributes 6 | 7 | belongs_to :placement, :inverse_of => :banners 8 | 9 | has_many :helper_items, -> { order(:position) }, :dependent => :destroy, inverse_of: :banner 10 | accepts_nested_attributes_for :helper_items 11 | 12 | scope :find_future, lambda { 13 | order('priority desc'). 14 | where('end_time > ? OR end_time IS NULL', Time.now). 15 | includes(:placement, :helper_items) 16 | } 17 | scope :find_current, lambda { 18 | find_future. 19 | where('start_time < ? OR start_time IS NULL', 1.hour.from_now). 20 | where('target_view_count IS NULL OR target_view_count > running_view_count') 21 | } 22 | 23 | # todo validates 24 | # validate placement 25 | 26 | def name_sym 27 | @name_sym ||= name.downcase.to_sym 28 | end 29 | 30 | def has_frequency? 31 | !frequency.nil? && frequency > 0 32 | end 33 | 34 | def show_today_has_viewcounts?(current_view_count = nil) 35 | return true if target_view_count.nil? 36 | 37 | current_view_count = running_view_count if current_view_count.nil? 38 | 39 | return false if current_view_count >= target_view_count 40 | return true if fast_mode? 41 | 42 | @show_now_today_target ||= 43 | if target_view_count.nil? || end_time.nil? || end_time < 24.hours.from_now 44 | true 45 | else 46 | total_hours = ((end_time - start_time - 12.hours)/1.hour).round 47 | hourly_view_count = target_view_count/total_hours 48 | 49 | hours_ending_now = ((Time.now.end_of_hour - start_time)/1.hour).ceil 50 | hours_ending_now * hourly_view_count 51 | end 52 | 53 | @show_now_today_target == true || current_view_count < @show_now_today_target 54 | end 55 | 56 | def show_now_basics?(use_time_limits = true) 57 | confirmed? && 58 | (!use_time_limits || start_time.nil? || start_time < Time.now) && 59 | (!use_time_limits || end_time.nil? || Time.now < end_time) && 60 | show_today_has_viewcounts? 61 | end 62 | 63 | def reload 64 | super 65 | reset_cache 66 | end 67 | 68 | def cache_key 69 | "AdvertSelectorBanner_#{id}" 70 | end 71 | 72 | def running_view_count 73 | counter = Rails.cache.read(cache_key).to_i 74 | counter = 0 if defined?($advert_selector_avoid_cache) && $advert_selector_avoid_cache 75 | counter = self[:running_view_count] if counter < self[:running_view_count] 76 | counter 77 | end 78 | 79 | def reset_cache 80 | Rails.cache.write(cache_key, nil, :expires_in => 2.weeks) 81 | @show_now_today_target = nil 82 | @name_sym = nil 83 | end 84 | 85 | def add_one_viewcount 86 | unless self.target_view_count.nil? 87 | 88 | counter = running_view_count + 1 89 | Rails.cache.write(cache_key, counter, :expires_in => 2.weeks) 90 | self[:running_view_count] = counter 91 | 92 | since_update = running_view_count_change.last - running_view_count_change.first 93 | self.save if since_update >= 500 || counter >= target_view_count 94 | end 95 | end 96 | 97 | after_save :after_save_destroy_empty_helpers 98 | def after_save_destroy_empty_helpers 99 | helper_items.each do |hi| 100 | hi.destroy if hi.blank? 101 | end 102 | end 103 | 104 | end 105 | 106 | end 107 | -------------------------------------------------------------------------------- /app/controllers/advert_selector/banners_controller.rb: -------------------------------------------------------------------------------- 1 | require_dependency "advert_selector/application_controller" 2 | 3 | module AdvertSelector 4 | class BannersController < ApplicationController 5 | # GET /banners 6 | # GET /banners.json 7 | def index 8 | @banners = Banner.all 9 | 10 | respond_to do |format| 11 | format.html # index.html.erb 12 | format.json { render :json => @banners } 13 | end 14 | end 15 | 16 | # GET /banners/1 17 | # GET /banners/1.json 18 | def show 19 | redirect_to edit_banner_url(params[:id]) 20 | 21 | #@banner = Banner.find(params[:id]) 22 | # 23 | #respond_to do |format| 24 | # format.html # show.html.erb 25 | # format.json { render :json => @banner } 26 | #end 27 | end 28 | 29 | # GET /banners/new 30 | # GET /banners/new.json 31 | def new 32 | if params[:duplicate_id] && banner_dup = Banner.find(params[:duplicate_id]) 33 | @banner = banner_dup.dup 34 | @banner.name += " (copy)" 35 | @banner.confirmed = false 36 | 37 | banner_dup.helper_items.each do |hi| 38 | @banner.helper_items << hi.dup 39 | end 40 | 41 | else 42 | @banner = Banner.new 43 | @banner.start_time = Time.now.at_midnight 44 | @banner.end_time = 1.week.from_now.end_of_day 45 | end 46 | 47 | respond_to do |format| 48 | format.html # new.html.erb 49 | format.json { render :json => @banner } 50 | end 51 | end 52 | 53 | # GET /banners/1/edit 54 | def edit 55 | @banner = Banner.find(params[:id]) 56 | end 57 | 58 | # POST /banners 59 | # POST /banners.json 60 | def create 61 | @banner = Banner.new(banner_params) 62 | 63 | respond_to do |format| 64 | if @banner.save 65 | format.html { redirect_to @banner, :notice => 'Banner was successfully created.' } 66 | format.json { render :json => @banner, :status => :created, :location => @banner } 67 | else 68 | format.html { render :action => "new" } 69 | format.json { render :json => @banner.errors, :status => :unprocessable_entity } 70 | end 71 | end 72 | end 73 | 74 | # PUT /banners/1 75 | # PUT /banners/1.json 76 | def update 77 | @banner = Banner.find(params[:id]) 78 | 79 | respond_to do |format| 80 | if @banner.update(banner_params) 81 | format.html { redirect_to @banner, :notice => 'Banner was successfully updated.' } 82 | format.json { head :no_content } 83 | else 84 | format.html { render :action => "edit" } 85 | format.json { render :json => @banner.errors, :status => :unprocessable_entity } 86 | end 87 | end 88 | end 89 | 90 | 91 | def update_running_view_count 92 | @banner = Banner.find(params[:id]) 93 | if !(@count = params['banner']["running_view_count"]).blank? 94 | @count = @count.to_i 95 | Banner.where(:id => @banner.id).update_all(:running_view_count => @count) 96 | 50.times do 97 | # We are trying to make sure that no other process will overwrite this value 98 | Rails.cache.write(@banner.cache_key, @count, :expires_in => 2.weeks) 99 | sleep(0.02) 100 | end 101 | Banner.where(:id => @banner.id).update_all(:running_view_count => @count) 102 | #@banner[:running_view_count] = @count 103 | #@banner.save 104 | end 105 | 106 | redirect_to @banner 107 | end 108 | 109 | # DELETE /banners/1 110 | # DELETE /banners/1.json 111 | def destroy 112 | @banner = Banner.find(params[:id]) 113 | @banner.destroy 114 | 115 | respond_to do |format| 116 | format.html { redirect_to banners_url } 117 | format.json { head :no_content } 118 | end 119 | end 120 | 121 | private 122 | def banner_params 123 | params 124 | .require(:banner) 125 | .permit( 126 | :comment, :end_time, :frequency, :name, :placement_id, :start_time, :target_view_count, :priority, :confirmed, :fast_mode, 127 | :helper_items_attributes => [:id, :name, :content_for, :position, :content] 128 | ) 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdvertSelector 2 | 3 | Rails adserver tool for selecting a smaller subset of banners from all 4 | possible banners with differing banner placement combinations. Gem 5 | includes admin tools for handling banners in live 6 | environment. Includes basic targeting, viewcount, frequency etc 7 | setups. 8 | 9 | Good for deciding e.g. what height of header banner you will have 10 | during the initial requests without extra javascript calls to 11 | adserver. This helps to avoid problems of browser not knowing what 12 | size of banner there is and enables browser to render the whole page 13 | faster. 14 | 15 | [](http://travis-ci.org/holli/advert_selector) 16 | 17 | ## Features 18 | 19 | - selecting small subset of (advert) banners from multiple banners 20 | - banners can be also be any kind of widgets 21 | - defining banners 22 | - setting filters to banners in a rails friendly way 23 | - defining placements 24 | - setting conflicting placements 25 | - setting filters for placements 26 | - admin tools 27 | - editing everything 28 | - testing banners before setting it to live env 29 | - showing information in banner testing 30 | - showing error log if there is a problem in banner filters 31 | 32 | ## Install 33 | 34 | ``` 35 | in /gemfile 36 | gem 'advert_selector' 37 | 38 | # Run following 39 | # bundle 40 | # bundle exec rake railties:install:migrations 41 | # rake db:migrate 42 | 43 | in top of views/layouts/your_layout 44 | <% advert_selector_initialize(:all) %> 45 | and if you want testing info then at the bottom 46 | <%= advert_selector_force_test_infos %> 47 | 48 | set to somewhere in your layout e.g. 49 | <%= content_for :banner_header %> 50 | 51 | in config/routes 52 | mount AdvertSelector::Engine => "/advert_selector" 53 | 54 | test the admin tool in url 55 | http://localhost:3000/advert_selector/ 56 | 57 | If you have problems, try to install simple_form again by setting `gem simple_form` to .Gemfile. 58 | 59 | ``` 60 | 61 | ## Configuration 62 | 63 | 64 | ``` 65 | set extra_configuration in /config/initializers/advert_selector.rb 66 | 67 | 68 | AdvertSelector.default_banner_test_url = "http://yourdomain.com/?" 69 | self.default_time_zone = 'Helsinki' 70 | 71 | class AdminAccessToGemTools 72 | def self.admin_access(controller) 73 | if !controller.session[:admin_logged].blank? || Rails.env.development? 74 | return true 75 | else 76 | return false 77 | end 78 | end 79 | end 80 | AdvertSelector.admin_access_class = AdminAccessToGemTools 81 | 82 | 83 | ``` 84 | 85 | 86 | ## Targeting / Content / HelperItem 87 | 88 | All contents and targetings are done through HelperItem-model. 89 | 90 | ### For targeting define custom helpers alongside your normal viewhelpers. Start by name advert_selector. E.g 91 | 92 | ``` 93 | module AdvertSelectorHelper 94 | def advert_selector_targeting_gender?(helper_item) 95 | params[:gender] == helper_item.content 96 | end 97 | end 98 | 99 | And in banner define HelperItem with name 'targeting_age?' and content 'male'. After that the banner would be shown only with 100 | requests that has param[:gender]='male'. 101 | 102 | ``` 103 | 104 | ### For displaying banner content 105 | 106 | HelperItems that are tagged with content_for are used with content_for method in rails. E.g. HelperItem.name = :banner_header 107 | and then in your views you display results by <%= content_for :banner_header %> 108 | 109 | ## Inside 110 | 111 | Banners are read to ruby processes memory once every 10 minutes from sql database. 112 | Gem uses Rails.cache to cache viewcount of banners. Viewcount is updated to db once in a while. 113 | 114 | Remember that this is not the real viewcount that more advanced banner handling systems have. Bots e.g. will increase the viewcount number 115 | 116 | 117 | ## Some links you might also consider 118 | 119 | - http://www.openx.com/ 120 | - http://www.google.com/dfp/info/sb/index.html 121 | 122 | 123 | ## Support 124 | 125 | Submit suggestions or feature requests as a GitHub Issue or Pull Request. Remember to update tests. Tests are quite extensive. 126 | 127 | Check travis for what environments are supported http://travis-ci.org/#!/holli/advert_selector 128 | 129 | [](http://travis-ci.org/holli/advert_selector) 130 | 131 | ## Licence 132 | 133 | This project rocks and uses MIT-LICENSE. 134 | -------------------------------------------------------------------------------- /test/integration/banner_shows_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../test_helper' 2 | #require 'test/test_helper' 3 | 4 | class BannerShowsTest < ActionDispatch::IntegrationTest 5 | # fixtures :all 6 | 7 | setup do 8 | @coke.reload 9 | @coke.fast_mode = true 10 | @coke.save! 11 | assert @coke.show_now_basics?, "fixtures problem" 12 | @pepsi.reload 13 | @pepsi.update({:confirmed => false}) 14 | assert !@pepsi.show_now_basics?, "fixtures prolbem 2" 15 | AdvertSelector.admin_access_class = AdvertSelector::AdminAccessClassAlwaysTrue 16 | end 17 | 18 | def response_includes_banner?(banner) 19 | @response.body.include?(banner.helper_items.where(:content_for => true).last.content) 20 | end 21 | def assert_response_includes_banner(banner) 22 | assert response_includes_banner?(banner), "should have default banner content in response" 23 | end 24 | 25 | test "normal request and banner loading" do 26 | AdvertSelector::Banner.expects(:find_current).twice.returns(AdvertSelector::Banner.find_future) 27 | 28 | get '/' 29 | assert_response :success 30 | 31 | assert_response_includes_banner(@coke) 32 | 33 | assert $advert_selector_banners_load_time > 1.minute.ago 34 | original_time = $advert_selector_banners_load_time 35 | 36 | #assert_equal $advert_selector_banners, AdvertSelector::Banner.find_current 37 | 38 | get '/' 39 | assert_response :success 40 | 41 | assert_equal $advert_selector_banners_load_time, original_time 42 | 43 | Timecop.travel( 15.minutes.from_now ) do 44 | get '/' 45 | assert $advert_selector_banners_load_time != original_time 46 | assert $advert_selector_banners_load_time > 1.minute.ago 47 | end 48 | end 49 | 50 | test "only_once_per_session banners" do 51 | placement = @coke.placement 52 | placement.only_once_per_session = true 53 | placement.save! 54 | 55 | get '/' 56 | assert_response :success 57 | assert_response_includes_banner(@coke) 58 | 59 | assert_equal ["Leaderboard"], session["advert_selector_session_shown"], "should have info in session" 60 | 61 | get '/' 62 | assert_response :success 63 | assert !response_includes_banner?(@coke), "should not include banner on second view" 64 | end 65 | 66 | test "banner_frequency, limit frequency within one week" do 67 | @coke.update({:frequency => 2}) 68 | 69 | get '/' 70 | assert_response :success 71 | assert_response_includes_banner(@coke) 72 | 73 | cookie_expiration_date_org = cookies.send(:eval, '@cookies').first.send(:eval, '@options')["expires"].to_time 74 | assert 6.days.from_now < cookie_expiration_date_org 75 | assert 7.days.from_now > cookie_expiration_date_org 76 | 77 | Timecop.travel( 3.days.from_now ) 78 | 79 | get '/' 80 | assert_response :success 81 | assert_response_includes_banner(@coke) 82 | 83 | cookie_expiration_date_new = cookies.send(:eval, '@cookies').first.send(:eval, '@options')["expires"].to_time 84 | assert_equal cookie_expiration_date_new, cookie_expiration_date_org 85 | 86 | get '/' 87 | assert_response :success 88 | assert !response_includes_banner?(@coke), "should not include banner after expiration date" 89 | 90 | end 91 | 92 | test "HelperItem runned" do 93 | get '/' 94 | assert_response :success 95 | assert_response_includes_banner(@coke) 96 | 97 | $advert_selector_banners_load_time = nil 98 | 99 | @coke.helper_items.create!(:position => 0, :name => 'always_false') 100 | get '/' 101 | assert_response :success 102 | assert !response_includes_banner?(@coke), "should not include banner if helper_item returned false" 103 | 104 | end 105 | 106 | test "HelperItem with raising error and common error displays" do 107 | 108 | @coke.helper_items.create!(:position => 0, :name => 'raise_error') 109 | get '/' 110 | assert_response :success 111 | assert !response_includes_banner?(@coke), "should not include banner if helper_item raised error" 112 | 113 | get '/advert_selector/placements' 114 | assert_response :success 115 | assert_select '.alert-error', :text => /Error with banner Coke in placement Leaderboard/ 116 | 117 | end 118 | 119 | 120 | test "complex setup conflicting banners placements" do 121 | @parade_banner.update({:confirmed => true, :frequency => 1}) 122 | get '/' 123 | assert_response :success 124 | assert response_includes_banner?(@parade_banner) 125 | assert !response_includes_banner?(@coke) 126 | 127 | get '/' 128 | assert_response :success 129 | assert !response_includes_banner?(@parade_banner) 130 | assert response_includes_banner?(@coke) 131 | end 132 | 133 | test "complex setup multiple banners placements" do 134 | @parade_banner.update({:confirmed => true, :frequency => 1}) 135 | @parade_banner.placement.conflicting_placements_array = "" 136 | @parade_banner.placement.save! 137 | 138 | get '/' 139 | assert_response :success 140 | assert response_includes_banner?(@parade_banner) 141 | assert response_includes_banner?(@coke) 142 | end 143 | 144 | test "banner preview url forces banner and displays information" do 145 | get '/' 146 | assert !response_includes_banner?(@parade_banner) 147 | assert_select '#advert_selector_info', :count => 0 148 | 149 | get "/?advert_selector_force=#{@parade_banner.id}&advert_selector_force_stamp=#{@parade_banner.start_time.to_i}" 150 | assert_response :success 151 | assert response_includes_banner?(@parade_banner) 152 | 153 | assert_select '#advert_selector_info', :count => 1 154 | end 155 | 156 | end 157 | 158 | -------------------------------------------------------------------------------- /app/helpers/advert_selector/application_helper.rb: -------------------------------------------------------------------------------- 1 | module AdvertSelector 2 | module ApplicationHelper 3 | 4 | def advert_selector_initialize(available_placements = :all) 5 | Rails.logger.tagged('AdvertSelector') do 6 | 7 | Rails.logger.debug("AdvertSelection initialized") 8 | 9 | @advert_selector_banners_selected = [] 10 | 11 | if params[:advert_selector_force] 12 | $advert_selector_banners_load_time = nil # reload everything 13 | if (banner_found = Banner.find_by_id(params[:advert_selector_force])) && banner_found.start_time.to_i.to_s == params[:advert_selector_force_stamp] 14 | advert_selector_banner_force(banner_found) 15 | end 16 | end 17 | 18 | advert_selector_banners.each do |banner_iter| 19 | if available_placements == :all || available_placements.include?(banner_iter.placement.name_sym) 20 | advert_selector_banner_try(banner_iter) 21 | end 22 | end 23 | 24 | Rails.logger.debug("AdvertSelection finished") 25 | end 26 | "" 27 | end 28 | 29 | def advert_selector_force_test_infos 30 | if defined?(@advert_selector_force_banner_infos) && @advert_selector_force_banner_infos 31 | content_tag :div, :id => "advert_selector_info", :class => 'alert alert-info', :style => "position: fixed; bottom: 5px;" do 32 | content_tag(:strong) { "AdvertSelectorInfos for HelperItems:
".html_safe } + 33 | content_tag(:ul) { 34 | @advert_selector_force_banner_infos.to_a.collect{|k, v| content_tag(:li){"#{k} : #{h(v)}".html_safe} }.join("\n").html_safe 35 | } 36 | end 37 | end 38 | 39 | end 40 | 41 | 42 | def advert_selector_banner_try(banner) 43 | 44 | if banner.show_now_basics? && 45 | advert_selector_placement_free?(banner.placement) && 46 | advert_selector_placement_once_per_session_ok?(banner.placement) && 47 | advert_selector_banner_frequency_ok?(banner) 48 | 49 | banner.helper_items.each do |hi| 50 | if hi.content_for? 51 | content_for hi.name_sym, hi.content.html_safe 52 | else 53 | return unless send("advert_selector_#{hi.name}", hi) 54 | end 55 | end 56 | 57 | advert_selector_placement_once_per_session_shown(banner.placement) 58 | advert_selector_banner_frequency_shown(banner) 59 | 60 | banner.add_one_viewcount unless request.user_agent =~ /bot/i 61 | 62 | @advert_selector_banners_selected.push(banner) 63 | 64 | Rails.logger.info("Showing banner (#{banner.id}) #{banner.name} in placement #{banner.placement.name}") 65 | end 66 | 67 | rescue => e 68 | begin 69 | str = "Error with banner #{banner.name} in placement #{banner.placement.name}.\n#{Time.now.iso8601} - #{request.url} - #{params.inspect}\n#{e.inspect}\n\n#{e.backtrace.first(10).join("\n")}" 70 | 71 | AdvertSelector::ErrorsCache.add(str) 72 | Rails.logger.error(str) 73 | rescue => e 74 | Rails.logger.error("ERROR INSIDE ERROR with #{banner.name} in placement #{banner.placement.name} : #{e.inspect}") 75 | end 76 | end 77 | 78 | def advert_selector_banner_force(banner) 79 | @advert_selector_force_banner_infos = [] 80 | @advert_selector_force_banner_infos.push [:show_now_basics_times_not_used, banner.show_now_basics?(false)] 81 | @advert_selector_force_banner_infos.push [:show_now_basics_with_times, banner.show_now_basics?] 82 | @advert_selector_force_banner_infos.push [:placement_free, advert_selector_placement_free?(banner.placement)] 83 | @advert_selector_force_banner_infos.push [:placement_once_per_session, advert_selector_placement_once_per_session_ok?(banner.placement)] 84 | @advert_selector_force_banner_infos.push [:frequency, advert_selector_banner_frequency_ok?(banner)] 85 | banner.helper_items.each do |hi| 86 | @advert_selector_force_banner_infos.push [hi.name_sym, 87 | if hi.content_for? 88 | content_for hi.name_sym, hi.content.html_safe 89 | content_for(hi.name_sym).first(20) 90 | else 91 | send("advert_selector_#{hi.name}", hi) 92 | end] 93 | end 94 | 95 | @advert_selector_banners_selected.push(banner) 96 | Rails.logger.info("ForceShowing banner #{banner.name} in placement #{banner.placement.name}") 97 | 98 | end 99 | 100 | def advert_selector_placement_free?(placement) 101 | !placement.conflicting_with?(@advert_selector_banners_selected.collect{|b| b.placement.name_sym}) 102 | end 103 | 104 | def advert_selector_placement_once_per_session_ok?(placement) 105 | !( placement.only_once_per_session? && session[:advert_selector_session_shown] && 106 | session[:advert_selector_session_shown].include?(placement.name) ) 107 | end 108 | def advert_selector_placement_once_per_session_shown(placement) 109 | if placement.only_once_per_session? 110 | session[:advert_selector_session_shown] = [] if session[:advert_selector_session_shown].nil? 111 | session[:advert_selector_session_shown].push(placement.name) 112 | end 113 | end 114 | 115 | def advert_selector_banner_frequency_cookie(banner) 116 | val, time = cookies["ad_#{banner.id}"].to_s.split(",") 117 | [val.to_i, time] 118 | end 119 | def advert_selector_banner_frequency_ok?(banner) 120 | !banner.has_frequency? || advert_selector_banner_frequency_cookie(banner).first < banner.frequency 121 | end 122 | def advert_selector_banner_frequency_shown(banner) 123 | return true unless banner.has_frequency? 124 | 125 | val, time = advert_selector_banner_frequency_cookie(banner) 126 | time = time.blank? ? 1.week.from_now : Time.parse(time) 127 | val += 1 128 | cookies["ad_#{banner.id}"] = {:domain => :all, :expires => time, :value => [val, time.iso8601].join(",") } 129 | end 130 | 131 | ########################################################## 132 | 133 | def advert_selector_request_params_include?(placement) 134 | key, val = placement.content.to_s.split("=") 135 | return params[key] == val 136 | end 137 | 138 | 139 | ########################################################## 140 | 141 | $advert_selector_banners = [] 142 | $advert_selector_banners_load_time = nil 143 | def advert_selector_banners 144 | if $advert_selector_banners_load_time.nil? || $advert_selector_banners_load_time < 10.minutes.ago || Rails.env.development? 145 | Rails.logger.info("AdvertSelection fetching banners and placements") 146 | $advert_selector_banners_load_time = Time.now 147 | 148 | $advert_selector_banners = Banner.find_current 149 | end 150 | 151 | $advert_selector_banners 152 | end 153 | 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /test/dummy/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 => :lookup` 26 | # to the input. If so, they will retrieve the values from the model 27 | # if any exists. If you want to enable the lookup for 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 | end 47 | 48 | config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b| 49 | b.use :html5 50 | b.use :placeholder 51 | b.use :label 52 | b.wrapper :tag => 'div', :class => 'controls' do |ba| 53 | ba.use :input 54 | ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } 55 | ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' } 56 | end 57 | end 58 | 59 | config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b| 60 | b.use :html5 61 | b.use :placeholder 62 | b.use :label 63 | b.wrapper :tag => 'div', :class => 'controls' do |input| 64 | input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend| 65 | prepend.use :input 66 | end 67 | input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' } 68 | input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } 69 | end 70 | end 71 | 72 | config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b| 73 | b.use :html5 74 | b.use :placeholder 75 | b.use :label 76 | b.wrapper :tag => 'div', :class => 'controls' do |input| 77 | input.wrapper :tag => 'div', :class => 'input-append' do |append| 78 | append.use :input 79 | end 80 | input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' } 81 | input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } 82 | end 83 | end 84 | 85 | # Wrappers for forms and inputs using the Twitter Bootstrap toolkit. 86 | # Check the Bootstrap docs (http://twitter.github.com/bootstrap) 87 | # to learn about the different styles for forms and inputs, 88 | # buttons and other elements. 89 | config.default_wrapper = :bootstrap 90 | 91 | # Define the way to render check boxes / radio buttons with labels. 92 | # Defaults to :nested for bootstrap config. 93 | # :inline => input + label 94 | # :nested => label > input 95 | config.boolean_style = :nested 96 | 97 | # Default class for buttons 98 | config.button_class = 'btn' 99 | 100 | # Method used to tidy up errors. Specify any Rails Array method. 101 | # :first lists the first message for each field. 102 | # Use :to_sentence to list all errors for each field. 103 | # config.error_method = :first 104 | 105 | # Default tag used for error notification helper. 106 | config.error_notification_tag = :div 107 | 108 | # CSS class to add for error notification helper. 109 | config.error_notification_class = 'alert alert-error' 110 | 111 | # ID to add for error notification helper. 112 | # config.error_notification_id = nil 113 | 114 | # Series of attempts to detect a default label method for collection. 115 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] 116 | 117 | # Series of attempts to detect a default value method for collection. 118 | # config.collection_value_methods = [ :id, :to_s ] 119 | 120 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. 121 | # config.collection_wrapper_tag = nil 122 | 123 | # You can define the class to use on all collection wrappers. Defaulting to none. 124 | # config.collection_wrapper_class = nil 125 | 126 | # You can wrap each item in a collection of radio/check boxes with a tag, 127 | # defaulting to :span. Please note that when using :boolean_style = :nested, 128 | # SimpleForm will force this option to be a label. 129 | # config.item_wrapper_tag = :span 130 | 131 | # You can define a class to use in all item wrappers. Defaulting to none. 132 | # config.item_wrapper_class = nil 133 | 134 | # How the label text should be generated altogether with the required text. 135 | # config.label_text = lambda { |label, required| "#{required} #{label}" } 136 | 137 | # You can define the class to use on all labels. Default is nil. 138 | config.label_class = 'control-label' 139 | 140 | # You can define the class to use on all forms. Default is simple_form. 141 | # config.form_class = :simple_form 142 | 143 | # You can define which elements should obtain additional classes 144 | # config.generate_additional_classes_for = [:wrapper, :label, :input] 145 | 146 | # Whether attributes are required by default (or not). Default is true. 147 | # config.required_by_default = true 148 | 149 | # Tell browsers whether to use default HTML5 validations (novalidate option). 150 | # Default is enabled. 151 | config.browser_validations = false 152 | 153 | # Collection of methods to detect if a file type was given. 154 | # config.file_methods = [ :mounted_as, :file?, :public_filename ] 155 | 156 | # Custom mappings for input types. This should be a hash containing a regexp 157 | # to match as key, and the input type that will be used when the field name 158 | # matches the regexp as value. 159 | # config.input_mappings = { /count/ => :integer } 160 | 161 | # Default priority for time_zone inputs. 162 | # config.time_zone_priority = nil 163 | 164 | # Default priority for country inputs. 165 | # config.country_priority = nil 166 | 167 | # Default size for text inputs. 168 | # config.default_input_size = 50 169 | 170 | # When false, do not use translations for labels. 171 | # config.translate_labels = true 172 | 173 | # Automatically discover new inputs in Rails' autoload path. 174 | # config.inputs_discovery = true 175 | 176 | # Cache SimpleForm inputs discovery 177 | # config.cache_discovery = !Rails.env.development? 178 | end 179 | -------------------------------------------------------------------------------- /test/unit/advert_selector/banner_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../test_helper' 2 | 3 | module AdvertSelector 4 | class BannerTest < ActiveSupport::TestCase 5 | # fixtures :all 6 | 7 | setup do 8 | $advert_selector_avoid_cache = true 9 | end 10 | 11 | test 'find_future && find_current scopes' do 12 | #binding.pry 13 | assert_equal 3, Banner.find_future.size 14 | assert_equal 3, Banner.find_current.size 15 | 16 | Timecop.travel( 1.year.ago ) do 17 | assert_equal 3, Banner.find_future.size 18 | assert_equal 0, Banner.find_current.size 19 | end 20 | 21 | end 22 | 23 | test "name_sym" do 24 | assert_equal :coke, @coke.name_sym 25 | end 26 | 27 | test "running_viewcount & add_one_viewcount" do 28 | $advert_selector_avoid_cache = false 29 | @coke.reset_cache 30 | @coke[:running_view_count] = 0 31 | @coke.save 32 | 33 | assert_equal 0, @coke.running_view_count 34 | @coke.add_one_viewcount 35 | @coke.add_one_viewcount 36 | assert_equal 2, @coke.running_view_count 37 | 38 | coke_second = Banner.find(@coke.id) 39 | assert_equal 0, coke_second[:running_view_count], "should not save value to db after every reload" 40 | assert_equal 2, coke_second.running_view_count, "should fetch value from rails cache in every view" 41 | 42 | Rails.cache.write(@coke.cache_key, 550, :expires_in => 2.weeks) 43 | @coke.add_one_viewcount 44 | 45 | coke_third = Banner.find(@coke.id) 46 | assert_equal 551, coke_third[:running_view_count], "should have saved value to db after so many views" 47 | end 48 | 49 | test "running_viewcount & add_one_viewcount reaching target" do 50 | $advert_selector_avoid_cache = false 51 | @coke.reset_cache 52 | @coke[:running_view_count] = 0 53 | @coke.target_view_count = 10 54 | @coke.save 55 | 56 | assert_equal 0, @coke.running_view_count 57 | Rails.cache.write(@coke.cache_key, @coke.target_view_count-1, :expires_in => 2.weeks) 58 | @coke.add_one_viewcount 59 | assert_equal 10, @coke.running_view_count 60 | 61 | coke_second = Banner.find(@coke.id) 62 | assert_equal 10, coke_second[:running_view_count], "should save if reaching target" 63 | end 64 | 65 | test "view_count per_hour" do 66 | 67 | start_time = Time.now.at_beginning_of_day 68 | 69 | # 10 views per hour 70 | @coke.update(:start_time => start_time, :end_time => start_time + 112.hours, 71 | :target_view_count => 1000, :fast_mode => false) 72 | @coke.running_view_count = 0 73 | @coke.save! 74 | 75 | Timecop.travel( start_time + 4.5.hours ) 76 | assert @coke.show_today_has_viewcounts? 77 | 78 | @coke.reload 79 | @coke.running_view_count = 39 80 | assert @coke.show_today_has_viewcounts? 81 | 82 | @coke.reload 83 | @coke.running_view_count = 49 84 | assert @coke.show_today_has_viewcounts? 85 | 86 | @coke.reload 87 | @coke.running_view_count = 51 88 | assert !@coke.show_today_has_viewcounts? 89 | 90 | Timecop.travel( start_time + 90.hours ) 91 | @coke.reload 92 | @coke.running_view_count = 990 93 | assert @coke.show_today_has_viewcounts?, "should let last 24h to display everything straight away" 94 | 95 | end 96 | 97 | test "view_count basics compare_value and per_fast_mode" do 98 | start_time = Time.now.at_beginning_of_day 99 | 100 | # 10 views per hour 101 | @coke.update(:start_time => start_time, :end_time => start_time + 112.hours, 102 | :target_view_count => 1000, :fast_mode => false) 103 | @coke.running_view_count = 0 104 | @coke.save! 105 | 106 | Timecop.travel( start_time + 4.5.hours ) 107 | 108 | assert @coke.show_today_has_viewcounts? 109 | @coke.reload 110 | assert !@coke.show_today_has_viewcounts?(900), "use given value in has_viewcounts" 111 | 112 | @coke.reload 113 | @coke.fast_mode = true 114 | assert @coke.show_today_has_viewcounts?(900), "true always with fast_mode on" 115 | 116 | @coke.reload 117 | @coke.fast_mode = true 118 | assert !@coke.show_today_has_viewcounts?(1010), "should be false if viewcount has been achieved even though in fast mode" 119 | 120 | @coke.reload 121 | @coke.target_view_count = nil 122 | assert @coke.show_today_has_viewcounts?(2000), "should be true if no target viewcount" 123 | end 124 | 125 | test "view_count daily tests" do 126 | start_time = Time.now.at_beginning_of_day 127 | @coke.update(:start_time => start_time, :end_time => 10.days.from_now.at_beginning_of_day, 128 | :target_view_count => 1000, :fast_mode => false) 129 | @coke.running_view_count = 0 130 | @coke.save! 131 | 132 | Timecop.travel( start_time + 11.hours ) 133 | 134 | assert @coke.show_today_has_viewcounts? 135 | 136 | @coke.reload 137 | @coke.running_view_count = 130 138 | assert !@coke.show_today_has_viewcounts?, "daily limit should be full" 139 | 140 | @coke.fast_mode = true 141 | assert @coke.show_today_has_viewcounts?, "fast_mode true, always true" 142 | @coke.fast_mode = false 143 | 144 | 145 | Timecop.travel( 5.days.from_now.at_midnight + 12.hours ) do 146 | @coke.reload 147 | @coke.running_view_count = 300 148 | assert @coke.show_today_has_viewcounts? 149 | assert @coke.show_today_has_viewcounts?(300) 150 | assert !@coke.show_today_has_viewcounts?(600), "should compare with given value" 151 | 152 | @coke.reload 153 | @coke.running_view_count = 600 154 | assert !@coke.show_today_has_viewcounts? 155 | end 156 | 157 | Timecop.travel( 9.days.from_now.at_midnight + 12.hours ) do 158 | @coke.reload 159 | assert @coke.show_today_has_viewcounts?(990), "last day should always be true" 160 | end 161 | 162 | @coke.reload 163 | assert !@coke.show_today_has_viewcounts?(1010), "should be false if viewcount has been achieved" 164 | 165 | @coke.reload 166 | @coke.target_view_count = nil 167 | assert @coke.show_today_has_viewcounts?(1000), "should be true if no target viewcount" 168 | 169 | Timecop.travel( start_time + 19.hours ) do 170 | @coke.reload 171 | @coke.update(:start_time => start_time + 18.hours, :end_time => 10.days.from_now.at_beginning_of_day, 172 | :target_view_count => 1000) 173 | @coke.running_view_count = 0 174 | assert @coke.show_today_has_viewcounts?(), "should be true for the first day even if start is later in the evening" 175 | end 176 | end 177 | 178 | test "show_now_basic? for default banners" do 179 | @coke.confirmed = false 180 | @coke.save! 181 | assert !@coke.show_now_basics?, "not confirmed" 182 | 183 | @coke.confirmed = true 184 | @coke.save! 185 | assert @coke.show_now_basics? 186 | 187 | @coke.reload 188 | @coke.running_view_count = 100000000 189 | assert !@coke.show_now_basics?, "target reached" 190 | end 191 | 192 | test "show_now_basic? time usages" do 193 | assert @coke.show_now_basics?, "setup is wrong" 194 | 195 | @coke.start_time = 1.hour.from_now 196 | @coke.save! 197 | assert !@coke.show_now_basics?, "should not display if advert in future" 198 | 199 | @coke.start_time = 2.hour.ago 200 | @coke.end_time = 1.hour.ago 201 | @coke.save! 202 | assert !@coke.show_now_basics?, "should not display if advert in past" 203 | 204 | @coke.start_time = nil 205 | @coke.end_time = 1.hour.from_now 206 | @coke.save! 207 | assert @coke.show_now_basics?, "should display if advert has only end_time in the future" 208 | 209 | end 210 | 211 | test "HelperItems" do 212 | helper_items_count = HelperItem.count 213 | @coke.helper_items.build 214 | @coke.helper_items.build(:name => 'content_for_invalid', :content_for => true, :position => 10) 215 | @coke.helper_items.build(:name => 'some_limit_helper_here', :position => 0) 216 | assert @coke.save 217 | 218 | banner = Banner.find(@coke.id) 219 | 220 | assert_equal helper_items_count+2, HelperItem.count 221 | 222 | assert 'some_limit_helper_here', banner.helper_items.first.name 223 | 224 | assert 'content_for_invalid', banner.helper_items.last.name 225 | end 226 | 227 | 228 | # test "the truth" do 229 | # assert true 230 | # end 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /test/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" 7 | templates that are primarily responsible for inserting pre-built data in between 8 | HTML tags. The model contains the "smart" domain objects (such as Account, 9 | Product, Person, Post) that holds all the business logic and knows how to 10 | persist themselves to a database. The controller handles the incoming requests 11 | (such as Save New Account, Update Product, Show Post) by manipulating the model 12 | and directing data to the view. 13 | 14 | In Rails, the model is handled by what's called an object-relational mapping 15 | layer entitled Active Record. This layer allows you to present the data from 16 | database rows as objects and embellish these data objects with business logic 17 | methods. You can read more about Active Record in 18 | link:files/vendor/rails/activerecord/README.html. 19 | 20 | The controller and view are handled by the Action Pack, which handles both 21 | layers by its two parts: Action View and Action Controller. These two layers 22 | are bundled in a single package due to their heavy interdependence. This is 23 | unlike the relationship between the Active Record and Action Pack that is much 24 | more separate. Each of these packages can be used independently outside of 25 | Rails. You can read more about Action Pack in 26 | link:files/vendor/rails/actionpack/README.html. 27 | 28 | 29 | == Getting Started 30 | 31 | 1. At the command prompt, create a new Rails application: 32 | rails new myapp (where myapp is the application name) 33 | 34 | 2. Change directory to myapp and start the web server: 35 | cd myapp; rails server (run with --help for options) 36 | 37 | 3. Go to http://localhost:3000/ and you'll see: 38 | "Welcome aboard: You're riding Ruby on Rails!" 39 | 40 | 4. Follow the guidelines to start developing your application. You can find 41 | the following resources handy: 42 | 43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html 44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ 45 | 46 | 47 | == Debugging Rails 48 | 49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 50 | will help you debug it and get it back on the rails. 51 | 52 | First area to check is the application log files. Have "tail -f" commands 53 | running on the server.log and development.log. Rails will automatically display 54 | debugging and runtime information to these files. Debugging info will also be 55 | shown in the browser on requests from 127.0.0.1. 56 | 57 | You can also log your own messages directly into the log file from your code 58 | using the Ruby logger class from inside your controllers. Example: 59 | 60 | class WeblogController < ActionController::Base 61 | def destroy 62 | @weblog = Weblog.find(params[:id]) 63 | @weblog.destroy 64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 65 | end 66 | end 67 | 68 | The result will be a message in your log file along the lines of: 69 | 70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! 71 | 72 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 73 | 74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are 75 | several books available online as well: 76 | 77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) 78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 79 | 80 | These two books will bring you up to speed on the Ruby language and also on 81 | programming in general. 82 | 83 | 84 | == Debugger 85 | 86 | Debugger support is available through the debugger command when you start your 87 | Mongrel or WEBrick server with --debugger. This means that you can break out of 88 | execution at any point in the code, investigate and change the model, and then, 89 | resume execution! You need to install ruby-debug to run the server in debugging 90 | mode. With gems, use sudo gem install ruby-debug. Example: 91 | 92 | class WeblogController < ActionController::Base 93 | def index 94 | @posts = Post.all 95 | debugger 96 | end 97 | end 98 | 99 | So the controller will accept the action, run the first line, then present you 100 | with a IRB prompt in the server window. Here you can do things like: 101 | 102 | >> @posts.inspect 103 | => "[#nil, "body"=>nil, "id"=>"1"}>, 105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" 107 | >> @posts.first.title = "hello from a debugger" 108 | => "hello from a debugger" 109 | 110 | ...and even better, you can examine how your runtime objects actually work: 111 | 112 | >> f = @posts.first 113 | => #nil, "body"=>nil, "id"=>"1"}> 114 | >> f. 115 | Display all 152 possibilities? (y or n) 116 | 117 | Finally, when you're ready to resume execution, you can enter "cont". 118 | 119 | 120 | == Console 121 | 122 | The console is a Ruby shell, which allows you to interact with your 123 | application's domain model. Here you'll have all parts of the application 124 | configured, just like it is when the application is running. You can inspect 125 | domain models, change values, and save to the database. Starting the script 126 | without arguments will launch it in the development environment. 127 | 128 | To start the console, run rails console from the application 129 | directory. 130 | 131 | Options: 132 | 133 | * Passing the -s, --sandbox argument will rollback any modifications 134 | made to the database. 135 | * Passing an environment name as an argument will load the corresponding 136 | environment. Example: rails console production. 137 | 138 | To reload your controllers and models after launching the console run 139 | reload! 140 | 141 | More information about irb can be found at: 142 | link:http://www.rubycentral.org/pickaxe/irb.html 143 | 144 | 145 | == dbconsole 146 | 147 | You can go to the command line of your database directly through rails 148 | dbconsole. You would be connected to the database with the credentials 149 | defined in database.yml. Starting the script without arguments will connect you 150 | to the development database. Passing an argument will connect you to a different 151 | database, like rails dbconsole production. Currently works for MySQL, 152 | PostgreSQL and SQLite 3. 153 | 154 | == Description of Contents 155 | 156 | The default directory structure of a generated Ruby on Rails application: 157 | 158 | |-- app 159 | | |-- assets 160 | | |-- images 161 | | |-- javascripts 162 | | `-- stylesheets 163 | | |-- controllers 164 | | |-- helpers 165 | | |-- mailers 166 | | |-- models 167 | | `-- views 168 | | `-- layouts 169 | |-- config 170 | | |-- environments 171 | | |-- initializers 172 | | `-- locales 173 | |-- db 174 | |-- doc 175 | |-- lib 176 | | `-- tasks 177 | |-- log 178 | |-- public 179 | |-- script 180 | |-- test 181 | | |-- fixtures 182 | | |-- functional 183 | | |-- integration 184 | | |-- performance 185 | | `-- unit 186 | |-- tmp 187 | | |-- cache 188 | | |-- pids 189 | | |-- sessions 190 | | `-- sockets 191 | `-- vendor 192 | |-- assets 193 | `-- stylesheets 194 | `-- plugins 195 | 196 | app 197 | Holds all the code that's specific to this particular application. 198 | 199 | app/assets 200 | Contains subdirectories for images, stylesheets, and JavaScript files. 201 | 202 | app/controllers 203 | Holds controllers that should be named like weblogs_controller.rb for 204 | automated URL mapping. All controllers should descend from 205 | ApplicationController which itself descends from ActionController::Base. 206 | 207 | app/models 208 | Holds models that should be named like post.rb. Models descend from 209 | ActiveRecord::Base by default. 210 | 211 | app/views 212 | Holds the template files for the view that should be named like 213 | weblogs/index.html.erb for the WeblogsController#index action. All views use 214 | eRuby syntax by default. 215 | 216 | app/views/layouts 217 | Holds the template files for layouts to be used with views. This models the 218 | common header/footer method of wrapping views. In your views, define a layout 219 | using the layout :default and create a file named default.html.erb. 220 | Inside default.html.erb, call <% yield %> to render the view using this 221 | layout. 222 | 223 | app/helpers 224 | Holds view helpers that should be named like weblogs_helper.rb. These are 225 | generated for you automatically when using generators for controllers. 226 | Helpers can be used to wrap functionality for your views into methods. 227 | 228 | config 229 | Configuration files for the Rails environment, the routing map, the database, 230 | and other dependencies. 231 | 232 | db 233 | Contains the database schema in schema.rb. db/migrate contains all the 234 | sequence of Migrations for your schema. 235 | 236 | doc 237 | This directory is where your application documentation will be stored when 238 | generated using rake doc:app 239 | 240 | lib 241 | Application specific libraries. Basically, any kind of custom code that 242 | doesn't belong under controllers, models, or helpers. This directory is in 243 | the load path. 244 | 245 | public 246 | The directory available for the web server. Also contains the dispatchers and the 247 | default HTML files. This should be set as the DOCUMENT_ROOT of your web 248 | server. 249 | 250 | script 251 | Helper scripts for automation and generation. 252 | 253 | test 254 | Unit and functional tests along with fixtures. When using the rails generate 255 | command, template test files will be generated for you and placed in this 256 | directory. 257 | 258 | vendor 259 | External libraries that the application depends on. Also includes the plugins 260 | subdirectory. If the app has frozen rails, those gems also go here, under 261 | vendor/rails/. This directory is in the load path. 262 | --------------------------------------------------------------------------------