wayne-friendly 0.5.1
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 +2 -0
- data/.gitignore +26 -0
- data/APACHE-LICENSE +202 -0
- data/CHANGELOG.md +28 -0
- data/CONTRIBUTORS.md +7 -0
- data/LICENSE +20 -0
- data/README.md +288 -0
- data/Rakefile +68 -0
- data/TODO.md +5 -0
- data/VERSION +1 -0
- data/examples/friendly.yml +7 -0
- data/friendly.gemspec +240 -0
- data/lib/friendly.rb +58 -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 +98 -0
- data/lib/friendly/boolean.rb +10 -0
- data/lib/friendly/cache.rb +24 -0
- data/lib/friendly/cache/by_id.rb +33 -0
- data/lib/friendly/data_store.rb +73 -0
- data/lib/friendly/document.rb +70 -0
- data/lib/friendly/document/associations.rb +50 -0
- data/lib/friendly/document/attributes.rb +114 -0
- data/lib/friendly/document/convenience.rb +41 -0
- data/lib/friendly/document/mixin.rb +15 -0
- data/lib/friendly/document/scoping.rb +66 -0
- data/lib/friendly/document/storage.rb +63 -0
- data/lib/friendly/document_table.rb +56 -0
- data/lib/friendly/index.rb +73 -0
- data/lib/friendly/indexer.rb +50 -0
- data/lib/friendly/memcached.rb +48 -0
- data/lib/friendly/newrelic.rb +6 -0
- data/lib/friendly/query.rb +42 -0
- data/lib/friendly/scope.rb +100 -0
- data/lib/friendly/scope_proxy.rb +43 -0
- data/lib/friendly/sequel_monkey_patches.rb +34 -0
- data/lib/friendly/storage.rb +31 -0
- data/lib/friendly/storage_factory.rb +24 -0
- data/lib/friendly/storage_proxy.rb +111 -0
- data/lib/friendly/table.rb +15 -0
- data/lib/friendly/table_creator.rb +50 -0
- data/lib/friendly/time.rb +14 -0
- data/lib/friendly/translator.rb +33 -0
- data/lib/friendly/uuid.rb +148 -0
- data/lib/tasks/friendly.rake +7 -0
- data/rails/init.rb +3 -0
- data/spec/config.yml.example +7 -0
- data/spec/fakes/data_store_fake.rb +29 -0
- data/spec/fakes/database_fake.rb +12 -0
- data/spec/fakes/dataset_fake.rb +28 -0
- data/spec/fakes/document.rb +18 -0
- data/spec/fakes/serializer_fake.rb +12 -0
- data/spec/fakes/time_fake.rb +12 -0
- data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
- data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
- data/spec/integration/batch_insertion_spec.rb +29 -0
- data/spec/integration/convenience_api_spec.rb +25 -0
- data/spec/integration/count_spec.rb +12 -0
- data/spec/integration/default_value_spec.rb +30 -0
- data/spec/integration/dirty_tracking_spec.rb +43 -0
- data/spec/integration/find_via_cache_spec.rb +101 -0
- data/spec/integration/finder_spec.rb +71 -0
- data/spec/integration/has_many_spec.rb +18 -0
- data/spec/integration/index_spec.rb +57 -0
- data/spec/integration/named_scope_spec.rb +34 -0
- data/spec/integration/offline_indexing_spec.rb +53 -0
- data/spec/integration/pagination_spec.rb +63 -0
- data/spec/integration/scope_chaining_spec.rb +22 -0
- data/spec/integration/table_creator_spec.rb +69 -0
- data/spec/integration/write_through_cache_spec.rb +53 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +105 -0
- data/spec/unit/associations/association_spec.rb +57 -0
- data/spec/unit/associations/set_spec.rb +43 -0
- data/spec/unit/attribute_spec.rb +125 -0
- data/spec/unit/cache_by_id_spec.rb +102 -0
- data/spec/unit/cache_spec.rb +21 -0
- data/spec/unit/data_store_spec.rb +201 -0
- data/spec/unit/document/attributes_spec.rb +130 -0
- data/spec/unit/document_spec.rb +318 -0
- data/spec/unit/document_table_spec.rb +126 -0
- data/spec/unit/friendly_spec.rb +25 -0
- data/spec/unit/index_spec.rb +196 -0
- data/spec/unit/memcached_spec.rb +114 -0
- data/spec/unit/query_spec.rb +104 -0
- data/spec/unit/scope_proxy_spec.rb +44 -0
- data/spec/unit/scope_spec.rb +113 -0
- data/spec/unit/storage_factory_spec.rb +59 -0
- data/spec/unit/storage_proxy_spec.rb +244 -0
- data/spec/unit/translator_spec.rb +91 -0
- data/website/index.html +210 -0
- data/website/scripts/clipboard.swf +0 -0
- data/website/scripts/shBrushAS3.js +61 -0
- data/website/scripts/shBrushBash.js +66 -0
- data/website/scripts/shBrushCSharp.js +67 -0
- data/website/scripts/shBrushColdFusion.js +102 -0
- data/website/scripts/shBrushCpp.js +99 -0
- data/website/scripts/shBrushCss.js +93 -0
- data/website/scripts/shBrushDelphi.js +57 -0
- data/website/scripts/shBrushDiff.js +43 -0
- data/website/scripts/shBrushErlang.js +54 -0
- data/website/scripts/shBrushGroovy.js +69 -0
- data/website/scripts/shBrushJScript.js +52 -0
- data/website/scripts/shBrushJava.js +59 -0
- data/website/scripts/shBrushJavaFX.js +60 -0
- data/website/scripts/shBrushPerl.js +74 -0
- data/website/scripts/shBrushPhp.js +91 -0
- data/website/scripts/shBrushPlain.js +35 -0
- data/website/scripts/shBrushPowerShell.js +76 -0
- data/website/scripts/shBrushPython.js +66 -0
- data/website/scripts/shBrushRuby.js +57 -0
- data/website/scripts/shBrushScala.js +53 -0
- data/website/scripts/shBrushSql.js +68 -0
- data/website/scripts/shBrushVb.js +58 -0
- data/website/scripts/shBrushXml.js +71 -0
- data/website/scripts/shCore.js +30 -0
- data/website/scripts/shLegacy.js +30 -0
- data/website/styles/friendly.css +103 -0
- data/website/styles/help.png +0 -0
- data/website/styles/ie.css +35 -0
- data/website/styles/magnifier.png +0 -0
- data/website/styles/page_white_code.png +0 -0
- data/website/styles/page_white_copy.png +0 -0
- data/website/styles/print.css +29 -0
- data/website/styles/printer.png +0 -0
- data/website/styles/screen.css +257 -0
- data/website/styles/shCore.css +330 -0
- data/website/styles/shThemeDefault.css +173 -0
- data/website/styles/shThemeDjango.css +176 -0
- data/website/styles/shThemeEclipse.css +190 -0
- data/website/styles/shThemeEmacs.css +175 -0
- data/website/styles/shThemeFadeToGrey.css +177 -0
- data/website/styles/shThemeMidnight.css +175 -0
- data/website/styles/shThemeRDark.css +175 -0
- metadata +337 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
|
2
|
+
require 'active_support/core_ext'
|
|
3
|
+
|
|
4
|
+
describe "Paginating" do
|
|
5
|
+
before do
|
|
6
|
+
User.all(:name => "Fred").each { |u| u.destroy }
|
|
7
|
+
@users = (0...15).map do |i|
|
|
8
|
+
User.create(:name => "Fred",
|
|
9
|
+
:created_at => i.hours.ago)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "fetching the nil page" do
|
|
14
|
+
before do
|
|
15
|
+
@found = User.paginate(:name => "Fred",
|
|
16
|
+
:order! => :created_at.desc,
|
|
17
|
+
:page! => nil,
|
|
18
|
+
:per_page! => 5)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "returns the first :per_page results" do
|
|
22
|
+
@found.should == @users.slice(0,5)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "returns an instance of WillPaginate::Collection" do
|
|
26
|
+
@found.should be_instance_of(WillPaginate::Collection)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "fetching a page by number" do
|
|
31
|
+
before do
|
|
32
|
+
@found = User.paginate(:name => "Fred",
|
|
33
|
+
:order! => :created_at.desc,
|
|
34
|
+
:page! => 2,
|
|
35
|
+
:per_page! => 5)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "returns the :per_page results starting at offset :per_page * page" do
|
|
39
|
+
@found.should == @users.slice(5,5)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns an instance of WillPaginate::Collection" do
|
|
43
|
+
@found.should be_instance_of(WillPaginate::Collection)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe "when :page is a string" do
|
|
48
|
+
before do
|
|
49
|
+
@found = User.paginate(:name => "Fred",
|
|
50
|
+
:order! => :created_at.desc,
|
|
51
|
+
:page! => "2",
|
|
52
|
+
:per_page! => 5)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "returns the :per_page results starting at offset :per_page * page" do
|
|
56
|
+
@found.should == @users.slice(5,5)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "returns an instance of WillPaginate::Collection" do
|
|
60
|
+
@found.should be_instance_of(WillPaginate::Collection)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
|
2
|
+
|
|
3
|
+
describe "Creating the tables for a model" do
|
|
4
|
+
before do
|
|
5
|
+
@klass = Class.new do
|
|
6
|
+
include Friendly::Document
|
|
7
|
+
|
|
8
|
+
def self.table_name; "stuffs"; end
|
|
9
|
+
|
|
10
|
+
attribute :name, String
|
|
11
|
+
attribute :user_id, Friendly::UUID
|
|
12
|
+
|
|
13
|
+
indexes [:name, :created_at]
|
|
14
|
+
indexes :user_id
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@klass.create_tables!
|
|
18
|
+
@schema = Friendly.db.schema(:stuffs)
|
|
19
|
+
@unique_id_index = Friendly.db.indexes(:stuffs)
|
|
20
|
+
@table = Hash[*@schema.map { |s| [s.first, s.last] }.flatten]
|
|
21
|
+
@index_schema = Friendly.db.schema(:index_stuffs_on_name_and_created_at)
|
|
22
|
+
@index_table = Hash[*@index_schema.map { |s| [s.first, s.last] }.flatten]
|
|
23
|
+
@id_idx_schema = Friendly.db.schema(:index_stuffs_on_user_id)
|
|
24
|
+
@id_index_table = Hash[*@id_idx_schema.map { |s| [s.first, s.last] }.flatten]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
after do
|
|
28
|
+
Friendly.db.drop_table(:stuffs)
|
|
29
|
+
Friendly.db.drop_table(:index_stuffs_on_name_and_created_at)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "creates a table for the document" do
|
|
33
|
+
@table.keys.length.should == 5
|
|
34
|
+
@table[:added_id][:db_type].should == "int(11)"
|
|
35
|
+
@table[:added_id][:primary_key].should be_true
|
|
36
|
+
@table[:id][:db_type].should == "binary(16)"
|
|
37
|
+
@table[:attributes][:db_type].should == "text"
|
|
38
|
+
@table[:created_at][:db_type].should == "datetime"
|
|
39
|
+
@table[:updated_at][:db_type].should == "datetime"
|
|
40
|
+
|
|
41
|
+
@unique_id_index.keys.length.should == 1
|
|
42
|
+
@unique_id_index[:id][:columns].should == [:id]
|
|
43
|
+
@unique_id_index[:id][:unique].should be_true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "creates a table for each index" do
|
|
47
|
+
@index_table.keys.length.should == 3
|
|
48
|
+
@index_table[:name][:db_type].should == "varchar(255)"
|
|
49
|
+
@index_table[:name][:primary_key].should be_true
|
|
50
|
+
@index_table[:created_at][:db_type].should == "datetime"
|
|
51
|
+
@index_table[:created_at][:primary_key].should be_true
|
|
52
|
+
@index_table[:id][:db_type].should == "binary(16)"
|
|
53
|
+
@index_table[:id][:primary_key].should be_true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "knows how to create an index for a field of a custom type" do
|
|
57
|
+
@id_index_table.keys.length.should == 2
|
|
58
|
+
@id_index_table[:user_id][:db_type].should == "binary(16)"
|
|
59
|
+
@id_index_table[:user_id][:primary_key].should be_true
|
|
60
|
+
@id_index_table[:id][:db_type].should == "binary(16)"
|
|
61
|
+
@id_index_table[:id][:primary_key].should be_true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "doesn't raise if the tables already exist" do
|
|
65
|
+
lambda {
|
|
66
|
+
@klass.create_tables!
|
|
67
|
+
}.should_not raise_error
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
|
2
|
+
|
|
3
|
+
describe "Writing through to cache on create" do
|
|
4
|
+
before do
|
|
5
|
+
@address = Address.create :street => "Spooner"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "writes through to memcache using the model street and guid as cache key" do
|
|
9
|
+
$cache.get("Address/0/#{@address.id.to_guid}").should == @address
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "Writing with a version number" do
|
|
14
|
+
before do
|
|
15
|
+
Address.storage_proxy.caches.clear
|
|
16
|
+
Address.caches_by :id, :version => 1
|
|
17
|
+
@address = Address.create :street => "Spooner"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
after do
|
|
21
|
+
Address.storage_proxy.caches.clear
|
|
22
|
+
Address.caches_by :id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "adds the version number to the cache key" do
|
|
26
|
+
$cache.get("Address/1/#{@address.id.to_guid}").should == @address
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "Writing through to cache on update" do
|
|
31
|
+
before do
|
|
32
|
+
@address = Address.create :street => "Spooner"
|
|
33
|
+
@address.street = "Joe"
|
|
34
|
+
@address.save
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "writes through to memcache using the model street and guid as cache key" do
|
|
38
|
+
$cache.get("Address/0/#{@address.id.to_guid}").street.should == @address.street
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "Writing through to cache on destroy" do
|
|
43
|
+
before do
|
|
44
|
+
@address = Address.create :street => "Spooner"
|
|
45
|
+
@address.destroy
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "removes the object from cache" do
|
|
49
|
+
lambda {
|
|
50
|
+
$cache.get("Address/0/#{@address.id.to_guid}")
|
|
51
|
+
}.should raise_error(Memcached::NotFound)
|
|
52
|
+
end
|
|
53
|
+
end
|
data/spec/spec.opts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color --backtrace
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
3
|
+
Dir[File.expand_path(File.dirname(__FILE__)) + "/fakes/*.rb"].each do |f|
|
|
4
|
+
require f
|
|
5
|
+
end
|
|
6
|
+
require 'rubygems'
|
|
7
|
+
require 'spec'
|
|
8
|
+
require 'spec/autorun'
|
|
9
|
+
require 'sequel'
|
|
10
|
+
require 'json/pure'
|
|
11
|
+
gem 'jferris-mocha'
|
|
12
|
+
require 'mocha'
|
|
13
|
+
require 'memcached'
|
|
14
|
+
require 'friendly'
|
|
15
|
+
|
|
16
|
+
config = YAML.load(File.read(File.dirname(__FILE__) + "/config.yml"))['test']
|
|
17
|
+
Friendly.configure config
|
|
18
|
+
$db = Friendly.db
|
|
19
|
+
Sequel::MySQL.default_engine = "InnoDB"
|
|
20
|
+
|
|
21
|
+
$db.drop_table :users if $db.table_exists?("users")
|
|
22
|
+
$db.drop_table :index_users_on_name if $db.table_exists?("index_users_on_name")
|
|
23
|
+
if $db.table_exists?("index_users_on_name_and_created_at")
|
|
24
|
+
$db.drop_table :index_users_on_name_and_created_at
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
datastore = Friendly::DataStore.new($db)
|
|
28
|
+
Friendly.datastore = datastore
|
|
29
|
+
|
|
30
|
+
$cache = Memcached.new
|
|
31
|
+
Friendly.cache = Friendly::Memcached.new($cache)
|
|
32
|
+
|
|
33
|
+
class User
|
|
34
|
+
include Friendly::Document
|
|
35
|
+
|
|
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
|
|
41
|
+
|
|
42
|
+
indexes :happy
|
|
43
|
+
indexes :friend
|
|
44
|
+
indexes :name
|
|
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
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
User.create_tables!
|
|
58
|
+
|
|
59
|
+
class Address
|
|
60
|
+
include Friendly::Document
|
|
61
|
+
|
|
62
|
+
attribute :user_id, Friendly::UUID
|
|
63
|
+
attribute :street, String
|
|
64
|
+
|
|
65
|
+
indexes :user_id
|
|
66
|
+
indexes :street
|
|
67
|
+
indexes :created_at
|
|
68
|
+
|
|
69
|
+
caches_by :id
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
Address.create_tables!
|
|
73
|
+
|
|
74
|
+
module Mocha
|
|
75
|
+
module API
|
|
76
|
+
def setup_mocks_for_rspec
|
|
77
|
+
mocha_setup
|
|
78
|
+
end
|
|
79
|
+
def verify_mocks_for_rspec
|
|
80
|
+
mocha_verify
|
|
81
|
+
end
|
|
82
|
+
def teardown_mocks_for_rspec
|
|
83
|
+
mocha_teardown
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module Factory
|
|
89
|
+
def row(opts = {})
|
|
90
|
+
{ :id => 1, :created_at => Time.new, :updated_at => Time.new }.merge(opts)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def query(conditions)
|
|
94
|
+
stub(:order => conditions.delete(:order!),
|
|
95
|
+
:limit => conditions.delete(:limit!),
|
|
96
|
+
:preserve_order? => conditions.delete(:preserve_order!),
|
|
97
|
+
:conditions => conditions,
|
|
98
|
+
:offset => conditions.delete(:offset!))
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
Spec::Runner.configure do |config|
|
|
103
|
+
config.mock_with Mocha::API
|
|
104
|
+
config.include Factory
|
|
105
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
|
2
|
+
|
|
3
|
+
describe "Friendly::Attribute" do
|
|
4
|
+
before do
|
|
5
|
+
@klass = Class.new { def will_change(a); end }
|
|
6
|
+
@name = Friendly::Attribute.new(@klass, :name, String)
|
|
7
|
+
@id = Friendly::Attribute.new(@klass, :id, Friendly::UUID)
|
|
8
|
+
@no_type = Friendly::Attribute.new(@klass, :no_type, nil)
|
|
9
|
+
@default = Friendly::Attribute.new(@klass, :default, String, :default => "asdf")
|
|
10
|
+
@false = Friendly::Attribute.new(@klass, :false, String, :default => false)
|
|
11
|
+
@klass.stubs(:attributes).returns({:name => @name,
|
|
12
|
+
:id => @id,
|
|
13
|
+
:default => @default,
|
|
14
|
+
:false => @false})
|
|
15
|
+
@object = @klass.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "creates a getting on klass that notifies it of a change" do
|
|
19
|
+
@object.stubs(:will_change)
|
|
20
|
+
@object.name = "Something"
|
|
21
|
+
@object.should have_received(:will_change).with(:name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "creates a getter on klass" do
|
|
25
|
+
@object.name = "Something"
|
|
26
|
+
@object.name.should == "Something"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "creates an 'attr_was' getter" do
|
|
30
|
+
@object.instance_variable_set(:@name_was, "Joe the Plumber")
|
|
31
|
+
@object.name_was.should == "Joe the Plumber"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "creates an attr_changed? query method" do
|
|
35
|
+
@object.stubs(:attribute_changed?).with(:name).returns(true)
|
|
36
|
+
@object.should be_name_changed
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "typecasts values using the converter function" do
|
|
40
|
+
uuid = Friendly::UUID.new
|
|
41
|
+
@id.typecast(uuid.to_s).should == uuid
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "doesn't typecast values if they are of the right type" do
|
|
45
|
+
uuid = Friendly::UUID.new
|
|
46
|
+
@id.typecast(uuid).should == uuid
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "raises a useful error if it can't typecast" do
|
|
50
|
+
attribute = Friendly::Attribute.new(@klass, :weird, Class)
|
|
51
|
+
lambda {
|
|
52
|
+
attribute.typecast("ASDF")
|
|
53
|
+
}.should raise_error(Friendly::NoConverterExists)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "has a default value of type.new" do
|
|
57
|
+
@id.default.should be_instance_of(Friendly::UUID)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "has a default of nil if the type doesn't respond to :new" do
|
|
61
|
+
Friendly::Attribute.new(@klass, :age, Integer).default.should be_nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "doesn't try to convert when there's no type" do
|
|
65
|
+
@no_type.typecast(true).should == true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "can have a default value" do
|
|
69
|
+
@default.default.should == "asdf"
|
|
70
|
+
@obj = @klass.new
|
|
71
|
+
@default.assign_default_value(@obj)
|
|
72
|
+
@obj.default.should == "asdf"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "has a default value even if it's false" do
|
|
76
|
+
@false.default.should be_false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "knows how to assign its own default" do
|
|
80
|
+
@object = stub(:false= => nil)
|
|
81
|
+
@false.assign_default_value(@object)
|
|
82
|
+
@object.should have_received(:false=).with(false)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe "registering a type" do
|
|
86
|
+
before do
|
|
87
|
+
@klass = Class.new
|
|
88
|
+
Friendly::Attribute.register_type(@klass, "binary(16)") { |t| t.to_i }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
after { Friendly::Attribute.deregister_type(@klass) }
|
|
92
|
+
|
|
93
|
+
it "tells Attribute about the sql_type" do
|
|
94
|
+
Friendly::Attribute.sql_type(@klass).should == "binary(16)"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "registers the conversion method" do
|
|
98
|
+
Friendly::Attribute.new(@klass, :something, @klass).convert("1").should == 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "is custom_type?(@klass)" do
|
|
102
|
+
Friendly::Attribute.should be_custom_type(@klass)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe "deregistering a type" do
|
|
107
|
+
before do
|
|
108
|
+
@klass = Class.new
|
|
109
|
+
Friendly::Attribute.register_type(@klass, "whatever") {}
|
|
110
|
+
Friendly::Attribute.deregister_type(@klass)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "removes the converter method" do
|
|
114
|
+
Friendly::Attribute.converters.should_not be_has_key(@klass)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "removes the sql_type" do
|
|
118
|
+
Friendly::Attribute.sql_types.should_not be_has_key(@klass)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "is not custom_type?(@klass)" do
|
|
122
|
+
Friendly::Attribute.should_not be_custom_type(@klass)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|