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 CHANGED
@@ -1,2 +1,2 @@
1
- README.rdoc
1
+ README.md
2
2
  lib/**/*.rb
data/.gitignore CHANGED
@@ -21,3 +21,6 @@ pkg
21
21
  ## PROJECT::SPECIFIC
22
22
  test.rb
23
23
  test_active_record.rb
24
+ spec/config.yml
25
+ .yardoc
26
+ doc
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
@@ -0,0 +1,6 @@
1
+ Contributors to Friendly
2
+ ========================
3
+
4
+ * James Golick - http://jamesgolick.com (github: jamesgolick)
5
+ * Jonathan Palardy - http://technotales.wordpress.com (github: jpalardy)
6
+ * Jeff Rafter - http://socialorange.com (github: jeffrafter)
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.3.5
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.3.5"
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-17}
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,7 @@
1
+ require 'friendly/associations/association'
2
+ require 'friendly/associations/set'
3
+
4
+ module Friendly
5
+ module Associations
6
+ end
7
+ end
@@ -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
@@ -1,14 +1,36 @@
1
- require 'friendly/boolean'
2
- require 'friendly/uuid'
3
-
4
1
  module Friendly
5
2
  class Attribute
6
- CONVERTERS = {}
7
- CONVERTERS[UUID] = lambda { |s| UUID.new(s) }
8
- CONVERTERS[Integer] = lambda { |s| s.to_i }
9
- CONVERTERS[String] = lambda { |s| s.to_s }
10
- CONVERTERS[Boolean] = lambda { |s| s }
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
- CONVERTERS[type].call(value)
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 CONVERTERS.has_key?(type)
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