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
data/.document
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
README.
|
1
|
+
README.md
|
2
2
|
lib/**/*.rb
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Changelog
|
2
|
+
=========
|
3
|
+
|
4
|
+
### 0.4.0 (master)
|
5
|
+
|
6
|
+
* (jamesgolick) Add scope chaining. See the README and the docs for Friendly::Scope.
|
7
|
+
* (jamesgolick) Add has_many association.
|
8
|
+
* (jamesgolick) Add ad-hoc scopes. See the docs for Document.scope.
|
9
|
+
* (jamesgolick) Add named_scope functionality. See the docs for Document.named_scope.
|
10
|
+
* (jeffrafter + jamesgolick) DDL generation now supports custom types. Previously, it would only work with types that Sequel already supported. Now, if you want to register a custom type, use Friendly::Attribute.register_type(klass, sql_type, &conversion_method).
|
11
|
+
|
data/CONTRIBUTORS.md
ADDED
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
Friendly
|
2
|
-
|
2
|
+
========
|
3
3
|
|
4
4
|
### Short Version
|
5
5
|
|
@@ -112,6 +112,98 @@ Currently, only caching by id is supported, but caching of arbitrary indexes is
|
|
112
112
|
|
113
113
|
__We're seeing a 99.8% cache hit rate in production with this code.__
|
114
114
|
|
115
|
+
Scopes
|
116
|
+
======
|
117
|
+
|
118
|
+
### Named Scopes
|
119
|
+
|
120
|
+
It's possible to create a scope that you can refer to by name:
|
121
|
+
|
122
|
+
class Post
|
123
|
+
attribute :author, String
|
124
|
+
|
125
|
+
named_scope :by_james, :author => "James"
|
126
|
+
end
|
127
|
+
|
128
|
+
Calling the scope will provide you with a Friendly:Scope object:
|
129
|
+
|
130
|
+
Post.by_james #=> #<Friendly::Scope>
|
131
|
+
|
132
|
+
That scope object supports a variety of methods. Calling any of the methods is the equivalent of calling those methods directly on Document with the scope's parameters.
|
133
|
+
|
134
|
+
e.g.
|
135
|
+
|
136
|
+
Post.by_james.all == Post.all(:author => "James")
|
137
|
+
Post.by_james.first == Post.first(:author => "James")
|
138
|
+
Post.by_james.paginate # => #<WillPaginate::Collection>
|
139
|
+
Post.by_james.build.name == "James"
|
140
|
+
@post = Post.by_james.create
|
141
|
+
@post.new_record? # => false
|
142
|
+
@post.name # => "James"
|
143
|
+
|
144
|
+
Each of the methods also accepts override parameters. The APIs are the same as on Document.
|
145
|
+
|
146
|
+
Post.by_james.all(:author => "Steve") == Post.all(:author => "Steve")
|
147
|
+
|
148
|
+
You can also compose arbitrary combinations of scopes with simple chaining.
|
149
|
+
|
150
|
+
class Post
|
151
|
+
# ... snip ...
|
152
|
+
named_scope :recent, :order! => :created_at.desc, :limit! => 4
|
153
|
+
indexes :name, :created_at
|
154
|
+
end
|
155
|
+
|
156
|
+
Post.by_james.recent == Post.all(:name => "James",
|
157
|
+
:order! => :created_at.desc,
|
158
|
+
:limit! => 4)
|
159
|
+
|
160
|
+
If two parameters conflict, the right-most scope takes precedence.
|
161
|
+
|
162
|
+
### Ad-hoc Scopes
|
163
|
+
|
164
|
+
You can also create a scope object on the fly:
|
165
|
+
|
166
|
+
Post.scope(:author => "Steve")
|
167
|
+
|
168
|
+
The object you get is identical to the one you get from a named_scope. So, see above for the API.
|
169
|
+
|
170
|
+
Associations
|
171
|
+
============
|
172
|
+
|
173
|
+
Friendly currently only supports has\_many associations.
|
174
|
+
|
175
|
+
Creating a has\_many is as simple as setting up the necessary association and foreign key.
|
176
|
+
|
177
|
+
_Note: Make sure that the target model is indexed on the foreign key. If it isn't, querying the association will raise Friendly::MissingIndex._
|
178
|
+
|
179
|
+
e.g.
|
180
|
+
|
181
|
+
class Post
|
182
|
+
attribute :user_id, Friendly::UUID
|
183
|
+
indexes :user_id
|
184
|
+
end
|
185
|
+
|
186
|
+
class User
|
187
|
+
has_many :posts
|
188
|
+
end
|
189
|
+
|
190
|
+
@user = User.create
|
191
|
+
@post = @user.posts.create
|
192
|
+
@user.posts.all == [@post] # => true
|
193
|
+
|
194
|
+
Friendly defaults the foreign key to class_name_id just like ActiveRecord. It also converts the name of the association to the name of the target class just like ActiveRecord does.
|
195
|
+
|
196
|
+
The biggest difference in semantics between Friendly's has\_many and active\_record's is that Friendly's just returns a Friendly::Scope object. If you want all the associated objects, you have to call #all to get them.
|
197
|
+
|
198
|
+
You can also use any other Friendly::Scope method like scope chaining.
|
199
|
+
|
200
|
+
# note: the Post.recent scope is defined in the above section
|
201
|
+
@user.posts.recent == Post.all(:user_id => @user.id,
|
202
|
+
:order! => :created_at.desc,
|
203
|
+
:limit! => 4)
|
204
|
+
|
205
|
+
See the section above or the Friendly::Scope docs for more details.
|
206
|
+
|
115
207
|
Installation
|
116
208
|
============
|
117
209
|
|
data/Rakefile
CHANGED
@@ -13,6 +13,7 @@ begin
|
|
13
13
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
14
|
gem.add_development_dependency "cucumber", ">= 0"
|
15
15
|
gem.add_development_dependency "jferris-mocha"
|
16
|
+
gem.add_development_dependency "memcached"
|
16
17
|
gem.add_dependency "sequel", ">= 3.7.0"
|
17
18
|
gem.add_dependency "json"
|
18
19
|
gem.add_dependency "activesupport"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/friendly.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{friendly}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["James Golick"]
|
12
|
-
s.date = %q{2009-12-
|
12
|
+
s.date = %q{2009-12-22}
|
13
13
|
s.description = %q{}
|
14
14
|
s.email = %q{jamesgolick@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -20,6 +20,8 @@ Gem::Specification.new do |s|
|
|
20
20
|
".document",
|
21
21
|
".gitignore",
|
22
22
|
"APACHE-LICENSE",
|
23
|
+
"CHANGELOG.md",
|
24
|
+
"CONTRIBUTORS.md",
|
23
25
|
"LICENSE",
|
24
26
|
"README.md",
|
25
27
|
"Rakefile",
|
@@ -27,6 +29,9 @@ Gem::Specification.new do |s|
|
|
27
29
|
"examples/friendly.yml",
|
28
30
|
"friendly.gemspec",
|
29
31
|
"lib/friendly.rb",
|
32
|
+
"lib/friendly/associations.rb",
|
33
|
+
"lib/friendly/associations/association.rb",
|
34
|
+
"lib/friendly/associations/set.rb",
|
30
35
|
"lib/friendly/attribute.rb",
|
31
36
|
"lib/friendly/boolean.rb",
|
32
37
|
"lib/friendly/cache.rb",
|
@@ -37,8 +42,11 @@ Gem::Specification.new do |s|
|
|
37
42
|
"lib/friendly/document_table.rb",
|
38
43
|
"lib/friendly/index.rb",
|
39
44
|
"lib/friendly/memcached.rb",
|
45
|
+
"lib/friendly/named_scope.rb",
|
40
46
|
"lib/friendly/newrelic.rb",
|
41
47
|
"lib/friendly/query.rb",
|
48
|
+
"lib/friendly/scope.rb",
|
49
|
+
"lib/friendly/scope_proxy.rb",
|
42
50
|
"lib/friendly/sequel_monkey_patches.rb",
|
43
51
|
"lib/friendly/storage.rb",
|
44
52
|
"lib/friendly/storage_factory.rb",
|
@@ -49,12 +57,14 @@ Gem::Specification.new do |s|
|
|
49
57
|
"lib/friendly/translator.rb",
|
50
58
|
"lib/friendly/uuid.rb",
|
51
59
|
"rails/init.rb",
|
60
|
+
"spec/config.yml.example",
|
52
61
|
"spec/fakes/data_store_fake.rb",
|
53
62
|
"spec/fakes/database_fake.rb",
|
54
63
|
"spec/fakes/dataset_fake.rb",
|
55
64
|
"spec/fakes/document.rb",
|
56
65
|
"spec/fakes/serializer_fake.rb",
|
57
66
|
"spec/fakes/time_fake.rb",
|
67
|
+
"spec/integration/ad_hoc_scopes_spec.rb",
|
58
68
|
"spec/integration/basic_object_lifecycle_spec.rb",
|
59
69
|
"spec/integration/batch_insertion_spec.rb",
|
60
70
|
"spec/integration/convenience_api_spec.rb",
|
@@ -62,12 +72,17 @@ Gem::Specification.new do |s|
|
|
62
72
|
"spec/integration/default_value_spec.rb",
|
63
73
|
"spec/integration/find_via_cache_spec.rb",
|
64
74
|
"spec/integration/finder_spec.rb",
|
75
|
+
"spec/integration/has_many_spec.rb",
|
65
76
|
"spec/integration/index_spec.rb",
|
77
|
+
"spec/integration/named_scope_spec.rb",
|
66
78
|
"spec/integration/pagination_spec.rb",
|
79
|
+
"spec/integration/scope_chaining_spec.rb",
|
67
80
|
"spec/integration/table_creator_spec.rb",
|
68
81
|
"spec/integration/write_through_cache_spec.rb",
|
69
82
|
"spec/spec.opts",
|
70
83
|
"spec/spec_helper.rb",
|
84
|
+
"spec/unit/associations/association_spec.rb",
|
85
|
+
"spec/unit/associations/set_spec.rb",
|
71
86
|
"spec/unit/attribute_spec.rb",
|
72
87
|
"spec/unit/cache_by_id_spec.rb",
|
73
88
|
"spec/unit/cache_spec.rb",
|
@@ -78,7 +93,10 @@ Gem::Specification.new do |s|
|
|
78
93
|
"spec/unit/friendly_spec.rb",
|
79
94
|
"spec/unit/index_spec.rb",
|
80
95
|
"spec/unit/memcached_spec.rb",
|
96
|
+
"spec/unit/named_scope_spec.rb",
|
81
97
|
"spec/unit/query_spec.rb",
|
98
|
+
"spec/unit/scope_proxy_spec.rb",
|
99
|
+
"spec/unit/scope_spec.rb",
|
82
100
|
"spec/unit/storage_factory_spec.rb",
|
83
101
|
"spec/unit/storage_proxy_spec.rb",
|
84
102
|
"spec/unit/translator_spec.rb",
|
@@ -139,6 +157,7 @@ Gem::Specification.new do |s|
|
|
139
157
|
"spec/fakes/document.rb",
|
140
158
|
"spec/fakes/serializer_fake.rb",
|
141
159
|
"spec/fakes/time_fake.rb",
|
160
|
+
"spec/integration/ad_hoc_scopes_spec.rb",
|
142
161
|
"spec/integration/basic_object_lifecycle_spec.rb",
|
143
162
|
"spec/integration/batch_insertion_spec.rb",
|
144
163
|
"spec/integration/convenience_api_spec.rb",
|
@@ -146,11 +165,16 @@ Gem::Specification.new do |s|
|
|
146
165
|
"spec/integration/default_value_spec.rb",
|
147
166
|
"spec/integration/find_via_cache_spec.rb",
|
148
167
|
"spec/integration/finder_spec.rb",
|
168
|
+
"spec/integration/has_many_spec.rb",
|
149
169
|
"spec/integration/index_spec.rb",
|
170
|
+
"spec/integration/named_scope_spec.rb",
|
150
171
|
"spec/integration/pagination_spec.rb",
|
172
|
+
"spec/integration/scope_chaining_spec.rb",
|
151
173
|
"spec/integration/table_creator_spec.rb",
|
152
174
|
"spec/integration/write_through_cache_spec.rb",
|
153
175
|
"spec/spec_helper.rb",
|
176
|
+
"spec/unit/associations/association_spec.rb",
|
177
|
+
"spec/unit/associations/set_spec.rb",
|
154
178
|
"spec/unit/attribute_spec.rb",
|
155
179
|
"spec/unit/cache_by_id_spec.rb",
|
156
180
|
"spec/unit/cache_spec.rb",
|
@@ -161,7 +185,10 @@ Gem::Specification.new do |s|
|
|
161
185
|
"spec/unit/friendly_spec.rb",
|
162
186
|
"spec/unit/index_spec.rb",
|
163
187
|
"spec/unit/memcached_spec.rb",
|
188
|
+
"spec/unit/named_scope_spec.rb",
|
164
189
|
"spec/unit/query_spec.rb",
|
190
|
+
"spec/unit/scope_proxy_spec.rb",
|
191
|
+
"spec/unit/scope_spec.rb",
|
165
192
|
"spec/unit/storage_factory_spec.rb",
|
166
193
|
"spec/unit/storage_proxy_spec.rb",
|
167
194
|
"spec/unit/translator_spec.rb"
|
@@ -175,6 +202,7 @@ Gem::Specification.new do |s|
|
|
175
202
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
176
203
|
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
177
204
|
s.add_development_dependency(%q<jferris-mocha>, [">= 0"])
|
205
|
+
s.add_development_dependency(%q<memcached>, [">= 0"])
|
178
206
|
s.add_runtime_dependency(%q<sequel>, [">= 3.7.0"])
|
179
207
|
s.add_runtime_dependency(%q<json>, [">= 0"])
|
180
208
|
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
@@ -183,6 +211,7 @@ Gem::Specification.new do |s|
|
|
183
211
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
184
212
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
185
213
|
s.add_dependency(%q<jferris-mocha>, [">= 0"])
|
214
|
+
s.add_dependency(%q<memcached>, [">= 0"])
|
186
215
|
s.add_dependency(%q<sequel>, [">= 3.7.0"])
|
187
216
|
s.add_dependency(%q<json>, [">= 0"])
|
188
217
|
s.add_dependency(%q<activesupport>, [">= 0"])
|
@@ -192,6 +221,7 @@ Gem::Specification.new do |s|
|
|
192
221
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
193
222
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
194
223
|
s.add_dependency(%q<jferris-mocha>, [">= 0"])
|
224
|
+
s.add_dependency(%q<memcached>, [">= 0"])
|
195
225
|
s.add_dependency(%q<sequel>, [">= 3.7.0"])
|
196
226
|
s.add_dependency(%q<json>, [">= 0"])
|
197
227
|
s.add_dependency(%q<activesupport>, [">= 0"])
|
data/lib/friendly.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'friendly/associations'
|
1
2
|
require 'friendly/attribute'
|
2
3
|
require 'friendly/boolean'
|
3
4
|
require 'friendly/cache'
|
@@ -8,8 +9,11 @@ require 'friendly/document'
|
|
8
9
|
require 'friendly/document_table'
|
9
10
|
require 'friendly/index'
|
10
11
|
require 'friendly/memcached'
|
12
|
+
require 'friendly/named_scope'
|
11
13
|
require 'friendly/query'
|
12
14
|
require 'friendly/sequel_monkey_patches'
|
15
|
+
require 'friendly/scope'
|
16
|
+
require 'friendly/scope_proxy'
|
13
17
|
require 'friendly/storage_factory'
|
14
18
|
require 'friendly/storage_proxy'
|
15
19
|
require 'friendly/translator'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Friendly
|
2
|
+
module Associations
|
3
|
+
class Association
|
4
|
+
attr_reader :owner_klass, :name
|
5
|
+
|
6
|
+
def initialize(owner_klass, name, options = {})
|
7
|
+
@owner_klass = owner_klass
|
8
|
+
@name = name
|
9
|
+
@class_name = options[:class_name]
|
10
|
+
@foreign_key = options[:foreign_key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def klass
|
14
|
+
@klass ||= class_name.constantize
|
15
|
+
end
|
16
|
+
|
17
|
+
def foreign_key
|
18
|
+
@foreign_key ||= [owner_klass_name, :id].join("_").to_sym
|
19
|
+
end
|
20
|
+
|
21
|
+
def class_name
|
22
|
+
@class_name ||= name.to_s.classify
|
23
|
+
end
|
24
|
+
|
25
|
+
def owner_klass_name
|
26
|
+
owner_klass.name.to_s.underscore.singularize
|
27
|
+
end
|
28
|
+
|
29
|
+
def scope(document)
|
30
|
+
klass.scope(foreign_key => document.id)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Friendly
|
2
|
+
module Associations
|
3
|
+
class Set
|
4
|
+
attr_reader :klass, :association_klass, :associations
|
5
|
+
|
6
|
+
def initialize(klass, association_klass = Association)
|
7
|
+
@klass = klass
|
8
|
+
@association_klass = association_klass
|
9
|
+
@associations = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(name, options = {})
|
13
|
+
associations[name] = association_klass.new(klass, name, options)
|
14
|
+
add_association_accessor(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_scope(name, document)
|
18
|
+
get(name).scope(document)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(name)
|
22
|
+
associations[name]
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def add_association_accessor(name)
|
27
|
+
klass.class_eval do
|
28
|
+
eval <<-__END__
|
29
|
+
def #{name}
|
30
|
+
self.class.association_set.get_scope(:#{name}, self)
|
31
|
+
end
|
32
|
+
__END__
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/friendly/attribute.rb
CHANGED
@@ -1,14 +1,36 @@
|
|
1
|
-
require 'friendly/boolean'
|
2
|
-
require 'friendly/uuid'
|
3
|
-
|
4
1
|
module Friendly
|
5
2
|
class Attribute
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
class << self
|
4
|
+
def register_type(type, sql_type, &block)
|
5
|
+
sql_types[type.name] = sql_type
|
6
|
+
converters[type] = block
|
7
|
+
end
|
8
|
+
|
9
|
+
def deregister_type(type)
|
10
|
+
sql_types.delete(type.name)
|
11
|
+
converters.delete(type)
|
12
|
+
end
|
13
|
+
|
14
|
+
def sql_type(type)
|
15
|
+
sql_types[type.name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def sql_types
|
19
|
+
@sql_types ||= {}
|
20
|
+
end
|
11
21
|
|
22
|
+
def converters
|
23
|
+
@converters ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def custom_type?(klass)
|
27
|
+
!sql_type(klass).nil?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
converters[Integer] = lambda { |s| s.to_i }
|
32
|
+
converters[String] = lambda { |s| s.to_s }
|
33
|
+
|
12
34
|
attr_reader :klass, :name, :type, :default_value
|
13
35
|
|
14
36
|
def initialize(klass, name, type, options = {})
|
@@ -25,7 +47,7 @@ module Friendly
|
|
25
47
|
|
26
48
|
def convert(value)
|
27
49
|
assert_converter_exists(value)
|
28
|
-
|
50
|
+
converters[type].call(value)
|
29
51
|
end
|
30
52
|
|
31
53
|
def default
|
@@ -37,7 +59,7 @@ module Friendly
|
|
37
59
|
nil
|
38
60
|
end
|
39
61
|
end
|
40
|
-
|
62
|
+
|
41
63
|
protected
|
42
64
|
def build_accessors
|
43
65
|
n = name
|
@@ -55,11 +77,15 @@ module Friendly
|
|
55
77
|
end
|
56
78
|
|
57
79
|
def assert_converter_exists(value)
|
58
|
-
unless
|
80
|
+
unless converters.has_key?(type)
|
59
81
|
msg = "Can't convert #{value} to #{type}.
|
60
82
|
Add a custom converter to Friendly::Attribute::CONVERTERS."
|
61
83
|
raise NoConverterExists, msg
|
62
84
|
end
|
63
85
|
end
|
86
|
+
|
87
|
+
def converters
|
88
|
+
self.class.converters
|
89
|
+
end
|
64
90
|
end
|
65
91
|
end
|