├── .rspec ├── lib ├── activerecord-autoscope.rb └── activerecord │ ├── auto_scope │ ├── version.rb │ ├── config.rb │ ├── railtie.rb │ ├── hook_methods.rb │ └── scope_methods.rb │ └── auto_scope.rb ├── .travis.yml ├── bin ├── setup └── console ├── .gitignore ├── Rakefile ├── Gemfile ├── spec ├── activerecord │ └── autoscope_spec.rb └── spec_helper.rb ├── .rubocop.yml ├── LICENSE.txt ├── activerecord-autoscope.gemspec ├── Gemfile.lock └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /lib/activerecord-autoscope.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'activerecord/auto_scope' 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: false 3 | language: ruby 4 | cache: bundler 5 | rvm: 6 | - 2.6.3 7 | before_install: gem install bundler -v 2.0.1 8 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/activerecord/auto_scope/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveRecord 4 | module AutoScope 5 | VERSION = '0.3.0'.freeze 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | # rspec failure tracking 11 | .rspec_status 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in activerecord-autoscope.gemspec 6 | gemspec 7 | 8 | gem 'rubocop' 9 | -------------------------------------------------------------------------------- /spec/activerecord/autoscope_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe ActiveRecord::AutoScope do 4 | it 'has a version number' do 5 | expect(ActiveRecord::AutoScope::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/activerecord/auto_scope/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveRecord 4 | module AutoScope 5 | class Config # :nodoc: 6 | attr_accessor :auto_define_scopes 7 | 8 | def initialize 9 | @auto_define_scopes = true 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | require 'activerecord/autoscope' 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require 'irb' 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /lib/activerecord/auto_scope/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveRecord 4 | module AutoScope 5 | class Railtie < ::Rails::Railtie # :nodoc: 6 | config.after_initialize do 7 | ActiveSupport.on_load :active_record do 8 | ActiveRecord::Base.extend ::ActiveRecord::AutoScope::ScopeMethods 9 | ActiveRecord::Base.prepend ::ActiveRecord::AutoScope::HookMethods 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' 4 | require 'activerecord-autoscope' 5 | 6 | RSpec.configure do |config| 7 | # Enable flags like --only-failures and --next-failure 8 | config.example_status_persistence_file_path = '.rspec_status' 9 | 10 | # Disable RSpec exposing methods globally on `Module` and `main` 11 | config.disable_monkey_patching! 12 | 13 | config.expect_with :rspec do |c| 14 | c.syntax = :expect 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/activerecord/auto_scope.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_record' 4 | 5 | require_relative 'auto_scope/config' 6 | require_relative 'auto_scope/hook_methods' 7 | require_relative 'auto_scope/scope_methods' 8 | require_relative 'auto_scope/version' 9 | 10 | begin 11 | require 'rails' 12 | require_relative 'auto_scope/railtie' 13 | rescue LoadError 14 | nil 15 | end 16 | 17 | module ActiveRecord 18 | module AutoScope # :nodoc: 19 | def self.configure 20 | yield config 21 | end 22 | 23 | def self.config 24 | @config ||= Config.new 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # The behavior of RuboCop can be controlled via the .rubocop.yml 2 | # configuration file. It makes it possible to enable/disable 3 | # certain cops (checks) and to alter their behavior if they accept 4 | # any parameters. The file can be placed either in your home 5 | # directory or in some project directory. 6 | # 7 | # RuboCop will start looking for the configuration file in the directory 8 | # where the inspected file is and continue its way up to the root directory. 9 | # 10 | # See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md 11 | AllCops: 12 | TargetRubyVersion: 2.2 13 | 14 | Metrics: 15 | Enabled: false 16 | 17 | Naming/FileName: 18 | Enabled: false 19 | -------------------------------------------------------------------------------- /lib/activerecord/auto_scope/hook_methods.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveRecord 4 | module AutoScope 5 | module HookMethods # :nodoc: 6 | def self.prepended(child) 7 | child.extend ClassMethods 8 | end 9 | 10 | module ClassMethods # :nodoc: 11 | private 12 | 13 | def method_missing(method_name, *args, &block) 14 | if enable_auto_define_scopes? && enable_auto_scopes! 15 | public_send(method_name, *args, &block) 16 | else 17 | super 18 | end 19 | end 20 | 21 | def respond_to_missing?(method_name, include_private = false) 22 | super 23 | end 24 | 25 | def enable_auto_define_scopes? 26 | ::ActiveRecord::AutoScope.config.auto_define_scopes && !abstract_class? 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Yoshiyuki Hirano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /activerecord-autoscope.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('./lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'activerecord/auto_scope/version' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = 'activerecord-autoscope' 9 | s.version = ActiveRecord::AutoScope::VERSION 10 | s.authors = ['Yoshiyuki Hirano'] 11 | s.email = ['yhirano@me.com'] 12 | s.homepage = 'https://github.com/yhirano55/activerecord-autoscope' 13 | s.summary = %(activerecord-autoscope automatically add useful scopes) 14 | s.description = s.summary 15 | s.license = 'MIT' 16 | s.files = Dir.chdir(File.expand_path('.', __dir__)) do 17 | `git ls-files -z`.split("\x0").reject do |f| 18 | f.match(%r{^(test|spec|features)/}) 19 | end 20 | end 21 | 22 | s.bindir = 'exe' 23 | s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) } 24 | s.require_paths = ['lib'] 25 | 26 | s.required_ruby_version = '>= 2.2.2' 27 | s.required_rubygems_version = '>= 1.8.11' 28 | 29 | s.add_dependency 'activerecord', '>= 5.2' 30 | 31 | s.add_development_dependency 'bundler', '~> 2.0' 32 | s.add_development_dependency 'rake', '~> 10.0' 33 | s.add_development_dependency 'rspec', '~> 3.0' 34 | end 35 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | activerecord-autoscope (0.3.0) 5 | activerecord (>= 5.2) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | activemodel (5.2.3) 11 | activesupport (= 5.2.3) 12 | activerecord (5.2.3) 13 | activemodel (= 5.2.3) 14 | activesupport (= 5.2.3) 15 | arel (>= 9.0) 16 | activesupport (5.2.3) 17 | concurrent-ruby (~> 1.0, >= 1.0.2) 18 | i18n (>= 0.7, < 2) 19 | minitest (~> 5.1) 20 | tzinfo (~> 1.1) 21 | arel (9.0.0) 22 | ast (2.4.0) 23 | concurrent-ruby (1.1.5) 24 | diff-lcs (1.3) 25 | i18n (1.6.0) 26 | concurrent-ruby (~> 1.0) 27 | jaro_winkler (1.5.2) 28 | minitest (5.11.3) 29 | parallel (1.17.0) 30 | parser (2.6.3.0) 31 | ast (~> 2.4.0) 32 | rainbow (3.0.0) 33 | rake (10.5.0) 34 | rspec (3.8.0) 35 | rspec-core (~> 3.8.0) 36 | rspec-expectations (~> 3.8.0) 37 | rspec-mocks (~> 3.8.0) 38 | rspec-core (3.8.0) 39 | rspec-support (~> 3.8.0) 40 | rspec-expectations (3.8.3) 41 | diff-lcs (>= 1.2.0, < 2.0) 42 | rspec-support (~> 3.8.0) 43 | rspec-mocks (3.8.0) 44 | diff-lcs (>= 1.2.0, < 2.0) 45 | rspec-support (~> 3.8.0) 46 | rspec-support (3.8.0) 47 | rubocop (0.68.1) 48 | jaro_winkler (~> 1.5.1) 49 | parallel (~> 1.10) 50 | parser (>= 2.5, != 2.5.1.1) 51 | rainbow (>= 2.2.2, < 4.0) 52 | ruby-progressbar (~> 1.7) 53 | unicode-display_width (>= 1.4.0, < 1.6) 54 | ruby-progressbar (1.10.0) 55 | thread_safe (0.3.6) 56 | tzinfo (1.2.5) 57 | thread_safe (~> 0.1) 58 | unicode-display_width (1.5.0) 59 | 60 | PLATFORMS 61 | ruby 62 | 63 | DEPENDENCIES 64 | activerecord-autoscope! 65 | bundler (~> 2.0) 66 | rake (~> 10.0) 67 | rspec (~> 3.0) 68 | rubocop 69 | 70 | BUNDLED WITH 71 | 2.0.1 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActiveRecord::AutoScope 2 | 3 | This gem automatically defines **scope methods which we often implement** 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'activerecord-autoscope' 11 | ``` 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install activerecord-autoscope 20 | 21 | ## Usage 22 | 23 | This gem automatically defines these scope methods **without configuration** 24 | 25 | These APIs are similar as [Ransack](https://github.com/activerecord-hackery/ransack/)'s one, but these scope methods are simple implementations. 26 | 27 | Please check its [implementation](https://github.com/yhirano55/activerecord-autoscope/blob/master/lib/activerecord/auto_scope/scope_methods.rb) if you're interested in. 28 | 29 | | Type | Scope method | Description | Notes | 30 | | ---- | --------- | ----------- | ----- | 31 | | All | `#{col}_eq(val)` | equal | | 32 | | All | `#{col}_not_eq(val)` | not equal | | 33 | | All | `#{col}_is(val)` | equal | | 34 | | All | `#{col}_is_not(val)` | not equal | | 35 | | All | `#{col}_null` | is null | | 36 | | All | `#{col}_not_null` | is not null | | 37 | | All | `#{col}_present` | not null and not empty | SQL: `col IS NOT NULL AND col != ''` | 38 | | All | `#{col}_blank` | null or empty | SQL: `col IS NULL OR col = ''` | 39 | | All | `#{col}_asc` | ascending order | | 40 | | All | `#{col}_desc` | descending order | | 41 | | `:integer` | `#{col}_gt(val)` | greater than | | 42 | | `:integer` | `#{col}_gteq(val)` | greater than or equal | | 43 | | `:integer` | `#{col}_lt(val)` | less than | | 44 | | `:integer` | `#{col}_lteq(val)` | less than or equal | | 45 | | `:string`, `:text` | `#{col}_start(val)` | starts with | SQL: `col LIKE 'value%'` | 46 | | `:string`, `:text` | `#{col}_not_start(val)` | does not start with | | 47 | | `:string`, `:text` | `#{col}_end(val)` | ends with | SQL: `col LIKE '%value'` | 48 | | `:string`, `:text` | `#{col}_not_end(val)` | does not end with | | 49 | | `:string`, `:text` | `#{col}_cont(val)` | contains | SQL: `col LIKE '%value%'` | 50 | | `:string`, `:text` | `#{col}_not_cont(val)` | does not contain | | 51 | | `:date`, `:datetime` | `#{col}_after(val)` | after | | 52 | | `:date`, `:datetime` | `#{col}_on_or_after(val)` | on or after | | 53 | | `:date`, `:datetime` | `#{col}_before(val)` | before | | 54 | | `:date`, `:datetime` | `#{col}_on_or_before(val)` | on or before | | 55 | | `:date`, `:datetime` | `#{col}_between(range)` | between | | 56 | | `:boolean` | `#{col}` | true | SQL: `col IS true` | 57 | | `:boolean` | `not_#{col}` | false | | 58 | 59 | ### Manually settings 60 | 61 | This gem provides automatically each scopes with `method_missing` If you don't any configuration, you can use it. But if you want to define these scope methods manually, you have to add this initializer file: 62 | 63 | ```ruby 64 | # config/initializers/activerecord_autoscope.rb 65 | ActiveRecord::AutoScope.config.auto_define_scopes = false 66 | ``` 67 | 68 | Then you have to enable inside each models: 69 | 70 | ```ruby 71 | class User < ApplicationRecord 72 | enable_auto_scopes! 73 | end 74 | ``` 75 | 76 | ## License 77 | 78 | [MIT License](https://opensource.org/licenses/MIT) 79 | -------------------------------------------------------------------------------- /lib/activerecord/auto_scope/scope_methods.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveRecord 4 | module AutoScope 5 | module ScopeMethods # :nodoc: 6 | def enable_auto_scopes! 7 | return if abstract_class? 8 | 9 | already_defined_scope = false 10 | 11 | columns.each do |column| 12 | attr_name = column.name 13 | table_name = column.table_name 14 | 15 | # NOTE: if it has already defined methods, return false 16 | if respond_to?("#{attr_name}_eq") && respond_to?("#{attr_name}_not_eq") 17 | already_defined_scope = true 18 | break 19 | end 20 | 21 | scope("#{attr_name}_eq", ->(value) { where(attr_name => value) }) 22 | scope("#{attr_name}_not_eq", ->(value) { where.not(attr_name => value) }) 23 | scope("#{attr_name}_is", ->(value) { where(attr_name => value) }) 24 | scope("#{attr_name}_is_not", ->(value) { where.not(attr_name => value) }) 25 | scope("#{attr_name}_null", -> { where(attr_name => nil) }) 26 | scope("#{attr_name}_not_null", -> { where.not(attr_name => nil) }) 27 | scope("#{attr_name}_present", -> { where("#{table_name}.#{attr_name} IS NOT NULL AND #{table_name}.#{attr_name} != ''") }) 28 | scope("#{attr_name}_blank", -> { where("#{table_name}.#{attr_name} IS NULL OR #{table_name}.#{attr_name} = ''") }) 29 | scope("#{attr_name}_asc", -> { order(attr_name => :asc) }) 30 | scope("#{attr_name}_desc", -> { order(attr_name => :desc) }) 31 | 32 | case column.type 33 | when :integer 34 | scope("#{attr_name}_gt", ->(value) { where("#{table_name}.#{attr_name} > ?", value) }) 35 | scope("#{attr_name}_gteq", ->(value) { where("#{table_name}.#{attr_name} >= ?", value) }) 36 | scope("#{attr_name}_lt", ->(value) { where("#{table_name}.#{attr_name} < ?", value) }) 37 | scope("#{attr_name}_lteq", ->(value) { where("#{table_name}.#{attr_name} <= ?", value) }) 38 | when :string, :text 39 | scope("#{attr_name}_start", ->(value) { where("#{table_name}.#{attr_name} LIKE ?", "#{sanitize_sql_like(value)}%") }) 40 | scope("#{attr_name}_not_start", ->(value) { where("#{table_name}.#{attr_name} NOT LIKE ?", "#{sanitize_sql_like(value)}%") }) 41 | scope("#{attr_name}_end", ->(value) { where("#{table_name}.#{attr_name} LIKE ?", "%#{sanitize_sql_like(value)}") }) 42 | scope("#{attr_name}_not_end", ->(value) { where("#{table_name}.#{attr_name} NOT LIKE ?", "%#{sanitize_sql_like(value)}") }) 43 | scope("#{attr_name}_cont", ->(value) { where("#{table_name}.#{attr_name} LIKE ?", "%#{sanitize_sql_like(value)}%") }) 44 | scope("#{attr_name}_not_cont", ->(value) { where("#{table_name}.#{attr_name} NOT LIKE ?", "%#{sanitize_sql_like(value)}%") }) 45 | when :date, :datetime 46 | scope("#{attr_name}_after", ->(value) { where("#{table_name}.#{attr_name} > ?", value) }) 47 | scope("#{attr_name}_on_or_after", ->(value) { where("#{table_name}.#{attr_name} >= ?", value) }) 48 | scope("#{attr_name}_before", ->(value) { where("#{table_name}.#{attr_name} < ?", value) }) 49 | scope("#{attr_name}_on_or_before", ->(value) { where("#{table_name}.#{attr_name} <= ?", value) }) 50 | scope("#{attr_name}_between", ->(_value) { where(attr_name => format_value) }) 51 | when :boolean 52 | scope(attr_name, -> { where(attr_name => true) }) 53 | scope("not_#{attr_name}", -> { where(attr_name => false) }) 54 | end 55 | end 56 | 57 | !already_defined_scope 58 | end 59 | end 60 | end 61 | end 62 | --------------------------------------------------------------------------------