├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── gemfiles ├── active_record │ ├── 3-0.gemfile │ ├── 3-1.gemfile │ ├── 3-2.gemfile │ ├── 4-0.gemfile │ ├── 4-1.gemfile │ ├── 4-2.gemfile │ └── 5-0.gemfile ├── data_mapper │ ├── 1-0.gemfile │ ├── 1-1.gemfile │ └── 1-2.gemfile ├── mongo_mapper │ ├── 0-10.gemfile │ ├── 0-11.gemfile │ ├── 0-12.gemfile │ ├── 0-13.gemfile │ └── 0-9.gemfile └── mongoid │ ├── 2-0.gemfile │ ├── 2-1.gemfile │ ├── 2-2.gemfile │ ├── 2-3.gemfile │ ├── 2-4.gemfile │ ├── 2-5.gemfile │ ├── 2-6.gemfile │ ├── 2-7.gemfile │ ├── 2-8.gemfile │ ├── 3-0.gemfile │ ├── 3-1.gemfile │ ├── 4-0.gemfile │ ├── 5-0.gemfile │ └── 5-1.gemfile ├── lib ├── periscope-activerecord.rb ├── periscope-data_mapper.rb ├── periscope-mongo_mapper.rb ├── periscope-mongoid.rb ├── periscope.rb └── periscope │ └── adapters │ ├── active_record.rb │ ├── data_mapper.rb │ ├── mongo_mapper.rb │ └── mongoid.rb ├── periscope-activerecord.gemspec ├── periscope-data_mapper.gemspec ├── periscope-mongo_mapper.gemspec ├── periscope-mongoid.gemspec ├── periscope.gemspec └── spec ├── periscope └── adapters │ ├── active_record_spec.rb │ ├── data_mapper_spec.rb │ ├── mongo_mapper_spec.rb │ └── mongoid_spec.rb ├── periscope_spec.rb ├── shared ├── databasic.rb └── periscopic.rb ├── spec_helper.rb └── support ├── adapters ├── active_record │ ├── connection.rb │ ├── database_cleaner.rb │ ├── factory_girl.rb │ ├── reset.rb │ ├── schema.rb │ └── user.rb ├── data_mapper │ ├── connection.rb │ ├── database_cleaner.rb │ ├── factory_girl.rb │ ├── reset.rb │ ├── schema.rb │ └── user.rb ├── mongo_mapper │ ├── connection.rb │ ├── database_cleaner.rb │ ├── factory_girl.rb │ ├── reset.rb │ └── user.rb └── mongoid │ ├── connection.rb │ ├── database_cleaner.rb │ ├── factory_girl.rb │ ├── reset.rb │ └── user.rb ├── mock_model.rb └── random.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | *.bundle 3 | *.gem 4 | *.o 5 | *.rbc 6 | *.so 7 | .bundle 8 | .config 9 | .yardoc 10 | Gemfile.lock 11 | InstalledFiles 12 | _yardoc 13 | coverage 14 | doc/ 15 | gemfiles/**/*.lock 16 | lib/bundler/man 17 | mkmf.log 18 | pkg 19 | rdoc 20 | spec/reports 21 | test/tmp 22 | test/version_tmp 23 | tmp 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - gem update bundler rake 3 | branches: 4 | only: 5 | - master 6 | cache: bundler 7 | env: 8 | global: 9 | secure: | # CODECLIMATE_REPO_TOKEN 10 | c1IvkNUiB8/23zmAof8gkthrz79Iou+e/rKddC6w11CNwv0s5nusWgjtwuMg 11 | fEEfEFU+Q3TPDtZ+YUjXbzRST6w734OqEltU0QNoFd+j1PL94c6o8iEniJT+ 12 | JAhKTfB5G7X8ZApxCSwBg3ADhM5VdNbF1itYQkg4OJac2mQfx38= 13 | gemfile: 14 | - gemfiles/active_record/3-0.gemfile 15 | - gemfiles/active_record/3-1.gemfile 16 | - gemfiles/active_record/3-2.gemfile 17 | - gemfiles/active_record/4-0.gemfile 18 | - gemfiles/active_record/4-1.gemfile 19 | - gemfiles/active_record/4-2.gemfile 20 | - gemfiles/active_record/5-0.gemfile 21 | - gemfiles/data_mapper/1-0.gemfile 22 | - gemfiles/data_mapper/1-1.gemfile 23 | - gemfiles/data_mapper/1-2.gemfile 24 | - gemfiles/mongo_mapper/0-9.gemfile 25 | - gemfiles/mongo_mapper/0-10.gemfile 26 | - gemfiles/mongo_mapper/0-11.gemfile 27 | - gemfiles/mongo_mapper/0-12.gemfile 28 | - gemfiles/mongo_mapper/0-13.gemfile 29 | - gemfiles/mongoid/2-0.gemfile 30 | - gemfiles/mongoid/2-1.gemfile 31 | - gemfiles/mongoid/2-2.gemfile 32 | - gemfiles/mongoid/2-3.gemfile 33 | - gemfiles/mongoid/2-4.gemfile 34 | - gemfiles/mongoid/2-5.gemfile 35 | - gemfiles/mongoid/2-6.gemfile 36 | - gemfiles/mongoid/2-7.gemfile 37 | - gemfiles/mongoid/2-8.gemfile 38 | - gemfiles/mongoid/3-0.gemfile 39 | - gemfiles/mongoid/3-1.gemfile 40 | - gemfiles/mongoid/4-0.gemfile 41 | - gemfiles/mongoid/5-0.gemfile 42 | - gemfiles/mongoid/5-1.gemfile 43 | language: ruby 44 | matrix: 45 | allow_failures: 46 | - rvm: ruby-head 47 | exclude: 48 | - rvm: 1.9.3 49 | gemfile: gemfiles/active_record/5-0.gemfile 50 | - rvm: 2.0.0 51 | gemfile: gemfiles/active_record/5-0.gemfile 52 | - rvm: 2.1.8 53 | gemfile: gemfiles/active_record/5-0.gemfile 54 | rvm: 55 | - 1.9.3 56 | - 2.0.0 57 | - 2.1.8 58 | - 2.2.4 59 | - 2.3.0 60 | - ruby-head 61 | script: bundle exec rspec 62 | services: mongodb 63 | sudo: false 64 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec name: "periscope" 4 | gemspec name: "periscope-activerecord" 5 | gemspec name: "periscope-data_mapper" 6 | gemspec name: "periscope-mongo_mapper" 7 | gemspec name: "periscope-mongoid" 8 | 9 | group :test do 10 | gem "codeclimate-test-reporter", require: false 11 | gem "database_cleaner" 12 | gem "dm-migrations" 13 | gem "dm-sqlite-adapter" 14 | gem "factory_girl" 15 | gem "rspec" 16 | gem "sqlite3" 17 | end 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Steve Richert 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Periscope 2 | 3 | [![Gem Version](https://img.shields.io/gem/v/periscope.svg?style=flat)](http://rubygems.org/gems/periscope) 4 | [![Build Status](https://img.shields.io/travis/laserlemon/periscope/master.svg?style=flat)](https://travis-ci.org/laserlemon/periscope) 5 | [![Code Climate](https://img.shields.io/codeclimate/github/laserlemon/periscope.svg?style=flat)](https://codeclimate.com/github/laserlemon/periscope) 6 | [![Coverage Status](https://img.shields.io/codeclimate/coverage/github/laserlemon/periscope.svg?style=flat)](https://codeclimate.com/github/laserlemon/periscope) 7 | [![Dependency Status](https://img.shields.io/gemnasium/laserlemon/periscope.svg?style=flat)](https://gemnasium.com/laserlemon/periscope) 8 | 9 | Periscope provides a simple way to chain scopes on your models and to open those scopes up to your users. 10 | 11 | ## Installation 12 | 13 | Periscope sits on top of your favorite ORM. Currently, the following ORMs are supported through individual gems extending Periscope: 14 | 15 | * Active Record ([periscope-activerecord](https://rubygems.org/gems/periscope-activerecord)) 16 | * MongoMapper ([periscope-mongo_mapper](https://rubygems.org/gems/periscope-mongo_mapper)) 17 | * Mongoid ([periscope-mongoid](https://rubygems.org/gems/periscope-mongoid)) 18 | * DataMapper ([periscope-data_mapper](https://rubygems.org/gems/periscope-data_mapper)) 19 | 20 | Simply add the gem to your bundle and you're off! 21 | 22 | ## The Problem 23 | 24 | More often than not, the index action in a RESTful Rails controller is expected to do a lot more than simply return all the records for a given model. We ask it to do all sorts of stuff like filtering, sorting and paginating results. Of course, this is typically done using _scopes_. 25 | 26 | But it can get ugly building long, complicated chains of scopes in the controller, especially when you try to give your users control over the scoping. Picture this: 27 | 28 | ```ruby 29 | def index 30 | @articles = Article.scoped 31 | @articles = @articles.published_after(params[:published_after]) if params.key?(:published_after) 32 | @articles = @articles.published_before(params[:published_before]) if params.key?(:published_before) 33 | end 34 | ``` 35 | 36 | You can imagine how bad this would get if more than two scopes were involved. 37 | 38 | ## The Solution 39 | 40 | With Periscope, you can have this instead: 41 | 42 | ```ruby 43 | def index 44 | @articles = Article.periscope(request.query_parameters) 45 | end 46 | ``` 47 | 48 | The `periscope` method will find keys in your params matching your scope names and chain your scopes for you. 49 | 50 | **Note:** We're using `request.query_parameters` so that we can exclude our controller and action params. `request.query_parameters` will just return the params that appear in the query string. 51 | 52 | ## But Wait! 53 | 54 | "What if I don't want to make all my scopes publicly accessible?" 55 | 56 | Within your model you can use the `scope_accessible` method to specify which scopes you want Periscope to honor. 57 | 58 | ```ruby 59 | class User < ActiveRecord::Base 60 | scope :gender, proc { |g| where(gender: g) } 61 | scope :makes, proc { |s| where("salary >= ?", s) } 62 | 63 | scope_accessible :gender 64 | end 65 | ``` 66 | 67 | And in your controller: 68 | 69 | ```ruby 70 | class UsersController < ApplicationController 71 | def index 72 | @users = User.periscope(request.query_parameters) 73 | end 74 | end 75 | ``` 76 | 77 | Requests to `/users?gender=male` will filter results to only male users. But a request to `/users?makes=1000000` will return all users, silently ignoring the protected scope. 78 | 79 | By default, all scopes are protected. 80 | 81 | ## There's More! 82 | 83 | ### Custom Parameter Parsing 84 | 85 | Sometimes the values you get from the query parameters aren't quite good enough. They may need to be massaged in order to work with your scopes and class methods. In those cases, you can provide a `:parser` option to your `scope_accessible` method. 86 | 87 | Parsers must respond to the `call` method, receiving the raw query parameter and returning an array of arguments to pass to the scope or class method. 88 | 89 | ```ruby 90 | class User < ActiveRecord::Base 91 | scope :gender, proc { |g| where(gender: g) } 92 | 93 | scope_accessible :gender, parser: proc { |g| [g.downcase] } 94 | end 95 | ``` 96 | 97 | ### On/Off Scopes 98 | 99 | But not all scopes accept arguments. For scopes that you want to toggle on or off, you can set a `:boolean => true` option. Whenever the received parameter is truthy, the scope will be applied. Otherwise, it will be skipped. 100 | 101 | ```ruby 102 | class User < ActiveRecord::Base 103 | scope :male, proc { where(gender: "male") } 104 | scope :female, proc { where(gender: "female") } 105 | 106 | scope_accessible :male, :female, boolean: true 107 | end 108 | ``` 109 | 110 | ### Custom Method Names 111 | 112 | Sometimes the query parameters you want to open up to your users may collide with existing method names or reserved Ruby words. In order to avoid collision, you can set a `:method` option to specify what method to use for a query parameter. 113 | 114 | ```ruby 115 | class Project < ActiveRecord::Base 116 | scope_accessible :begin, method: :begins_after 117 | scope_accessible :end, method: :ends_before 118 | 119 | def self.begins_after(date) 120 | where("begins_at >= ?", date) 121 | end 122 | 123 | def self.ends_before(date) 124 | where("ends_at <= ?", date) 125 | end 126 | end 127 | ``` 128 | 129 | Alternatively, you can set `:prefix` and/or `:suffix` options, which will be applied to the query parameter name to determine the corresponding method name. 130 | 131 | ```ruby 132 | class Project < ActiveRecord::Base 133 | scope_accessible :begin, :end, suffix: "_date" 134 | 135 | def self.begin_date(date) 136 | where("begins_at >= ?", date) 137 | end 138 | 139 | def self.end_date(date) 140 | where("ends_at <= ?", date) 141 | end 142 | end 143 | ``` 144 | 145 | ## This sucks. 146 | 147 | How can I make it better? 148 | 149 | 1. Fork it. 150 | 2. Make it better. 151 | 3. Send me a pull request. 152 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_helper" 2 | require "rspec/core/rake_task" 3 | 4 | Bundler::GemHelper.install_tasks(name: "periscope") 5 | Bundler::GemHelper.install_tasks(name: "periscope-activerecord") 6 | Bundler::GemHelper.install_tasks(name: "periscope-mongo_mapper") 7 | Bundler::GemHelper.install_tasks(name: "periscope-mongoid") 8 | Bundler::GemHelper.install_tasks(name: "periscope-data_mapper") 9 | 10 | ADAPTERS = %w(active_record data_mapper mongo_mapper mongoid) 11 | 12 | ADAPTERS.each do |adapter| 13 | desc "Run RSpec code examples for #{adapter} adapter" 14 | RSpec::Core::RakeTask.new(adapter => "#{adapter}:adapter") do |t| 15 | t.pattern = "spec/periscope/adapters/#{adapter}_spec.rb" 16 | end 17 | 18 | namespace adapter do 19 | task :adapter do 20 | ENV["ADAPTER"] = adapter 21 | end 22 | end 23 | end 24 | 25 | RSpec::Core::RakeTask.new(spec: ADAPTERS + [:adapter]) do |t| 26 | t.pattern = "spec/periscope_spec.rb" 27 | end 28 | 29 | task :adapter do 30 | ENV["ADAPTER"] = nil 31 | end 32 | 33 | task default: :spec 34 | -------------------------------------------------------------------------------- /gemfiles/active_record/3-0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activerecord", "~> 3.0.0" 4 | 5 | gemspec name: "periscope-activerecord", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | gem "sqlite3" 13 | end 14 | -------------------------------------------------------------------------------- /gemfiles/active_record/3-1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activerecord", "~> 3.1.0" 4 | 5 | gemspec name: "periscope-activerecord", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | gem "sqlite3" 13 | end 14 | -------------------------------------------------------------------------------- /gemfiles/active_record/3-2.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activerecord", "~> 3.2.0" 4 | 5 | gemspec name: "periscope-activerecord", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | gem "sqlite3" 13 | end 14 | -------------------------------------------------------------------------------- /gemfiles/active_record/4-0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activerecord", "~> 4.0.0" 4 | 5 | gemspec name: "periscope-activerecord", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | gem "sqlite3" 13 | end 14 | -------------------------------------------------------------------------------- /gemfiles/active_record/4-1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activerecord", "~> 4.1.0" 4 | 5 | gemspec name: "periscope-activerecord", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | gem "sqlite3" 13 | end 14 | -------------------------------------------------------------------------------- /gemfiles/active_record/4-2.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activerecord", "~> 4.2.0" 4 | 5 | gemspec name: "periscope-activerecord", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | gem "sqlite3" 13 | end 14 | -------------------------------------------------------------------------------- /gemfiles/active_record/5-0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activerecord", "> 5.beta", "< 5.1" 4 | 5 | gemspec name: "periscope-activerecord", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | gem "sqlite3" 13 | end 14 | -------------------------------------------------------------------------------- /gemfiles/data_mapper/1-0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "dm-core", "~> 1.0.0" 4 | 5 | gemspec name: "periscope-data_mapper", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "dm-migrations" 11 | gem "dm-sqlite-adapter" 12 | gem "factory_girl" 13 | gem "rspec" 14 | gem "sqlite3" 15 | end 16 | -------------------------------------------------------------------------------- /gemfiles/data_mapper/1-1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "dm-core", "~> 1.1.0" 4 | 5 | gemspec name: "periscope-data_mapper", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "dm-migrations" 11 | gem "dm-sqlite-adapter" 12 | gem "factory_girl" 13 | gem "rspec" 14 | gem "sqlite3" 15 | end 16 | -------------------------------------------------------------------------------- /gemfiles/data_mapper/1-2.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "dm-core", "~> 1.2.0" 4 | 5 | gemspec name: "periscope-data_mapper", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "dm-migrations" 11 | gem "dm-sqlite-adapter" 12 | gem "factory_girl" 13 | gem "rspec" 14 | gem "sqlite3" 15 | end 16 | -------------------------------------------------------------------------------- /gemfiles/mongo_mapper/0-10.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongo_mapper", "~> 0.10.0" 4 | 5 | gemspec name: "periscope-mongo_mapper", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongo_mapper/0-11.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongo_mapper", "~> 0.11.0" 4 | 5 | gemspec name: "periscope-mongo_mapper", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongo_mapper/0-12.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongo_mapper", "~> 0.12.0" 4 | 5 | gemspec name: "periscope-mongo_mapper", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongo_mapper/0-13.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongo_mapper", "~> 0.13.0" 4 | 5 | gemspec name: "periscope-mongo_mapper", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongo_mapper/0-9.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongo_mapper", "~> 0.9.0" 4 | 5 | gemspec name: "periscope-mongo_mapper", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.0.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.1.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-2.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.2.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-3.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.3.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-4.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.4.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-5.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.5.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-6.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.6.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-7.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.7.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/2-8.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 2.8.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/3-0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 3.0.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/3-1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 3.1.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/4-0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 4.0.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/5-0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 5.0.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/mongoid/5-1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "mongoid", "~> 5.1.0" 4 | 5 | gemspec name: "periscope-mongoid", path: "../../" 6 | 7 | group :test do 8 | gem "codeclimate-test-reporter", require: false 9 | gem "database_cleaner" 10 | gem "factory_girl" 11 | gem "rspec" 12 | end 13 | -------------------------------------------------------------------------------- /lib/periscope-activerecord.rb: -------------------------------------------------------------------------------- 1 | require "periscope/adapters/active_record" 2 | -------------------------------------------------------------------------------- /lib/periscope-data_mapper.rb: -------------------------------------------------------------------------------- 1 | require "periscope/adapters/data_mapper" 2 | -------------------------------------------------------------------------------- /lib/periscope-mongo_mapper.rb: -------------------------------------------------------------------------------- 1 | require "periscope/adapters/mongo_mapper" 2 | -------------------------------------------------------------------------------- /lib/periscope-mongoid.rb: -------------------------------------------------------------------------------- 1 | require "periscope/adapters/mongoid" 2 | -------------------------------------------------------------------------------- /lib/periscope.rb: -------------------------------------------------------------------------------- 1 | module Periscope 2 | def scope_accessible(*scopes) 3 | options = scopes.last.is_a?(Hash) ? scopes.pop : {} 4 | scopes.each { |s| periscope_options[s.to_s] = options } 5 | end 6 | 7 | def periscope(params = {}) 8 | params.inject(periscope_default_scope) do |chain, (scope, param)| 9 | periscope_call(chain, scope.to_s, param) 10 | end 11 | end 12 | 13 | private 14 | 15 | def periscope_options 16 | @periscope_options ||= {} 17 | end 18 | 19 | def periscope_default_scope 20 | raise NotImplementedError 21 | end 22 | 23 | def periscope_call(chain, scope, param) 24 | return chain unless options = periscope_options[scope] 25 | 26 | method = periscope_method(scope, options) 27 | values = periscope_values(param, options) 28 | 29 | if periscope_ignore?(values.first, options) 30 | chain 31 | else 32 | options[:boolean] ? chain.send(method) : chain.send(method, *values) 33 | end 34 | end 35 | 36 | def periscope_ignore?(value, options) 37 | if options[:ignore_blank] 38 | periscope_blank?(value) 39 | elsif options[:boolean] 40 | !value 41 | end 42 | end 43 | 44 | def periscope_blank?(value) 45 | return true unless value 46 | value.respond_to?(:blank?) ? value.blank? : value.empty? 47 | end 48 | 49 | def periscope_method(scope, options) 50 | options[:method] || [options[:prefix], scope, options[:suffix]].compact.join 51 | end 52 | 53 | def periscope_values(param, options) 54 | options[:parser] ? options[:parser].call(param) : [param] 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/periscope/adapters/active_record.rb: -------------------------------------------------------------------------------- 1 | require "periscope" 2 | require "active_record" 3 | 4 | module Periscope 5 | module Adapters 6 | module ActiveRecord 7 | include Periscope 8 | 9 | private 10 | 11 | def periscope_default_scope 12 | ::ActiveRecord::VERSION::MAJOR >= 4 ? all : scoped 13 | end 14 | end 15 | end 16 | end 17 | 18 | ActiveRecord::Base.extend(Periscope::Adapters::ActiveRecord) 19 | -------------------------------------------------------------------------------- /lib/periscope/adapters/data_mapper.rb: -------------------------------------------------------------------------------- 1 | require "periscope" 2 | require "dm-core" 3 | 4 | module Periscope 5 | module Adapters 6 | module DataMapper 7 | include Periscope 8 | 9 | private 10 | 11 | def periscope_default_scope 12 | all 13 | end 14 | end 15 | end 16 | end 17 | 18 | DataMapper::Model.send(:include, Periscope::Adapters::DataMapper) 19 | -------------------------------------------------------------------------------- /lib/periscope/adapters/mongo_mapper.rb: -------------------------------------------------------------------------------- 1 | require "periscope" 2 | require "mongo_mapper" 3 | 4 | module Periscope 5 | module Adapters 6 | module MongoMapper 7 | extend ActiveSupport::Concern 8 | 9 | module ClassMethods 10 | include Periscope 11 | 12 | private 13 | 14 | def periscope_default_scope 15 | where 16 | end 17 | end 18 | end 19 | end 20 | end 21 | 22 | MongoMapper::Document.plugin(Periscope::Adapters::MongoMapper) 23 | -------------------------------------------------------------------------------- /lib/periscope/adapters/mongoid.rb: -------------------------------------------------------------------------------- 1 | require "periscope" 2 | require "mongoid" 3 | 4 | module Periscope 5 | module Adapters 6 | module Mongoid 7 | include Periscope 8 | 9 | private 10 | 11 | def periscope_default_scope 12 | scoped 13 | end 14 | end 15 | end 16 | end 17 | 18 | Mongoid::Document::ClassMethods.send(:include, Periscope::Adapters::Mongoid) 19 | -------------------------------------------------------------------------------- /periscope-activerecord.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = "periscope-activerecord" 5 | gem.version = "2.1.1" 6 | 7 | gem.author = "Steve Richert" 8 | gem.email = "steve.richert@gmail.com" 9 | gem.summary = "Push your Active Record models' scopes up to the surface" 10 | gem.homepage = "https://github.com/laserlemon/periscope" 11 | gem.license = "MIT" 12 | 13 | gem.add_dependency "activerecord", ">= 3", "< 5.1" 14 | gem.add_dependency "periscope", "~> 2.1.0" 15 | 16 | gem.files = %w( 17 | lib/periscope-activerecord.rb 18 | lib/periscope/adapters/active_record.rb 19 | LICENSE.txt 20 | periscope-activerecord.gemspec 21 | README.md 22 | ) 23 | end 24 | -------------------------------------------------------------------------------- /periscope-data_mapper.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = "periscope-data_mapper" 5 | gem.version = "2.1.0" 6 | 7 | gem.author = "Steve Richert" 8 | gem.email = "steve.richert@gmail.com" 9 | gem.summary = "Push your DataMapper models' scopes up to the surface" 10 | gem.homepage = "https://github.com/laserlemon/periscope" 11 | gem.license = "MIT" 12 | 13 | gem.add_dependency "dm-core", "~> 1.0" 14 | gem.add_dependency "periscope", "~> 2.1.0" 15 | 16 | gem.files = %w( 17 | lib/periscope-data_mapper.rb 18 | lib/periscope/adapters/data_mapper.rb 19 | LICENSE.txt 20 | periscope-data_mapper.gemspec 21 | README.md 22 | ) 23 | end 24 | -------------------------------------------------------------------------------- /periscope-mongo_mapper.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = "periscope-mongo_mapper" 5 | gem.version = "2.1.0" 6 | 7 | gem.author = "Steve Richert" 8 | gem.email = "steve.richert@gmail.com" 9 | gem.summary = "Push your MongoMapper models' scopes up to the surface" 10 | gem.homepage = "https://github.com/laserlemon/periscope" 11 | gem.license = "MIT" 12 | 13 | gem.add_dependency "mongo_mapper", "~> 0.9" 14 | gem.add_dependency "periscope", "~> 2.1.0" 15 | 16 | gem.files = %w( 17 | lib/periscope-mongo_mapper.rb 18 | lib/periscope/adapters/mongo_mapper.rb 19 | LICENSE.txt 20 | periscope-mongo_mapper.gemspec 21 | README.md 22 | ) 23 | end 24 | -------------------------------------------------------------------------------- /periscope-mongoid.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = "periscope-mongoid" 5 | gem.version = "2.1.2" 6 | 7 | gem.author = "Steve Richert" 8 | gem.email = "steve.richert@gmail.com" 9 | gem.summary = "Push your Mongoid models' scopes up to the surface" 10 | gem.homepage = "https://github.com/laserlemon/periscope" 11 | gem.license = "MIT" 12 | 13 | gem.add_dependency "mongoid", ">= 2", "< 5.2" 14 | gem.add_dependency "periscope", "~> 2.1.0" 15 | 16 | gem.files = %w( 17 | lib/periscope-mongoid.rb 18 | lib/periscope/adapters/mongoid.rb 19 | LICENSE.txt 20 | periscope-mongoid.gemspec 21 | README.md 22 | ) 23 | end 24 | -------------------------------------------------------------------------------- /periscope.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = "periscope" 5 | gem.version = "2.1.0" 6 | 7 | gem.author = "Steve Richert" 8 | gem.email = "steve.richert@gmail.com" 9 | gem.summary = "Push your models' scopes up to the surface" 10 | gem.homepage = "https://github.com/laserlemon/periscope" 11 | gem.license = "MIT" 12 | 13 | gem.add_development_dependency "bundler", "~> 1.11" 14 | gem.add_development_dependency "rake", "~> 10.5" 15 | 16 | gem.files = %w( 17 | lib/periscope.rb 18 | LICENSE.txt 19 | periscope.gemspec 20 | README.md 21 | ) 22 | end 23 | -------------------------------------------------------------------------------- /spec/periscope/adapters/active_record_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Periscope::Adapters::ActiveRecord", adapter: "active_record" do 4 | let(:model) { User } 5 | 6 | include_examples "periscopic" 7 | include_examples "databasic" 8 | end 9 | -------------------------------------------------------------------------------- /spec/periscope/adapters/data_mapper_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Periscope::Adapters::DataMapper", adapter: "data_mapper" do 4 | let(:model) { User } 5 | 6 | include_examples "periscopic" 7 | include_examples "databasic" 8 | end 9 | -------------------------------------------------------------------------------- /spec/periscope/adapters/mongo_mapper_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Periscope::Adapters::MongoMapper", adapter: "mongo_mapper" do 4 | let(:model) { User } 5 | 6 | include_examples "periscopic" 7 | include_examples "databasic" 8 | end 9 | -------------------------------------------------------------------------------- /spec/periscope/adapters/mongoid_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Periscope::Adapters::Mongoid", adapter: "mongoid" do 4 | let(:model) { User } 5 | 6 | include_examples "periscopic" 7 | include_examples "databasic" 8 | end 9 | -------------------------------------------------------------------------------- /spec/periscope_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Periscope, adapter: nil do 4 | let(:model) do 5 | klass = MockModel.new 6 | klass.extend(Periscope) 7 | klass 8 | end 9 | 10 | include_examples "periscopic" 11 | end 12 | -------------------------------------------------------------------------------- /spec/shared/databasic.rb: -------------------------------------------------------------------------------- 1 | shared_examples "databasic" do 2 | context "with a database" do 3 | before do 4 | create(:user, gender: "male", salary: 1_000_000) 5 | create(:user, gender: "female", salary: 2_000_000) 6 | create(:user, gender: "female", salary: 3_000_000) 7 | end 8 | 9 | it "returns all records for no params" do 10 | expect(User.periscope.count).to eq(3) 11 | end 12 | 13 | it "respects existing scoping" do 14 | expect(User.female.periscope.count).to eq(2) 15 | end 16 | 17 | it "links to existing scoping" do 18 | User.scope_accessible(:makes) 19 | expect(User.female.periscope(makes: 3_000_000).count).to eq(1) 20 | end 21 | 22 | it "applies named scopes" do 23 | User.scope_accessible(:male, boolean: true) 24 | expect(User.periscope(male: true).count).to eq(1) 25 | end 26 | 27 | it "applies class methods" do 28 | User.scope_accessible(:gender) 29 | expect(User.periscope(gender: "male").count).to eq(1) 30 | end 31 | 32 | it "chains scopes" do 33 | User.scope_accessible(:gender, :makes) 34 | expect(User.periscope(gender: "female", makes: 3_000_000).count).to eq(1) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/shared/periscopic.rb: -------------------------------------------------------------------------------- 1 | shared_examples "periscopic" do 2 | context "without a database" do 3 | before do 4 | allow(model).to receive(:periscope_default_scope).and_return(model) 5 | end 6 | 7 | def expect_scopes(calls) 8 | calls.each do |method, args| 9 | args << no_args if args.empty? 10 | expect(model).to receive(method).with(*args).and_return(model) 11 | end 12 | end 13 | 14 | it "uses the default scope for no params" do 15 | scoped = double(:scoped).as_null_object 16 | expect(model).to receive(:periscope_default_scope).once.and_return(scoped) 17 | expect(model.periscope).to eq(scoped) 18 | end 19 | 20 | it "uses the default scope for empty params" do 21 | scoped = double(:scoped).as_null_object 22 | expect(model).to receive(:periscope_default_scope).once.and_return(scoped) 23 | expect(model.periscope({})).to eq(scoped) 24 | end 25 | 26 | it "ignores protected scopes" do 27 | expect(model).not_to receive(:foo) 28 | model.periscope(foo: "bar") 29 | end 30 | 31 | it "calls accessible scopes with values" do 32 | expect_scopes(foo: ["bar"]) 33 | model.scope_accessible(:foo) 34 | model.periscope(foo: "bar") 35 | end 36 | 37 | it "recognizes accessible scopes, whether string or symbol" do 38 | expect_scopes(foo: ["baz"], bar: ["mitzvah"]) 39 | model.scope_accessible(:foo, "bar") 40 | model.periscope("foo" => "baz", bar: "mitzvah") 41 | end 42 | 43 | it "ignores protected scopes when mixed with accessible scopes" do 44 | expect_scopes(foo: ["baz"]) 45 | expect(model).not_to receive(:bar) 46 | model.scope_accessible(:foo) 47 | model.periscope(foo: "baz", bar: "mitzvah") 48 | end 49 | 50 | it "assigns method names in order to avoid collisions" do 51 | expect_scopes(begins_after: ["1983-05-28"], ends_before: ["2083-05-28"]) 52 | model.scope_accessible(:begin, method: :begins_after) 53 | model.scope_accessible(:end, method: :ends_before) 54 | model.periscope(begin: "1983-05-28", end: "2083-05-28") 55 | end 56 | 57 | it "prefixes method names in order to avoid collisions" do 58 | expect_scopes(scope_for_begin: ["1983-05-28"], scope_for_end: ["2083-05-28"]) 59 | model.scope_accessible(:begin, :end, prefix: "scope_for_") 60 | model.periscope(begin: "1983-05-28", end: "2083-05-28") 61 | end 62 | 63 | it "suffixes method names in order to avoid collisions" do 64 | expect_scopes(begin_scope: ["1983-05-28"], end_scope: ["2083-05-28"]) 65 | model.scope_accessible(:begin, :end, suffix: "_scope") 66 | model.periscope(begin: "1983-05-28", end: "2083-05-28") 67 | end 68 | 69 | it "prefixes and suffixes method names" do 70 | expect_scopes(foo_begin_bar: ["1983-05-28"], foo_end_bar: ["2083-05-28"]) 71 | model.scope_accessible(:begin, :end, prefix: "foo_", suffix: "_bar") 72 | model.periscope(begin: "1983-05-28", end: "2083-05-28") 73 | end 74 | 75 | it "allows multiple scope_accessible calls" do 76 | expect_scopes(foo: ["baz"], bar: ["mitzvah"]) 77 | model.scope_accessible(:foo) 78 | model.scope_accessible(:bar) 79 | model.periscope(foo: "baz", bar: "mitzvah") 80 | end 81 | 82 | it "overrides scope_accessible options per call" do 83 | expect_scopes(start: ["1983-05-28"], end_scope: ["2083-05-28"]) 84 | model.scope_accessible(:start, :end, suffix: "_scope") 85 | model.scope_accessible(:start) 86 | model.periscope(start: "1983-05-28", end: "2083-05-28") 87 | end 88 | 89 | it "allows custom parameter parsing via proc" do 90 | expect_scopes(foo: ["BAR"]) 91 | model.scope_accessible(:foo, parser: proc { |p| [p.upcase] }) 92 | model.periscope(foo: "bar") 93 | end 94 | 95 | it "allows custom parameter parsing via custom parser" do 96 | expect_scopes(foo: ["BAR"]) 97 | parser = double(:parser).as_null_object 98 | expect(parser).to receive(:call).once.with("bar").and_return(["BAR"]) 99 | model.scope_accessible(:foo, parser: parser) 100 | model.periscope(foo: "bar") 101 | end 102 | 103 | it "allows parameter exclusion for argument-less scopes" do 104 | expect_scopes(foo: []) 105 | model.scope_accessible(:foo, boolean: true) 106 | model.periscope(foo: "bar") 107 | end 108 | 109 | it "allows accessible scope exclusion given a falsey param" do 110 | expect(model).not_to receive(:foo) 111 | model.scope_accessible(:foo, boolean: true) 112 | model.periscope(foo: nil) 113 | end 114 | 115 | it "allows accessible scope exclusion given a falsey parsed value" do 116 | expect(model).not_to receive(:foo) 117 | model.scope_accessible(:foo, boolean: true, parser: proc { [nil] }) 118 | model.periscope(foo: "bar") 119 | end 120 | 121 | it "allows accessible scope exclusion given a blank param" do 122 | expect(model).not_to receive(:foo) 123 | model.scope_accessible(:foo, ignore_blank: true) 124 | model.periscope(foo: nil) 125 | model.periscope(foo: "") 126 | model.periscope(foo: []) 127 | model.periscope(foo: {}) 128 | end 129 | 130 | it "calls accessible scopes with values when ignoring blank params" do 131 | expect_scopes(foo: ["bar"]) 132 | model.scope_accessible(:foo, ignore_blank: true) 133 | model.periscope(foo: "bar") 134 | end 135 | 136 | it "allows accessible boolean scope exclusion given a blank param" do 137 | expect(model).not_to receive(:foo) 138 | model.scope_accessible(:foo, boolean: true, ignore_blank: true) 139 | model.periscope(foo: nil) 140 | model.periscope(foo: "") 141 | model.periscope(foo: []) 142 | model.periscope(foo: {}) 143 | end 144 | 145 | it "calls accessible boolean scopes when ignoring blank params" do 146 | expect_scopes(foo: []) 147 | model.scope_accessible(:foo, boolean: true, ignore_blank: true) 148 | model.periscope(foo: "bar") 149 | end 150 | 151 | it "passes along a nil param" do 152 | expect_scopes(foo: [nil]) 153 | model.scope_accessible(:foo) 154 | model.periscope(foo: nil) 155 | end 156 | 157 | it "passes along a nil parsed value" do 158 | expect_scopes(foo: [nil]) 159 | model.scope_accessible(:foo, parser: proc { [nil] }) 160 | model.periscope(foo: "bar") 161 | end 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV["CODECLIMATE_REPO_TOKEN"] 2 | require "codeclimate-test-reporter" 3 | CodeClimate::TestReporter.start 4 | end 5 | 6 | adapter, gemfile = ENV["ADAPTER"], ENV["BUNDLE_GEMFILE"] 7 | adapter ||= gemfile && gemfile[%r(gemfiles/(.*?)/)] && $1 8 | 9 | require "periscope" 10 | 11 | Dir["./spec/shared/*.rb"].shuffle.each { |f| require f } 12 | Dir["./spec/support/*.rb"].shuffle.each { |f| require f } 13 | 14 | if adapter 15 | require "periscope/adapters/#{adapter}" 16 | Dir["./spec/support/adapters/#{adapter}/*.rb"].shuffle.each { |f| require f } 17 | end 18 | 19 | RSpec.configure do |config| 20 | config.filter_run(adapter: adapter) 21 | end 22 | -------------------------------------------------------------------------------- /spec/support/adapters/active_record/connection.rb: -------------------------------------------------------------------------------- 1 | require "active_record" 2 | 3 | ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") 4 | -------------------------------------------------------------------------------- /spec/support/adapters/active_record/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../connection", __FILE__) 2 | 3 | require "database_cleaner" 4 | 5 | DatabaseCleaner["active_record"].strategy = :truncation 6 | 7 | RSpec.configure do |config| 8 | config.before do 9 | DatabaseCleaner.clean 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/adapters/active_record/factory_girl.rb: -------------------------------------------------------------------------------- 1 | require "factory_girl" 2 | 3 | RSpec.configure do |config| 4 | config.include(FactoryGirl::Syntax::Methods) 5 | end 6 | 7 | FactoryGirl.define do 8 | factory :user do 9 | gender { %w(male female)[rand(2)] } 10 | salary { rand(1_000_001) } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/adapters/active_record/reset.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before do 3 | User.instance_variable_set(:@periscope_options, nil) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/adapters/active_record/schema.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../connection", __FILE__) 2 | 3 | ActiveRecord::Migration.verbose = false 4 | 5 | ActiveRecord::Schema.define do 6 | create_table :users, force: true do |t| 7 | t.string :gender 8 | t.integer :salary, default: 0, null: false 9 | t.timestamps 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/adapters/active_record/user.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../schema", __FILE__) 2 | 3 | class User < ActiveRecord::Base 4 | scope :male, proc { where(gender: "male") } 5 | scope :female, proc { where(gender: "female") } 6 | 7 | def self.gender(gender) 8 | where(gender: gender) 9 | end 10 | 11 | def self.makes(salary) 12 | where("salary >= ?", salary) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/support/adapters/data_mapper/connection.rb: -------------------------------------------------------------------------------- 1 | require "dm-core" 2 | require "dm-sqlite-adapter" 3 | 4 | DataMapper.setup(:default, "sqlite::memory:") 5 | -------------------------------------------------------------------------------- /spec/support/adapters/data_mapper/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../connection", __FILE__) 2 | 3 | require "database_cleaner" 4 | 5 | DatabaseCleaner["data_mapper"].strategy = :truncation 6 | 7 | RSpec.configure do |config| 8 | config.before do 9 | DatabaseCleaner.clean 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/adapters/data_mapper/factory_girl.rb: -------------------------------------------------------------------------------- 1 | require "factory_girl" 2 | 3 | RSpec.configure do |config| 4 | config.include(FactoryGirl::Syntax::Methods) 5 | end 6 | 7 | FactoryGirl.define do 8 | factory :user do 9 | gender { %w(male female)[rand(2)] } 10 | salary { rand(1_000_001) } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/adapters/data_mapper/reset.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before do 3 | User.instance_variable_set(:@periscope_options, nil) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/adapters/data_mapper/schema.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../user", __FILE__) 2 | 3 | require "dm-migrations" 4 | 5 | User.auto_migrate! 6 | -------------------------------------------------------------------------------- /spec/support/adapters/data_mapper/user.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../connection", __FILE__) 2 | 3 | class User 4 | include DataMapper::Resource 5 | 6 | property :id, Serial 7 | property :gender, String 8 | property :salary, Integer 9 | 10 | def self.male 11 | all(gender: "male") 12 | end 13 | 14 | def self.female 15 | all(gender: "female") 16 | end 17 | 18 | def self.gender(gender) 19 | all(gender: gender) 20 | end 21 | 22 | def self.makes(salary) 23 | all(:salary.gte => salary) 24 | end 25 | end 26 | 27 | DataMapper.finalize 28 | -------------------------------------------------------------------------------- /spec/support/adapters/mongo_mapper/connection.rb: -------------------------------------------------------------------------------- 1 | # We need to require "active_support" first here because MongoMapper requires 2 | # "active_support/core_ext" without first requiring "active_support". On some 3 | # systems, this causes intermittent test suite failures. 4 | # 5 | # See: https://github.com/rails/rails/issues/14664 6 | # and: https://github.com/mongomapper/mongomapper/commit/64f9fc6 7 | # 8 | require "active_support" 9 | 10 | require "mongo_mapper" 11 | 12 | MongoMapper.connection = Mongo::Connection.new 13 | MongoMapper.database = "periscope_test" 14 | -------------------------------------------------------------------------------- /spec/support/adapters/mongo_mapper/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../connection", __FILE__) 2 | 3 | require "database_cleaner" 4 | 5 | DatabaseCleaner["mongo_mapper"].strategy = :truncation 6 | 7 | RSpec.configure do |config| 8 | config.before do 9 | DatabaseCleaner.clean 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/adapters/mongo_mapper/factory_girl.rb: -------------------------------------------------------------------------------- 1 | require "factory_girl" 2 | 3 | RSpec.configure do |config| 4 | config.include(FactoryGirl::Syntax::Methods) 5 | end 6 | 7 | FactoryGirl.define do 8 | factory :user do 9 | gender { %w(male female)[rand(2)] } 10 | salary { rand(1_000_001) } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/adapters/mongo_mapper/reset.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before do 3 | User.instance_variable_set(:@periscope_options, nil) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/adapters/mongo_mapper/user.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../connection", __FILE__) 2 | 3 | class User 4 | include MongoMapper::Document 5 | 6 | key :gender, String 7 | key :salary, Integer 8 | 9 | scope :male, proc { where(gender: "male") } 10 | scope :female, proc { where(gender: "female") } 11 | 12 | def self.gender(gender) 13 | where(gender: gender) 14 | end 15 | 16 | def self.makes(salary) 17 | where(:salary.gte => salary) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid/connection.rb: -------------------------------------------------------------------------------- 1 | require "mongoid" 2 | require "mongoid/version" 3 | 4 | if Mongoid::VERSION >= "3" 5 | Mongoid.connect_to("periscope_test") 6 | else 7 | Mongoid.master = Mongo::Connection.new.db("periscope_test") 8 | end 9 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../connection", __FILE__) 2 | 3 | require "database_cleaner" 4 | 5 | DatabaseCleaner["mongoid"].strategy = :truncation 6 | 7 | RSpec.configure do |config| 8 | config.before do 9 | DatabaseCleaner.clean 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid/factory_girl.rb: -------------------------------------------------------------------------------- 1 | require "factory_girl" 2 | 3 | RSpec.configure do |config| 4 | config.include(FactoryGirl::Syntax::Methods) 5 | end 6 | 7 | FactoryGirl.define do 8 | factory :user do 9 | gender { %w(male female)[rand(2)] } 10 | salary { rand(1_000_001) } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid/reset.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before do 3 | User.instance_variable_set(:@periscope_options, nil) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid/user.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../connection", __FILE__) 2 | 3 | class User 4 | include Mongoid::Document 5 | 6 | field :gender, type: String 7 | field :salary, type: Integer 8 | 9 | scope :male, proc { where(gender: "male") } 10 | scope :female, proc { where(gender: "female") } 11 | 12 | def self.gender(gender) 13 | where(gender: gender) 14 | end 15 | 16 | def self.makes(salary) 17 | where(:salary.gte => salary) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/mock_model.rb: -------------------------------------------------------------------------------- 1 | class MockModel 2 | def called_methods 3 | @called_methods ||= {} 4 | end 5 | 6 | def method_missing(method, *args) 7 | called_methods[method] = args 8 | self 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/random.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.order = "random" 3 | end 4 | --------------------------------------------------------------------------------