friendly 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -1
- data/.gitignore +3 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTORS.md +6 -0
- data/README.md +93 -1
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/friendly.gemspec +32 -2
- data/lib/friendly.rb +4 -0
- data/lib/friendly/associations.rb +7 -0
- data/lib/friendly/associations/association.rb +34 -0
- data/lib/friendly/associations/set.rb +37 -0
- data/lib/friendly/attribute.rb +37 -11
- data/lib/friendly/boolean.rb +6 -2
- data/lib/friendly/document.rb +93 -1
- data/lib/friendly/named_scope.rb +17 -0
- data/lib/friendly/scope.rb +100 -0
- data/lib/friendly/scope_proxy.rb +45 -0
- data/lib/friendly/sequel_monkey_patches.rb +0 -1
- data/lib/friendly/table_creator.rb +11 -6
- data/lib/friendly/uuid.rb +5 -0
- data/spec/config.yml.example +7 -0
- data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
- data/spec/integration/has_many_spec.rb +18 -0
- data/spec/integration/named_scope_spec.rb +34 -0
- data/spec/integration/scope_chaining_spec.rb +22 -0
- data/spec/integration/table_creator_spec.rb +17 -5
- data/spec/spec_helper.rb +19 -6
- data/spec/unit/associations/association_spec.rb +57 -0
- data/spec/unit/associations/set_spec.rb +43 -0
- data/spec/unit/attribute_spec.rb +41 -0
- data/spec/unit/document_spec.rb +47 -0
- data/spec/unit/named_scope_spec.rb +16 -0
- data/spec/unit/scope_proxy_spec.rb +44 -0
- data/spec/unit/scope_spec.rb +113 -0
- metadata +39 -2
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
require "active_support/core_ext"
|
3
|
+
|
4
|
+
describe "Chaining scopes together" do
|
5
|
+
describe "then calling #all" do
|
6
|
+
before do
|
7
|
+
User.all(:name => "Quagmire").each { |q| q.destroy }
|
8
|
+
@users = (0...10).map do |i|
|
9
|
+
User.create :name => "Quagmire",
|
10
|
+
:created_at => i.hours.ago
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "queries using a combination of both scopes" do
|
15
|
+
User.named_quagmire.recent.all.should == @users.slice(0, 3)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "gives scopes on the right priority" do
|
19
|
+
User.named_joe.named_quagmire.first.name.should == "Quagmire"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -7,16 +7,20 @@ describe "Creating the tables for a model" do
|
|
7
7
|
|
8
8
|
def self.table_name; "stuffs"; end
|
9
9
|
|
10
|
-
attribute :name,
|
10
|
+
attribute :name, String
|
11
|
+
attribute :user_id, Friendly::UUID
|
11
12
|
|
12
13
|
indexes [:name, :created_at]
|
14
|
+
indexes :user_id
|
13
15
|
end
|
14
16
|
|
15
17
|
@klass.create_tables!
|
16
|
-
@schema
|
17
|
-
@table
|
18
|
-
@index_schema
|
19
|
-
@index_table
|
18
|
+
@schema = Friendly.db.schema(:stuffs)
|
19
|
+
@table = Hash[*@schema.map { |s| [s.first, s.last] }.flatten]
|
20
|
+
@index_schema = Friendly.db.schema(:index_stuffs_on_name_and_created_at)
|
21
|
+
@index_table = Hash[*@index_schema.map { |s| [s.first, s.last] }.flatten]
|
22
|
+
@id_idx_schema = Friendly.db.schema(:index_stuffs_on_user_id)
|
23
|
+
@id_index_table = Hash[*@id_idx_schema.map { |s| [s.first, s.last] }.flatten]
|
20
24
|
end
|
21
25
|
|
22
26
|
after do
|
@@ -44,6 +48,14 @@ describe "Creating the tables for a model" do
|
|
44
48
|
@index_table[:id][:primary_key].should be_true
|
45
49
|
end
|
46
50
|
|
51
|
+
it "knows how to create an index for a field of a custom type" do
|
52
|
+
@id_index_table.keys.length.should == 2
|
53
|
+
@id_index_table[:user_id][:db_type].should == "binary(16)"
|
54
|
+
@id_index_table[:user_id][:primary_key].should be_true
|
55
|
+
@id_index_table[:id][:db_type].should == "binary(16)"
|
56
|
+
@id_index_table[:id][:primary_key].should be_true
|
57
|
+
end
|
58
|
+
|
47
59
|
it "doesn't raise if the tables already exist" do
|
48
60
|
lambda {
|
49
61
|
@klass.create_tables!
|
data/spec/spec_helper.rb
CHANGED
@@ -7,13 +7,14 @@ require 'rubygems'
|
|
7
7
|
require 'spec'
|
8
8
|
require 'spec/autorun'
|
9
9
|
require 'sequel'
|
10
|
-
require 'json'
|
10
|
+
require 'json/pure'
|
11
11
|
gem 'jferris-mocha'
|
12
12
|
require 'mocha'
|
13
13
|
require 'memcached'
|
14
14
|
require 'friendly'
|
15
15
|
|
16
|
-
|
16
|
+
config = YAML.load(File.read(File.dirname(__FILE__) + "/config.yml"))['test']
|
17
|
+
Friendly.configure config
|
17
18
|
$db = Friendly.db
|
18
19
|
Sequel::MySQL.default_engine = "InnoDB"
|
19
20
|
|
@@ -32,13 +33,25 @@ Friendly.cache = Friendly::Memcached.new($cache)
|
|
32
33
|
class User
|
33
34
|
include Friendly::Document
|
34
35
|
|
35
|
-
attribute :name,
|
36
|
-
attribute :age,
|
37
|
-
attribute :happy,
|
38
|
-
attribute :sad,
|
36
|
+
attribute :name, String
|
37
|
+
attribute :age, Integer
|
38
|
+
attribute :happy, Friendly::Boolean, :default => true
|
39
|
+
attribute :sad, Friendly::Boolean, :default => false
|
40
|
+
attribute :friend, Friendly::UUID
|
39
41
|
|
42
|
+
indexes :happy
|
43
|
+
indexes :friend
|
40
44
|
indexes :name
|
41
45
|
indexes :name, :created_at
|
46
|
+
|
47
|
+
named_scope :named_joe, :name => "Joe"
|
48
|
+
named_scope :named_quagmire, :name => "Quagmire"
|
49
|
+
named_scope :recent, :order! => :created_at.desc,
|
50
|
+
:limit! => 3
|
51
|
+
|
52
|
+
has_many :addresses
|
53
|
+
has_many :addresses_override, :class_name => "Address",
|
54
|
+
:foreign_key => :user_id
|
42
55
|
end
|
43
56
|
|
44
57
|
User.create_tables!
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path("../../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe "Friendly::Associations::Association" do
|
4
|
+
before do
|
5
|
+
@owner_klass = stub(:name => "User")
|
6
|
+
@klass = stub
|
7
|
+
# FIXME: ugh.
|
8
|
+
String.any_instance.stubs(:constantize).returns(@klass)
|
9
|
+
@assoc_klass = Friendly::Associations::Association
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "with defaults" do
|
13
|
+
before do
|
14
|
+
@association = @assoc_klass.new(@owner_klass, :addresses)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has a default klass of name.classify.constantize" do
|
18
|
+
@association.klass.should == @klass
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has a foreign_key of owner_klass.name.singularize + '_id'" do
|
22
|
+
@association.foreign_key.should == :user_id
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns a scope on klass of {:foreign_key => document.id}" do
|
26
|
+
@scope = stub
|
27
|
+
@klass.stubs(:scope).with(:user_id => 42).returns(@scope)
|
28
|
+
|
29
|
+
@association.scope(stub(:id => 42)).should == @scope
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "with overridden attributes" do
|
34
|
+
before do
|
35
|
+
@klass = stub
|
36
|
+
@class_name = "SomeOtherClass"
|
37
|
+
@class_name.stubs(:constantize).returns(@klass)
|
38
|
+
@association = @assoc_klass.new @owner_klass, :whatever,
|
39
|
+
:class_name => @class_name,
|
40
|
+
:foreign_key => :other_id
|
41
|
+
end
|
42
|
+
|
43
|
+
it "uses the overridden class_name to get the class" do
|
44
|
+
@association.klass.should == @klass
|
45
|
+
end
|
46
|
+
|
47
|
+
it "uses the overridden foreign_key" do
|
48
|
+
@association.foreign_key.should == :other_id
|
49
|
+
end
|
50
|
+
|
51
|
+
it "uses the override foreign_key in the scope" do
|
52
|
+
@scope = stub
|
53
|
+
@klass.stubs(:scope).with(:other_id => 42).returns(@scope)
|
54
|
+
@association.scope(stub(:id => 42)).should == @scope
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path("../../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe "Friendly::Associations::Set" do
|
4
|
+
before do
|
5
|
+
@klass = Class.new
|
6
|
+
@association_klass = stub
|
7
|
+
@set = Friendly::Associations::Set.new(@klass, @association_klass)
|
8
|
+
@assoc = stub
|
9
|
+
@association_klass.stubs(:new).
|
10
|
+
with(@klass, :my_awesome_association, {}).returns(@assoc)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "adding an association" do
|
14
|
+
before do
|
15
|
+
@set.add(:my_awesome_association)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "creates an association and adds it to its hash by name" do
|
19
|
+
@set.associations[:my_awesome_association].should == @assoc
|
20
|
+
end
|
21
|
+
|
22
|
+
it "adds an instance method to the klass through which to acces the assoc" do
|
23
|
+
@doc = @klass.new
|
24
|
+
@scope = stub
|
25
|
+
@klass.stubs(:association_set).returns(@set)
|
26
|
+
@assoc.stubs(:scope).with(@doc).returns(@scope)
|
27
|
+
@doc.my_awesome_association.should == @scope
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can return the association by name" do
|
32
|
+
@set.add(:my_awesome_association)
|
33
|
+
@set.get(:my_awesome_association).should == @assoc
|
34
|
+
end
|
35
|
+
|
36
|
+
it "provides the scope for an association by name" do
|
37
|
+
@doc = stub
|
38
|
+
@scope = stub
|
39
|
+
@assoc.stubs(:scope).with(@doc).returns(@scope)
|
40
|
+
@set.add(:my_awesome_association)
|
41
|
+
@set.get_scope(:my_awesome_association, @doc).should == @scope
|
42
|
+
end
|
43
|
+
end
|
data/spec/unit/attribute_spec.rb
CHANGED
@@ -61,4 +61,45 @@ describe "Friendly::Attribute" do
|
|
61
61
|
it "has a default value even if it's false" do
|
62
62
|
@false.default.should be_false
|
63
63
|
end
|
64
|
+
|
65
|
+
describe "registering a type" do
|
66
|
+
before do
|
67
|
+
@klass = Class.new
|
68
|
+
Friendly::Attribute.register_type(@klass, "binary(16)") { |t| t.to_i }
|
69
|
+
end
|
70
|
+
|
71
|
+
after { Friendly::Attribute.deregister_type(@klass) }
|
72
|
+
|
73
|
+
it "tells Attribute about the sql_type" do
|
74
|
+
Friendly::Attribute.sql_type(@klass).should == "binary(16)"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "registers the conversion method" do
|
78
|
+
Friendly::Attribute.new(@klass, :something, @klass).convert("1").should == 1
|
79
|
+
end
|
80
|
+
|
81
|
+
it "is custom_type?(@klass)" do
|
82
|
+
Friendly::Attribute.should be_custom_type(@klass)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "deregistering a type" do
|
87
|
+
before do
|
88
|
+
@klass = Class.new
|
89
|
+
Friendly::Attribute.register_type(@klass, "whatever") {}
|
90
|
+
Friendly::Attribute.deregister_type(@klass)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "removes the converter method" do
|
94
|
+
Friendly::Attribute.converters.should_not be_has_key(@klass)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "removes the sql_type" do
|
98
|
+
Friendly::Attribute.sql_types.should_not be_has_key(@klass)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "is not custom_type?(@klass)" do
|
102
|
+
Friendly::Attribute.should_not be_custom_type(@klass)
|
103
|
+
end
|
104
|
+
end
|
64
105
|
end
|
data/spec/unit/document_spec.rb
CHANGED
@@ -307,5 +307,52 @@ describe "Friendly::Document" do
|
|
307
307
|
@collection.should have_received(:replace).with(@docs)
|
308
308
|
end
|
309
309
|
end
|
310
|
+
|
311
|
+
describe "creating a named_scope" do
|
312
|
+
before do
|
313
|
+
@scope_proxy = stub(:add_named => nil)
|
314
|
+
@klass.scope_proxy = @scope_proxy
|
315
|
+
@klass.named_scope(:by_name, :order => :name)
|
316
|
+
end
|
317
|
+
|
318
|
+
it "asks the named_scope_set to add it" do
|
319
|
+
@klass.scope_proxy.should have_received(:add_named).
|
320
|
+
with(:by_name, :order => :name)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
describe "Document.has_named_scope?" do
|
325
|
+
it "delegates to the scope_proxy" do
|
326
|
+
@scope_proxy = stub
|
327
|
+
@scope_proxy.stubs(:has_named_scope?).with(:whatever).returns(true)
|
328
|
+
@klass.scope_proxy = @scope_proxy
|
329
|
+
@klass.has_named_scope?(:whatever).should be_true
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe "accessing an ad-hoc scope" do
|
334
|
+
before do
|
335
|
+
@scope = stub
|
336
|
+
@scope_proxy = stub
|
337
|
+
@scope_proxy.stubs(:ad_hoc).with(:order! => :name).returns(@scope)
|
338
|
+
@klass.scope_proxy = @scope_proxy
|
339
|
+
end
|
340
|
+
|
341
|
+
it "asks the named_scope_set to add it" do
|
342
|
+
@klass.scope(:order! => :name).should == @scope
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
describe "adding an association" do
|
347
|
+
before do
|
348
|
+
@assoc_set = stub(:add => nil)
|
349
|
+
@klass.association_set = @assoc_set
|
350
|
+
@klass.has_many :addresses
|
351
|
+
end
|
352
|
+
|
353
|
+
it "asks the association set to add it" do
|
354
|
+
@assoc_set.should have_received(:add).with(:addresses, {})
|
355
|
+
end
|
356
|
+
end
|
310
357
|
end
|
311
358
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe "Friendly::NamedScope" do
|
4
|
+
before do
|
5
|
+
@klass = stub
|
6
|
+
@scope = stub
|
7
|
+
@scope_klass = stub
|
8
|
+
@parameters = {:name => "James"}
|
9
|
+
@scope_klass.stubs(:new).with(@klass, @parameters).returns(@scope)
|
10
|
+
@named_scope = Friendly::NamedScope.new(@klass, @parameters, @scope_klass)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "provides scope instances with the given parameters" do
|
14
|
+
@named_scope.scope.should == @scope
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe "Friendly::ScopeProxy" do
|
4
|
+
before do
|
5
|
+
@klass = Class.new
|
6
|
+
@scope = stub
|
7
|
+
@scope_klass = stub
|
8
|
+
@scope_proxy = Friendly::ScopeProxy.new(@klass, @scope_klass)
|
9
|
+
@params = {:order! => :created_at.desc}
|
10
|
+
@scope_klass.stubs(:new).with(@klass, @params).returns(@scope)
|
11
|
+
@klass.stubs(:scope_proxy).returns(@scope_proxy)
|
12
|
+
@scope_proxy.add_named(:recent, @params)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns false for has_named_scope if it doesnt have one by that name" do
|
16
|
+
@scope_proxy.should_not be_has_named_scope(:whatever)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "adding a scope" do
|
20
|
+
it "adds a scope by that name to the set" do
|
21
|
+
@scope_proxy.get(:recent).should == @params
|
22
|
+
end
|
23
|
+
|
24
|
+
it "adds a method to the klass that returns an instance of the scope" do
|
25
|
+
@klass.recent.should == @scope
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns true for #has_named_scope?(name)" do
|
29
|
+
@scope_proxy.should be_has_named_scope(:recent)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "getting an instance of a scope" do
|
34
|
+
it "instantiates Scope" do
|
35
|
+
@scope_proxy.get_instance(:recent).should == @scope
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "accessing an ad_hoc scope" do
|
40
|
+
it "instantiates Scope" do
|
41
|
+
@scope_proxy.ad_hoc(@params).should == @scope
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe "Friendly::Scope" do
|
4
|
+
before do
|
5
|
+
@klass = stub
|
6
|
+
@scope_parameters = {:name => "Quagmire", :order! => :created_at.desc}
|
7
|
+
@scope = Friendly::Scope.new(@klass, @scope_parameters)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#all" do
|
11
|
+
before do
|
12
|
+
@documents = stub
|
13
|
+
end
|
14
|
+
|
15
|
+
it "delegates to klass with the scope parameters" do
|
16
|
+
@klass.stubs(:all).with(@scope_parameters).returns(@documents)
|
17
|
+
@scope.all.should == @documents
|
18
|
+
end
|
19
|
+
|
20
|
+
it "merges additional parameters" do
|
21
|
+
merged = @scope_parameters.merge(:name => "Joe")
|
22
|
+
@klass.stubs(:all).with(merged).returns(@documents)
|
23
|
+
@scope.all(:name => "Joe").should == @documents
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#first" do
|
28
|
+
before do
|
29
|
+
@document = stub
|
30
|
+
end
|
31
|
+
|
32
|
+
it "delegates to klass with the scope parameters" do
|
33
|
+
@klass.stubs(:first).with(@scope_parameters).returns(@document)
|
34
|
+
@scope.first.should == @document
|
35
|
+
end
|
36
|
+
|
37
|
+
it "merges additional parameters" do
|
38
|
+
merged = @scope_parameters.merge(:name => "Joe")
|
39
|
+
@klass.stubs(:first).with(merged).returns(@document)
|
40
|
+
@scope.first(:name => "Joe").should == @document
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#paginate" do
|
45
|
+
before do
|
46
|
+
@documents = stub
|
47
|
+
end
|
48
|
+
|
49
|
+
it "delegates to klass with the scope parameters" do
|
50
|
+
@klass.stubs(:paginate).with(@scope_parameters).returns(@documents)
|
51
|
+
@scope.paginate.should == @documents
|
52
|
+
end
|
53
|
+
|
54
|
+
it "merges additional parameters" do
|
55
|
+
merged = @scope_parameters.merge(:name => "Joe")
|
56
|
+
@klass.stubs(:paginate).with(merged).returns(@documents)
|
57
|
+
@scope.paginate(:name => "Joe").should == @documents
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#build" do
|
62
|
+
it "instantiates klass with the scope parameters (minus modifiers)" do
|
63
|
+
@doc = stub
|
64
|
+
@klass.stubs(:new).with(:name => "Quagmire").returns(@doc)
|
65
|
+
@scope.build.should == @doc
|
66
|
+
end
|
67
|
+
|
68
|
+
it "merges additional parameters" do
|
69
|
+
@doc = stub
|
70
|
+
@klass.stubs(:new).with(:name => "Fred").returns(@doc)
|
71
|
+
@scope.build(:name => "Fred").should == @doc
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#create" do
|
76
|
+
it "delegates to klass#create with the scope parameters (minus modifiers)" do
|
77
|
+
@doc = stub
|
78
|
+
@klass.stubs(:create).with(:name => "Quagmire").returns(@doc)
|
79
|
+
@scope.create.should == @doc
|
80
|
+
end
|
81
|
+
|
82
|
+
it "merges additional parameters" do
|
83
|
+
@doc = stub
|
84
|
+
@klass.stubs(:create).with(:name => "Fred").returns(@doc)
|
85
|
+
@scope.create(:name => "Fred").should == @doc
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "chaining another named scope" do
|
90
|
+
before do
|
91
|
+
@recent_params = {:order! => :created_at.desc}
|
92
|
+
@recent = Friendly::Scope.new(@klass, @recent_params)
|
93
|
+
@klass.stubs(:has_named_scope?).with(:recent).returns(true)
|
94
|
+
@klass.stubs(:recent).returns(@recent)
|
95
|
+
@scope_params = {:name => "Joe"}
|
96
|
+
@scope = Friendly::Scope.new(@klass, @scope_params)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "responds to the other scope method" do
|
100
|
+
@scope.should be_respond_to(:recent)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "creates a new scope that merges the two scopes together" do
|
104
|
+
merged_parameters = @scope_params.merge(@recent_params)
|
105
|
+
@scope.recent.parameters.should == merged_parameters
|
106
|
+
end
|
107
|
+
|
108
|
+
it "gives precedence to scopes on the right" do
|
109
|
+
@quagmire = Friendly::Scope.new(@klass, :name => "Quagmire")
|
110
|
+
(@scope + @quagmire).parameters[:name].should == "Quagmire"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|