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.
@@ -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...
@@ -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'
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH << 'lib'
2
+ require 'scopify'
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