scopify 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +56 -0
- data/Rakefile +20 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/scopify.rb +36 -0
- data/lib/scopify/scope.rb +74 -0
- data/scopify.gemspec +48 -0
- data/spec/scopify_spec.rb +103 -0
- data/spec/spec_helper.rb +2 -0
- metadata +71 -0
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
Add named scopes and chainable scopes to any Object / Model.
|
2
|
+
|
3
|
+
- As gem: ` sudo gem install scopify `
|
4
|
+
- As Rails plugin: ` rails plugin install git://github.com/grosser/scopify.git `
|
5
|
+
|
6
|
+
Usage
|
7
|
+
=====
|
8
|
+
|
9
|
+
### scoped
|
10
|
+
Create a scope on the fly.
|
11
|
+
MyDBWrapper.scoped(:limit => 10).scoped(:order => "something").all(:offset => 1)
|
12
|
+
--> MyDBWrapper.all receives: {:limit => 10, :order => "something", :offset => 1}
|
13
|
+
|
14
|
+
### scope
|
15
|
+
Create named scopes, with options, lambdas or other scopes
|
16
|
+
class MyDBWrapper
|
17
|
+
include Scopify
|
18
|
+
scope :good, :conditions => {:good => true}
|
19
|
+
scope :okay, good.scoped(:conditions => {:goodness => [1,2,3]}
|
20
|
+
scope :goodness, lambda{|factor| {:conditions => {:goodness => factor}} }
|
21
|
+
end
|
22
|
+
|
23
|
+
MyDBWrapper.good.first
|
24
|
+
MyDBWrapper.good.goodness(3).first
|
25
|
+
|
26
|
+
### scope_to_hash
|
27
|
+
Roll your own condition composing.
|
28
|
+
# not good ?
|
29
|
+
MyDBWrapper.scoped(:order => 'a').scoped(:order => 'b).all --> {:order => "a, b"}
|
30
|
+
|
31
|
+
# roll your own !
|
32
|
+
class MyDBWrapper
|
33
|
+
def self.scope_to_hash(options)
|
34
|
+
options.map do |key, values|
|
35
|
+
value = case key
|
36
|
+
when :limit, :offset then values.min
|
37
|
+
when :order then values.join(' AND ')
|
38
|
+
else values.join(', ')
|
39
|
+
end
|
40
|
+
[key, value]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# better now !
|
46
|
+
MyDBWrapper.scoped(:order => 'a').scoped(:order => 'b).all --> {:order => "a AND b"}
|
47
|
+
|
48
|
+
### first
|
49
|
+
When calling first on and scope, `:limit => 1` will be added
|
50
|
+
MyDBWrapper.scoped(:limit => 1).all == MyDBWrapper.first
|
51
|
+
|
52
|
+
Author
|
53
|
+
======
|
54
|
+
[Michael Grosser](http://pragmatig.wordpress.com)
|
55
|
+
grosser.michael@gmail.com
|
56
|
+
Hereby placed under public domain, do what you want, just do not hold me accountable...
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
task :default => :spec
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
project_name = 'scopify'
|
8
|
+
|
9
|
+
Jeweler::Tasks.new do |gem|
|
10
|
+
gem.name = project_name
|
11
|
+
gem.summary = "Add named scopes and scoped to any Object / Model."
|
12
|
+
gem.email = "grosser.michael@gmail.com"
|
13
|
+
gem.homepage = "http://github.com/grosser/#{project_name}"
|
14
|
+
gem.authors = ["Michael Grosser"]
|
15
|
+
end
|
16
|
+
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
|
20
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'scopify'
|
data/lib/scopify.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'scopify/scope'
|
2
|
+
|
3
|
+
module Scopify
|
4
|
+
VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:extend, ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def scoped(options)
|
12
|
+
Scope.build(self, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def scope(name, options)
|
16
|
+
# give access to current options inside of evaled method
|
17
|
+
meta_class = (class << self; self; end)
|
18
|
+
meta_class.send(:define_method, "#{name}_scope_options") do
|
19
|
+
options
|
20
|
+
end
|
21
|
+
|
22
|
+
class_eval <<-CODE
|
23
|
+
def self.#{name}(*args)
|
24
|
+
options = #{name}_scope_options
|
25
|
+
if options.is_a?(Proc)
|
26
|
+
scoped(options.call(*args))
|
27
|
+
elsif options.is_a?(Scope)
|
28
|
+
options
|
29
|
+
else
|
30
|
+
scoped(options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
CODE
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Scopify
|
2
|
+
class Scope
|
3
|
+
def initialize(base, options)
|
4
|
+
@base = base
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def scope_options
|
9
|
+
@options
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.build(base, options)
|
13
|
+
# :limit => 1 --> :limit => [1]
|
14
|
+
options = options.inject({}){|h,kv| h[kv[0]]||=[]; h[kv[0]] << kv[1]; h}
|
15
|
+
new(base, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def scoped(options)
|
19
|
+
merged = @options.dup
|
20
|
+
if options.is_a?(Scope)
|
21
|
+
# merge in raw options e.g. :limit => [1, 2]
|
22
|
+
options.scope_options.each do |k,v|
|
23
|
+
old = merged[k] || []
|
24
|
+
merged[k] = v
|
25
|
+
old.each{|x| merged[k] << x }
|
26
|
+
end
|
27
|
+
else
|
28
|
+
# merge in a normal hash e.g. :limit => 1
|
29
|
+
merged = @options.dup
|
30
|
+
options.each do |k,v|
|
31
|
+
merged[k] ||= []
|
32
|
+
merged[k] << v
|
33
|
+
end
|
34
|
+
end
|
35
|
+
self.class.new(@base, merged)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_hash
|
39
|
+
result = if @base.respond_to?(:scope_to_hash)
|
40
|
+
@base.scope_to_hash(@options)
|
41
|
+
else
|
42
|
+
@options.map do |k,v|
|
43
|
+
result = case k
|
44
|
+
when :limit, :offset then v.min
|
45
|
+
when :order then v * ', '
|
46
|
+
else v
|
47
|
+
end
|
48
|
+
[k, result]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if result.is_a?(Hash)
|
53
|
+
result
|
54
|
+
else
|
55
|
+
# convert array to hash
|
56
|
+
result.inject({}){|h, kv| h[kv[0]] = kv[1]; h}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_missing(method_name, *args, &block)
|
61
|
+
if @base.respond_to?("#{method_name}_scope_options")
|
62
|
+
# the method we call is a scope, continue chaining
|
63
|
+
scope = @base.send(method_name, *args)
|
64
|
+
scope.scoped(self)
|
65
|
+
else
|
66
|
+
# the method we call is a normal method, flatten everything
|
67
|
+
options = (args.first||{})
|
68
|
+
options = options.merge(:limit => 1) if method_name.to_sym == :first
|
69
|
+
options = scoped(options).to_hash
|
70
|
+
@base.send(method_name, options, &block)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/scopify.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{scopify}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Grosser"]
|
12
|
+
s.date = %q{2010-05-13}
|
13
|
+
s.email = %q{grosser.michael@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"README.md"
|
16
|
+
]
|
17
|
+
s.files = [
|
18
|
+
"README.md",
|
19
|
+
"Rakefile",
|
20
|
+
"VERSION",
|
21
|
+
"init.rb",
|
22
|
+
"lib/scopify.rb",
|
23
|
+
"lib/scopify/scope.rb",
|
24
|
+
"scopify.gemspec",
|
25
|
+
"spec/scopify_spec.rb",
|
26
|
+
"spec/spec_helper.rb"
|
27
|
+
]
|
28
|
+
s.homepage = %q{http://github.com/grosser/scopify}
|
29
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
30
|
+
s.require_paths = ["lib"]
|
31
|
+
s.rubygems_version = %q{1.3.6}
|
32
|
+
s.summary = %q{Add named scopes and scoped to any Object / Model.}
|
33
|
+
s.test_files = [
|
34
|
+
"spec/scopify_spec.rb",
|
35
|
+
"spec/spec_helper.rb"
|
36
|
+
]
|
37
|
+
|
38
|
+
if s.respond_to? :specification_version then
|
39
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
40
|
+
s.specification_version = 3
|
41
|
+
|
42
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
43
|
+
else
|
44
|
+
end
|
45
|
+
else
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
class T1
|
4
|
+
include Scopify
|
5
|
+
|
6
|
+
def self.foo(options)
|
7
|
+
options
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.first(options)
|
11
|
+
options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class T2
|
16
|
+
include Scopify
|
17
|
+
|
18
|
+
def self.foo(options)
|
19
|
+
options
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.scope_to_hash(options)
|
23
|
+
options.map do |k,v|
|
24
|
+
[k, v[0]+v[1]]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe Scopify do
|
30
|
+
describe :scoped do
|
31
|
+
it "returns a new scope" do
|
32
|
+
T1.scoped({}).class.should == Scopify::Scope
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can call anything on scope to reach base" do
|
36
|
+
T1.scoped({:limit => 1}).foo.should == {:limit => 1}
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can call anything giving additional options" do
|
40
|
+
T1.scoped({:limit => 1}).foo(:offset => 1).should == {:limit => 1, :offset => 1}
|
41
|
+
end
|
42
|
+
|
43
|
+
it "adds limit => 1 to first queries" do
|
44
|
+
T1.scoped({:order => 'FOO'}).first.should == {:limit => 1, :order => 'FOO'}
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can stack" do
|
48
|
+
T1.scoped(:limit => 1).scoped(:order => 'X').foo.should == {:limit => 1, :order => 'X'}
|
49
|
+
end
|
50
|
+
|
51
|
+
it "overwrites limit with the minimum" do
|
52
|
+
T1.scoped(:limit => 1).scoped(:limit => 2).foo.should == {:limit => 1}
|
53
|
+
T1.scoped(:limit => 2).scoped(:limit => 1).foo.should == {:limit => 1}
|
54
|
+
end
|
55
|
+
|
56
|
+
it "overwrites offset with the minimum" do
|
57
|
+
T1.scoped(:offset => 1).scoped(:offset => 2).foo.should == {:offset => 1}
|
58
|
+
T1.scoped(:offset => 2).scoped(:offset => 1).foo.should == {:offset => 1}
|
59
|
+
end
|
60
|
+
|
61
|
+
it "can use custom scope_to_hash" do
|
62
|
+
T2.scoped(:offset => 1).scoped(:offset => 2).foo.should == {:offset => 3}
|
63
|
+
end
|
64
|
+
|
65
|
+
it "does not mess with arrays" do
|
66
|
+
T2.scoped(:x => [[1]]).scoped(:x => [[2]]).scope_options.should == {:x => [[[1]], [[2]]]}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe :scope do
|
71
|
+
it "adds a named scope" do
|
72
|
+
T1.scope(:yyy, :limit => 1)
|
73
|
+
T1.yyy.foo.should == {:limit => 1}
|
74
|
+
end
|
75
|
+
|
76
|
+
it "can add a scoped scope" do
|
77
|
+
T1.scope(:xxx, :limit => 1)
|
78
|
+
T1.scope(:xxx2, T1.xxx.scoped(:offset => 1))
|
79
|
+
T1.xxx2.foo.should == {:limit => 1, :offset => 1}
|
80
|
+
end
|
81
|
+
|
82
|
+
it "can add scope with arguments" do
|
83
|
+
T1.scope(:aaa, lambda{|a| {:limit => a}})
|
84
|
+
T1.aaa(1).foo.should == {:limit => 1}
|
85
|
+
end
|
86
|
+
|
87
|
+
it "can stack scopes by name" do
|
88
|
+
T1.scope(:bbb, :limit => 1)
|
89
|
+
T1.scope(:bbb2, :offset => 1)
|
90
|
+
T1.bbb.bbb2.foo.should == {:limit => 1, :offset => 1}
|
91
|
+
end
|
92
|
+
|
93
|
+
it "keeps oder when stacking by name" do
|
94
|
+
T1.scope(:ccc, :order => 'a')
|
95
|
+
T1.scope(:ccc2, :order => 'b')
|
96
|
+
T1.ccc2.ccc.foo.should == {:order => 'b, a'}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "has a VERSION" do
|
101
|
+
Scopify::VERSION.should =~ /^\d+\.\d+\.\d+$/
|
102
|
+
end
|
103
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scopify
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Michael Grosser
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-13 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: grosser.michael@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.md
|
29
|
+
files:
|
30
|
+
- README.md
|
31
|
+
- Rakefile
|
32
|
+
- VERSION
|
33
|
+
- init.rb
|
34
|
+
- lib/scopify.rb
|
35
|
+
- lib/scopify/scope.rb
|
36
|
+
- scopify.gemspec
|
37
|
+
- spec/scopify_spec.rb
|
38
|
+
- spec/spec_helper.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/grosser/scopify
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --charset=UTF-8
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.3.6
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Add named scopes and scoped to any Object / Model.
|
69
|
+
test_files:
|
70
|
+
- spec/scopify_spec.rb
|
71
|
+
- spec/spec_helper.rb
|