magic_scopes 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
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.
data/README.rdoc ADDED
@@ -0,0 +1,55 @@
1
+ = MagicScopes
2
+
3
+ MagicScopes is the scopes generator for ActiveRecord.
4
+
5
+ It adds magic_scopes method (and nothing else) to ActiveRecord::Base.
6
+
7
+ It depends on ActiveRecord and ActiveSupport and can be used without Rails.
8
+ MagicScopes module should be included manually in the latter case.
9
+
10
+ It also has support for the StateMachine gem and can generate scopes for states.
11
+
12
+
13
+ == MagicScopes can generate these scopes:
14
+
15
+ * <b>with</b> and <b>without</b> options will generate scopes such as <b>with_rating</b>, <b>without_rating</b> that will look for non null and null values, respectively. For state attributes thee redefine corresponding state_machine scopes so they can be called without parameters to check against null and non null. (You can use it in the normal way too, of course). These scopes can be used with boolean, state, integer, decimal, time, date, datetime, float and string attributes. For integer, decimal, time, date, datetime and string attributes value(s) can be passed to the scope so the check will be performed against passed values (not against null/non null vakues). (ex.: with_rating(1,2,3), without_rating([5,4,3]))
16
+ * <b>is</b> and <b>not</b> options will generate scopes such as <b>published</b>, <b>not_published</b> ("is" is the one option that is not reflected in the name of the generated scope) that look for true and and false or null values for boolean attributes and for the specified state and for state that is not the specifieed one no null for state attributes.
17
+ * <b>eq</b> (stands for equal), <b>ne</b> (not equal), <b>gt</b> (greater than), <b>gte</b> (greater than or equal), <b>lt</b> (lesser than), <b>lte</b> (lesser than or equal) options generate scopes with corresponding suffixes and accept list of values or array as parameteters. Options names are self explanatory. These scopes can be used with integer, decimal, time, date and datetime attributes. lt and gt options can be used with float attributes.
18
+ * for string attributes <b>like</b> and <b>ilike</b> options can be specified which generate scopes like <b>title_like</b>, <b>title_ilike</b> which accept list or array of arguments and look for values like the specified ones, case sensitive and insensitive, respectively.
19
+ * <b>by</b> and <b>by_desc</b> options can be used with integer, decimal, time, date, datetime, float and string attributes and generate scopes like <b>by_created_at</b> and <b>by_created_at_desc</b>, which sort by created at asc and created at desc, respectively.
20
+ * <b>for</b> and <b>not_for</b> scopes can be used with belongs_to associations (including polymorphic ones) and generate scopes like <b>for_user</b>, <b>not_for_commentable</b>. They can accept list or array of associations. Also, hash(es) with id and type keys can be specified for polymorphic associations.
21
+
22
+
23
+ == Usage examples
24
+
25
+ * Call to magic_scopes without parameters will generate all possible scopes for all attributes, belongs_to associations and states. By specifying parameters you tell magic_scopes to generate only the scopes you need.
26
+ magic_scopes
27
+
28
+ * With specified attributes it will generate all possible scopes for title and bogus attributes.
29
+ magic_scopes :title, :bogus
30
+
31
+ * You can specify in (stands for include) and ex (for exclude) options, so:
32
+ * it will generate with and by scopes for all suitable attributes when in option is specified.
33
+ magic_scopes in: %w(with by)
34
+ * It will generate generate all possible scopes except with and by ones when ex option is specified.
35
+ magic_scopes ex: %w(with by)
36
+ * It will generate with and by scopes for all suitable attributes in specified list when both attributes list and in options are cpecified:
37
+ magic_scopes :title, :bogus, in: %w(with by)
38
+ * You can also override scopes that should be generated per attribute:
39
+ magic_scopes :title, :rating, bogus: :by_desc, in: %w(with by)
40
+ It will generate with and by scopes for title and rating attributes and by_desc scope for bogus attribute.
41
+
42
+ * All options accept strings, symbols and arrays as arguments.
43
+
44
+
45
+ == Standard scopes
46
+
47
+ In addition, some standard scopes can be specified using :std option, they are:
48
+ asc and sorted sort by id asc, desc and recent sort by id desc and random sorts in random order.
49
+ ex.: magic_scopes :title, :bogus, in: %w(with without), std: %w(recent rendom)
50
+ magic_scopes generate all possible standard scopes unless std option is specified.
51
+
52
+ MagicScopes is thoroughly tested and ready for production.
53
+ It works with ActiveRecord >= 3.0 and ruby 1.9. (1.8 is not supported).
54
+
55
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'MagicScopes'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ Bundler::GemHelper.install_tasks
24
+
25
+ APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
26
+ load 'rails/tasks/engine.rake'
27
+
28
+ require 'rspec/core/rake_task'
29
+ RSpec::Core::RakeTask.new(:spec) do |spec|
30
+ spec.pattern = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ desc 'Default: Run all specs.'
34
+ task default: :spec
@@ -0,0 +1,28 @@
1
+ require 'magic_scopes/standard_scopes'
2
+ require 'magic_scopes/scopes_builder'
3
+ require 'magic_scopes/scopes_generators/mixins/order_scopes'
4
+ require 'magic_scopes/scopes_generators/mixins/comparison_scopes'
5
+ require 'magic_scopes/scopes_generators/mixins/equality_scopes'
6
+ require 'magic_scopes/scopes_generators/mixins/presence_scopes'
7
+ require 'magic_scopes/scopes_generators/base'
8
+ require 'magic_scopes/scopes_generators/boolean'
9
+ require 'magic_scopes/scopes_generators/integer'
10
+ require 'magic_scopes/scopes_generators/string'
11
+ require 'magic_scopes/scopes_generators/float'
12
+ require 'magic_scopes/scopes_generators/association'
13
+ require 'magic_scopes/scopes_generators/state'
14
+ require 'magic_scopes/version'
15
+ require 'magic_scopes/railtie' if defined?(Rails)
16
+
17
+
18
+ module MagicScopes
19
+ extend ActiveSupport::Concern
20
+
21
+ module ClassMethods
22
+ def magic_scopes(*attrs)
23
+ builder = ScopesBuilder.new(self, *attrs)
24
+ builder.generate_standard_scopes
25
+ builder.generate_scopes
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ require 'rails/railtie'
2
+
3
+ module MagicScopes
4
+ class Railtie < Rails::Railtie
5
+ initializer 'magic_scopes.extend_active_record_base' do |app|
6
+ ActiveSupport.on_load(:active_record) do
7
+ send(:include, MagicScopes)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,203 @@
1
+ module MagicScopes
2
+ class ScopesBuilder
3
+
4
+ include MagicScopes::StandardScopes
5
+
6
+ STANDARD_OPTIONS = [:std, :in, :ex]
7
+
8
+ MAGIC_SCOPES = {
9
+ boolean: [:is, :not, :with, :without],
10
+ integer: [:with, :without, :eq, :ne, :gt, :gte, :lt, :lte, :by, :by_desc],
11
+ float: [:with, :without, :lt, :gt, :by, :by_desc],
12
+ string: [:with, :without, :eq, :ne, :by, :by_desc, :like, :not_like, :ilike, :not_ilike],
13
+ association: [:for, :not_for]
14
+ }
15
+ MAGIC_SCOPES[:state] = MAGIC_SCOPES[:boolean]
16
+ MAGIC_SCOPES[:decimal] = MAGIC_SCOPES[:time] = MAGIC_SCOPES[:datetime] = MAGIC_SCOPES[:date] = MAGIC_SCOPES[:integer]
17
+ MAGIC_SCOPES[:text] = MAGIC_SCOPES[:string]
18
+
19
+ delegate :scope, :order, :columns_hash, :reflections, :state_machines, to: :@model
20
+
21
+ def initialize(model, *attrs)
22
+ @model = model
23
+ @options = attrs.extract_options!.symbolize_keys
24
+ check_options
25
+ @attributes_with_scopes = extract_attributes_with_scopes
26
+ @attributes = make_attributes(attrs)
27
+ @is_attributes_passed = attrs.present? || @attributes_with_scopes.present?
28
+ @needed_scopes = make_needed_scopes
29
+ end
30
+
31
+ def generate_standard_scopes
32
+ (@options[:std] || STANDARD_SCOPES).each { |scope_type| send("#{scope_type}_scope") }
33
+ end
34
+
35
+ def generate_scopes
36
+ @attributes.inject({}) { |hsh, attr| hsh[attr] = generate_scopes_for_attr(attr, @needed_scopes); hsh }.merge(
37
+ @attributes_with_scopes.inject({}) { |hsh, (attr, attr_scopes)| hsh[attr] = generate_scopes_for_attr(attr, attr_scopes); hsh }
38
+ )
39
+ end
40
+
41
+
42
+ private
43
+
44
+ def generate_scopes_for_attr(attr, scopes)
45
+ scopes.inject([]) do |ar, scope_info|
46
+ scope_info = Array.wrap(scope_info)
47
+ if has_scope_generator_for?(attr, scope_info[0])
48
+ generate_scope_for(attr, scope_info[0], scope_info[1] != scope_info[0] ? scope_info[1] : nil)
49
+ ar << scope_info[0]
50
+ end
51
+ ar
52
+ end
53
+ end
54
+
55
+ def check_options
56
+ @options.assert_valid_keys(*valid_options)
57
+
58
+ if @options[:in] && @options[:ex]
59
+ raise ArgumentError, "In(clude) and ex(clude) options can not be specified simultaneously"
60
+ end
61
+
62
+ if @options[:std] && option = @options[:std].find { |opt| STANDARD_SCOPES.exclude?(opt.to_sym) }
63
+ raise ArgumentError, "Unknown option #{option} passed to magic_scopes#std"
64
+ end
65
+ end
66
+
67
+ def extract_attributes_with_scopes
68
+ attributes_with_scopes = @options.inject({}) do |hsh, (option_key, options)|
69
+ unless option_key.in?(STANDARD_OPTIONS)
70
+ hsh[option_key] = unless options.is_a?(Hash)
71
+ Array.wrap(@options.delete(option_key)).inject({}) { |hsh, option| hsh[option.to_sym] = option.to_sym; hsh }
72
+ else
73
+ options
74
+ end
75
+ end
76
+ hsh
77
+ end
78
+ extract_states_from_attrs!(attributes_with_scopes)
79
+
80
+ wrong_scope = nil
81
+ if wrong_attr = attributes_with_scopes.find do |attr, scopes_hash|
82
+ wrong_scope = scopes_hash.find { |scope_type, _| scope_types(attr).exclude?(scope_type.to_sym) }
83
+ end
84
+ raise ArgumentError, "Unknown scope #{wrong_scope} for attribute #{wrong_attr[0]} passed to magic_scopes"
85
+ end
86
+
87
+ attributes_with_scopes
88
+ end
89
+
90
+ def valid_options
91
+ @valid_options ||= (STANDARD_OPTIONS + columns_hash.keys + reflections.keys).map(&:to_sym)
92
+ end
93
+
94
+ def scope_types(*attrs)
95
+ filtered_scopes = if attrs.empty?
96
+ MAGIC_SCOPES
97
+ else
98
+ needed_attr_types = attrs.map { |attr| attrs_with_types[attr] }
99
+ MAGIC_SCOPES.select { |k, _| k.in?(needed_attr_types) }
100
+ end
101
+ filtered_scopes.values.flatten.uniq
102
+ end
103
+
104
+ def make_attributes(attrs)
105
+ attributes = if attrs.empty?
106
+ all_possible_attributes - @attributes_with_scopes.keys
107
+ else
108
+ extract_states_from_attrs(attrs.map(&:to_sym))
109
+ end
110
+
111
+ if wrong_attr = attributes.find { |attr| all_possible_attributes.exclude?(attr) }
112
+ raise ActiveRecord::UnknownAttributeError, "Unknown attribute #{wrong_attr} passed to magic_scopes"
113
+ end
114
+ if attr = attributes.find { |attr| @attributes_with_scopes.keys.include?(attr) }
115
+ raise ArgumentError, "Attribute #{attr} specified using both list and hash"
116
+ end
117
+ attributes
118
+ end
119
+
120
+ def make_needed_scopes
121
+ scopes_options = @options[:in] || @options[:ex]
122
+ scopes_options = if scopes_options
123
+ scopes_options = Array.wrap(scopes_options).map(&:to_sym)
124
+ if wrong_scope = scopes_options.find { |scope_type| scope_types.exclude?(scope_type) }
125
+ raise ArgumentError, "Unknown scope #{wrong_scope} passed to magic_scopes"
126
+ end
127
+ scopes_options
128
+ end
129
+
130
+ needed_scopes = if @options[:in]
131
+ scopes_options
132
+ elsif @options[:ex]
133
+ scope_types(*@attributes) - scopes_options
134
+ else
135
+ scope_types
136
+ end
137
+
138
+ if scopes_options && @is_attributes_passed && wrong_scope = check_for_wrong_scope(needed_scopes)
139
+ raise ArgumentError, "Can not build scope #{wrong_scope} for all passed attributes"
140
+ end
141
+
142
+ needed_scopes
143
+ end
144
+
145
+ def check_for_wrong_scope(scopes)
146
+ scopes.find { |scope_type| @attributes.any? { |attr| !has_scope_generator_for?(attr, scope_type) } }
147
+ end
148
+
149
+ def generate_scope_for(attr, scope_type, scope_name = nil)
150
+ type = type_for_attr(attr)
151
+ "MagicScopes::#{type.to_s.classify}ScopesGenerator".constantize.instance(@model, attr).send(scope_type, scope_name)
152
+ end
153
+
154
+ def has_scope_generator_for?(attr, scope_type)
155
+ scope_type.in?(MAGIC_SCOPES[type_for_attr(attr)])
156
+ end
157
+
158
+ def type_for_attr(attr)
159
+ attrs_with_types[attr]
160
+ end
161
+
162
+ def all_possible_attributes
163
+ @all_possible_attributes ||= extract_states_from_attrs(columns_hash.keys.map(&:to_sym) + reflections.keys)
164
+ end
165
+
166
+ def extract_states_from_attrs(attrs)
167
+ if defined?(StateMachine)
168
+ machines = state_machines.keys
169
+ if attrs.is_a?(Array)
170
+ attrs += machines.inject([]) do |ar, sm_key|
171
+ ar += state_machines[sm_key].states.map(&:name).compact if sm_key.in?(attrs)
172
+ ar
173
+ end
174
+ attrs -= machines
175
+ else
176
+ machines_states = machines.inject(attrs) do |hsh, sm_key|
177
+ state_machines[sm_key].states.map(&:name).compact.each { |state| hsh[state] = attrs[sm_key] } if sm_key.in?(attrs)
178
+ hsh
179
+ end
180
+ attrs.except!(*machines)
181
+ end
182
+ end
183
+ attrs
184
+ end
185
+
186
+ def extract_states_from_attrs!(attrs)
187
+ attrs = extract_states_from_attrs(attrs)
188
+ end
189
+
190
+ def attrs_with_types
191
+ @attrs_with_types ||= all_possible_attributes.inject({}) do |hsh, attr|
192
+ hsh[attr] = if reflections[attr]
193
+ :association
194
+ elsif (type = columns_hash[attr.to_s].try(:type)) && (!defined?(StateMachine) || !state_machines.keys.include?(attr))
195
+ type
196
+ else
197
+ :state
198
+ end
199
+ hsh
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,76 @@
1
+ module MagicScopes
2
+ class AssociationScopesGenerator < ScopesGenerator::Base
3
+
4
+ def initialize(model, attr)
5
+ super
6
+ @model = model
7
+ @attr = attr
8
+ @key = "#{model.table_name}.#{attr.to_s.foreign_key}"
9
+ @type_key = "#{model.table_name}.#{attr}_type"
10
+ end
11
+
12
+ def for(name)
13
+ if @model.reflections[@attr].options[:polymorphic]
14
+ scope name || "for_#{@attr}", ->(*vals) {
15
+ raise ArgumentError, "No argument for for_#{@attr} scope" if vals.empty?
16
+ ids_and_types = vals.map { |v| extract_ids_and_types(v, @attr) }.flatten
17
+ conditions = ids_and_types.map { |hsh| "(#{@key} = ? AND #{@type_key} = ?)" }.join(' OR ')
18
+ where(conditions, *ids_and_types.map(&:values).flatten)
19
+ }
20
+ else
21
+ scope name || "for_#{@attr}", ->(*vals) {
22
+ raise ArgumentError, "No argument for for_#{@attr} scope" if vals.empty?
23
+ where(@key => vals.map { |v| extract_ids(v, @attr) }.flatten )
24
+ }
25
+ end
26
+ end
27
+
28
+ def not_for(name)
29
+ if @model.reflections[@attr.to_sym].options[:polymorphic]
30
+ scope name || "not_for_#{@attr}", ->(*vals) {
31
+ raise ArgumentError, "No argument for for_#{@attr} scope" if vals.empty?
32
+ ids_and_types = vals.map { |v| extract_ids_and_types(v, @attr) }.flatten
33
+ conditions = ids_and_types.map { |hsh| "(#{@key} != ? AND #{@type_key} != ?)" }.join(' AND ')
34
+ where("#{conditions} OR (#{@key} IS NULL OR #{@type_key} IS NULL)", *ids_and_types.map(&:values).flatten)
35
+ }
36
+ else
37
+ scope name || "not_for_#{@attr}", ->(*vals) {
38
+ raise ArgumentError, "No argument for for_#{@attr} scope" if vals.empty?
39
+ ids = vals.map { |v| extract_ids(v, @attr) }.flatten
40
+ conditions = ids.size == 1 ? "!= ?" : "NOT IN (?)"
41
+ where("#{@key} #{conditions} OR #{@key} IS NULL", ids.size == 1 ? ids[0] : ids)
42
+ }
43
+ end
44
+ end
45
+
46
+
47
+ private
48
+
49
+ def extract_ids(val, attr)
50
+ if val.is_a?(Fixnum) || (val.is_a?(String) && val.to_i != 0)
51
+ val
52
+ elsif val.is_a?(ActiveRecord::Base)
53
+ val.id
54
+ elsif val.is_a?(Array) && val.all? { |v| v.is_a?(Fixnum) || (v.is_a?(String) && v.to_i != 0) || v.is_a?(ActiveRecord::Base) }
55
+ val.is_a?(ActiveRecord::Base) ? val.map(&:id) : val
56
+ else
57
+ raise ArgumentError, "Wrong argument type #{attr.class.name} for argument #{attr}"
58
+ end
59
+ end
60
+
61
+ def extract_ids_and_types(val, attr)
62
+ if val.is_a?(ActiveRecord::Base)
63
+ {id: val.id, type: val.class.name}
64
+ elsif val.is_a?(Hash) && val.assert_valid_keys(:id, :type)
65
+ val
66
+ elsif val.is_a?(Array) && val.all? { |v| v.is_a?(ActiveRecord::Base) }
67
+ val.map { |v| {id: v.id, type: v.class.name} }
68
+ elsif val.is_a?(Array) && val.size == 2 && id = val.find { |v| v.respond_to?(:to_i) && v.to_i != 0 }
69
+ val.delete(id)
70
+ {id: id, type: val[0]}
71
+ else
72
+ raise ArgumentError, "Wrong argument type #{attr.class.name} for argument #{attr}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,27 @@
1
+ module MagicScopes
2
+ module ScopesGenerator
3
+ class Base
4
+
5
+ @@instances = {}
6
+
7
+ private_class_method :new
8
+
9
+ def initialize(model, attr)
10
+ @model = model
11
+ @attr = attr
12
+ @key = "#{model.table_name}.#{attr}"
13
+ end
14
+
15
+ def self.instance(model, attr)
16
+ if @@instances[model.name].try(:has_key?, attr)
17
+ @@instances[model.name][attr]
18
+ else
19
+ @@instances[model.name] ||= {}
20
+ @@instances[model.name][attr] = new(model, attr)
21
+ end
22
+ end
23
+
24
+ delegate :scope, :where, :order, to: :@model
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ module MagicScopes
2
+ class BooleanScopesGenerator < ScopesGenerator::Base
3
+
4
+ include PresenceScopes
5
+
6
+ def is(name)
7
+ scope name || @attr, where("#{@key}" => true)
8
+ end
9
+
10
+ def not(name)
11
+ scope name || "not_#{@attr}", where("#{@key}" => [false, nil])
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module MagicScopes
2
+ class FloatScopesGenerator < ScopesGenerator::Base
3
+ include OrderScopes
4
+ include ComparisonScopes
5
+ include PresenceScopes
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ module MagicScopes
2
+ class IntegerScopesGenerator < ScopesGenerator::Base
3
+
4
+ include EqualityScopes
5
+ include OrderScopes
6
+ include ComparisonScopes
7
+
8
+ def gte(name)
9
+ scope name || "#{@attr}_gte", ->(val) { where("#{@key} >= ?", val) }
10
+ end
11
+
12
+ def lte(name)
13
+ scope name || "#{@attr}_lte", ->(val) { where("#{@key} <= ?", val) }
14
+ end
15
+
16
+ end
17
+
18
+ # TODO
19
+ # make something like:
20
+ # %w(integer decimal time datetime date).each do |type|
21
+ # send("#{type.classify}=", NumScopesGenerator)
22
+ # end
23
+ DecimalScopesGenerator = IntegerScopesGenerator
24
+ TimeScopesGenerator = IntegerScopesGenerator
25
+ DatetimeScopesGenerator = IntegerScopesGenerator
26
+ DateScopesGenerator = IntegerScopesGenerator
27
+ end
@@ -0,0 +1,11 @@
1
+ module MagicScopes
2
+ module ComparisonScopes
3
+ def gt(name)
4
+ scope name || "#{@attr}_gt", ->(val){ where("#{@key} > ?", val) }
5
+ end
6
+
7
+ def lt(name)
8
+ scope name || "#{@attr}_lt", ->(val){ where("#{@key} < ?", val) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module MagicScopes
2
+ module EqualityScopes
3
+ def with(name)
4
+ scope name || "with_#{@attr}", eq_scope
5
+ end
6
+
7
+ def eq(name)
8
+ scope name || "#{@attr}_eq", eq_scope
9
+ end
10
+
11
+ def without(name)
12
+ scope name || "without_#{@attr}", where("#{@key} IS NULL")
13
+ end
14
+
15
+ def ne(name)
16
+ scope name || "#{@attr}_ne", ->(*vals) {
17
+ raise ArgumentError, "No argument for for_#{@attr} scope" if vals.empty?
18
+ sql = "#{@key} " << (vals.size == 1 && !vals[0].is_a?(Array) ? '!= ?' : 'NOT IN (?)') << " OR #{@key} IS NULL"
19
+ where(sql, vals.flatten)
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def eq_scope
26
+ @eq_scope ||= ->(*vals) { vals.empty? ? where("#{@key} IS NOT NULL") : where(@key => vals.flatten) }
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ module MagicScopes
2
+ module OrderScopes
3
+ def by(name)
4
+ scope name || "by_#{@attr}", order("#{@attr} ASC")
5
+ end
6
+
7
+ def by_desc(name)
8
+ scope name || "by_#{@attr}_desc", order("#{@attr} DESC")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module MagicScopes
2
+ module PresenceScopes
3
+ def with(name)
4
+ scope name || "with_#{@attr}", where("#{@key} IS NOT NULL")
5
+ end
6
+
7
+ def without(name)
8
+ scope name || "without_#{@attr}", where("#{@key} IS NULL")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ if defined?(StateMachine)
2
+ module MagicScopes
3
+ class StateScopesGenerator < ScopesGenerator::Base
4
+ def initialize(model, state)
5
+ @model = model
6
+ @state = state
7
+ @key = "#{model.table_name}.#{attr}"
8
+ end
9
+
10
+ def is(name)
11
+ scope name || @state, where("#{@key}" => @state)
12
+ end
13
+
14
+ def not(name)
15
+ scope name || "not_#{@state}", where("#{@key} != ? OR #{@key} IS NULL", @state)
16
+ end
17
+
18
+ def with(name)
19
+ @model.instance_eval("undef :with_#{attr}")
20
+ scope name || "with_#{@attr}", ->(*vals) { where(vals.empty? ? "#{@key} IS NOT NULL" : ["#{@key} IN (?)", vals]) }
21
+ end
22
+
23
+ def without(name)
24
+ @model.instance_eval("undef :without_#{attr}")
25
+ scope name || "without_#{@attr}", ->(*vals) { where(vals.empty? ? "#{@key} IS NULL" : ["#{@key} NOT IN (?)", vals]) }
26
+ end
27
+
28
+ private
29
+
30
+ def attr
31
+ @attr ||= @model.state_machines.find { |_, sm| sm.states.map(&:name).include?(@state) }[0]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ module MagicScopes
2
+ class StringScopesGenerator < ScopesGenerator::Base
3
+
4
+ include EqualityScopes
5
+ include OrderScopes
6
+
7
+ def like(name)
8
+ scope name || "#{@attr}_like", ->(*vals) { where(build_query(*vals, "#{@key} LIKE ?", 'OR')) }
9
+ end
10
+
11
+ def not_like(name)
12
+ scope name || "#{@attr}_not_like", ->(*vals) { where(build_query(*vals, "#{@key} NOT LIKE ?", 'AND')) }
13
+ end
14
+
15
+ def ilike(name)
16
+ scope name || "#{@attr}_ilike", ilike_scope
17
+ end
18
+
19
+ def not_ilike(name)
20
+ scope name || "#{@attr}_not_ilike", ilike_scope('NOT')
21
+ end
22
+
23
+ private
24
+
25
+ def build_query(*vals, condition, operator)
26
+ [Array.new(vals.size, condition).join(" #{operator} "), *vals.map { |val| "%#{val}%" }]
27
+ end
28
+
29
+ def ilike_scope(operator = nil)
30
+ conditions = ilike_supported? ? "#{@key} #{operator} ILIKE ?" : "LOWER(#{@key}) #{operator} LIKE ?"
31
+ ->(*vals){ where(build_query(*vals, conditions, operator != 'NOT' ? 'OR' : 'AND')) }
32
+ end
33
+
34
+ def ilike_supported?
35
+ @model.connection.adapter_name == 'PostgreSQL'
36
+ end
37
+ end
38
+
39
+ TextScopesGenerator = StringScopesGenerator
40
+ end
@@ -0,0 +1,31 @@
1
+ module MagicScopes
2
+ module StandardScopes
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ self::STANDARD_SCOPES = [:asc, :sorted, :desc, :recent, :random]
7
+ end
8
+
9
+ private
10
+
11
+ def asc_scope
12
+ scope :asc, order(:id)
13
+ end
14
+
15
+ def sorted_scope
16
+ scope :sorted, order(:id)
17
+ end
18
+
19
+ def desc_scope
20
+ scope :desc, order('id DESC')
21
+ end
22
+
23
+ def recent_scope
24
+ scope :recent, order('id DESC')
25
+ end
26
+
27
+ def random_scope
28
+ scope :random, order('RANDOM()')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module MagicScopes
2
+ VERSION = "1.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: magic_scopes
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dmitry Afanasyev
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '3.2'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: sqlite3
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: state_machine
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec-rails
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: pry-debugger
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: ActiveRecord scopes generators
127
+ email:
128
+ - dimarzio1986@gmail.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - lib/magic_scopes/railtie.rb
134
+ - lib/magic_scopes/scopes_builder.rb
135
+ - lib/magic_scopes/scopes_generators/association.rb
136
+ - lib/magic_scopes/scopes_generators/base.rb
137
+ - lib/magic_scopes/scopes_generators/boolean.rb
138
+ - lib/magic_scopes/scopes_generators/float.rb
139
+ - lib/magic_scopes/scopes_generators/integer.rb
140
+ - lib/magic_scopes/scopes_generators/mixins/comparison_scopes.rb
141
+ - lib/magic_scopes/scopes_generators/mixins/equality_scopes.rb
142
+ - lib/magic_scopes/scopes_generators/mixins/order_scopes.rb
143
+ - lib/magic_scopes/scopes_generators/mixins/presence_scopes.rb
144
+ - lib/magic_scopes/scopes_generators/state.rb
145
+ - lib/magic_scopes/scopes_generators/string.rb
146
+ - lib/magic_scopes/standard_scopes.rb
147
+ - lib/magic_scopes/version.rb
148
+ - lib/magic_scopes.rb
149
+ - MIT-LICENSE
150
+ - Rakefile
151
+ - README.rdoc
152
+ homepage: http://github.com/icrowley
153
+ licenses: []
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ! '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ none: false
166
+ requirements:
167
+ - - ! '>='
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 1.8.24
173
+ signing_key:
174
+ specification_version: 3
175
+ summary: ActiveRecord scopes generators
176
+ test_files: []
177
+ has_rdoc: