friendly 0.3.5 → 0.4.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/.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
|