simply_stored 0.3.9 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. data/CHANGELOG.md +38 -0
  2. data/README.md +45 -24
  3. data/lib/simply_stored/couch/association_property.rb +27 -0
  4. data/lib/simply_stored/couch/belongs_to.rb +5 -7
  5. data/lib/simply_stored/couch/finders.rb +5 -1
  6. data/lib/simply_stored/couch/has_and_belongs_to_many.rb +202 -0
  7. data/lib/simply_stored/couch/has_many.rb +6 -51
  8. data/lib/simply_stored/couch/has_one.rb +4 -29
  9. data/lib/simply_stored/couch/properties.rb +11 -0
  10. data/lib/simply_stored/couch.rb +38 -2
  11. data/lib/simply_stored/instance_methods.rb +68 -29
  12. data/lib/simply_stored.rb +3 -1
  13. data/test/{couchdb/couch_active_model_compatibility_test.rb → active_model_compatibility_test.rb} +2 -2
  14. data/test/{couchdb/couch_belongs_to_test.rb → belongs_to_test.rb} +13 -3
  15. data/test/{couchdb/couch_conflict_handling_test.rb → conflict_handling_test.rb} +3 -3
  16. data/test/{couchdb/couch_finder_test.rb → finder_test.rb} +8 -3
  17. data/test/fixtures/couch.rb +55 -0
  18. data/test/has_and_belongs_to_many_test.rb +639 -0
  19. data/test/{couchdb/couch_has_many_test.rb → has_many_test.rb} +13 -3
  20. data/test/{couchdb/couch_has_one_test.rb → has_one_test.rb} +13 -3
  21. data/test/{couchdb/couch_instance_lifecycle_test.rb → instance_lifecycle_test.rb} +3 -3
  22. data/test/{couchdb/couch_mass_assignment_protection_test.rb → mass_assignment_protection_test.rb} +3 -3
  23. data/test/{couchdb/couch_s3_test.rb → s3_test.rb} +3 -3
  24. data/test/{couchdb/couch_soft_deletable_test.rb → soft_deletable_test.rb} +3 -3
  25. data/test/test_helper.rb +1 -5
  26. data/test/{couchdb/couch_validations_test.rb → validations_test.rb} +3 -3
  27. data/test/{couchdb/custom_views_test.rb → views_test.rb} +3 -3
  28. metadata +36 -234
  29. data/lib/simply_stored/simpledb/associations.rb +0 -215
  30. data/lib/simply_stored/simpledb/attributes.rb +0 -173
  31. data/lib/simply_stored/simpledb/storag.rb +0 -85
  32. data/lib/simply_stored/simpledb/validations.rb +0 -88
  33. data/lib/simply_stored/simpledb.rb +0 -216
  34. data/test/fixtures/simpledb/item.rb +0 -11
  35. data/test/fixtures/simpledb/item_daddy.rb +0 -8
  36. data/test/fixtures/simpledb/log_item.rb +0 -3
  37. data/test/fixtures/simpledb/namespace_bar.rb +0 -5
  38. data/test/fixtures/simpledb/namespace_foo.rb +0 -7
  39. data/test/fixtures/simpledb/protected_item.rb +0 -3
  40. data/test/simply_stored_simpledb_test.rb +0 -1376
  41. data/test/vendor/dhaka-2.2.1/lib/dhaka/dot/dot.rb +0 -29
  42. data/test/vendor/dhaka-2.2.1/lib/dhaka/evaluator/evaluator.rb +0 -133
  43. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/closure_hash.rb +0 -15
  44. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar.rb +0 -240
  45. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar_symbol.rb +0 -27
  46. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/precedence.rb +0 -19
  47. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/production.rb +0 -36
  48. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/accept_actions.rb +0 -36
  49. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/alphabet.rb +0 -21
  50. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/compiled_lexer.rb +0 -46
  51. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/dfa.rb +0 -121
  52. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexeme.rb +0 -32
  53. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer.rb +0 -70
  54. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer_run.rb +0 -78
  55. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_grammar.rb +0 -392
  56. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_parser.rb +0 -2010
  57. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_tokenizer.rb +0 -14
  58. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/specification.rb +0 -96
  59. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state.rb +0 -68
  60. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state_machine.rb +0 -37
  61. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/action.rb +0 -55
  62. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/channel.rb +0 -58
  63. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/compiled_parser.rb +0 -51
  64. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/conflict.rb +0 -54
  65. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/item.rb +0 -42
  66. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_result.rb +0 -50
  67. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_tree.rb +0 -66
  68. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser.rb +0 -165
  69. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_methods.rb +0 -11
  70. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_run.rb +0 -39
  71. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_state.rb +0 -74
  72. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/token.rb +0 -22
  73. data/test/vendor/dhaka-2.2.1/lib/dhaka/runtime.rb +0 -51
  74. data/test/vendor/dhaka-2.2.1/lib/dhaka/tokenizer/tokenizer.rb +0 -190
  75. data/test/vendor/dhaka-2.2.1/lib/dhaka.rb +0 -62
  76. data/test/vendor/dhaka-2.2.1/test/all_tests.rb +0 -5
  77. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator.rb +0 -64
  78. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator_test.rb +0 -43
  79. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar.rb +0 -41
  80. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar_test.rb +0 -9
  81. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_test_methods.rb +0 -9
  82. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer.rb +0 -39
  83. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer_test.rb +0 -38
  84. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_evaluator.rb +0 -43
  85. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar.rb +0 -24
  86. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar_test.rb +0 -30
  87. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_lexer_specification.rb +0 -23
  88. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_parser_test.rb +0 -33
  89. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_grammar.rb +0 -23
  90. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_tokenizer.rb +0 -22
  91. data/test/vendor/dhaka-2.2.1/test/brackets/brackets_test.rb +0 -28
  92. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver.rb +0 -46
  93. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver_test.rb +0 -276
  94. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator.rb +0 -284
  95. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator_test.rb +0 -38
  96. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_grammar.rb +0 -104
  97. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer.rb +0 -109
  98. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_specification.rb +0 -37
  99. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_test.rb +0 -58
  100. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser.rb +0 -879
  101. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser_test.rb +0 -55
  102. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_test.rb +0 -170
  103. data/test/vendor/dhaka-2.2.1/test/core/another_lalr_but_not_slr_grammar.rb +0 -20
  104. data/test/vendor/dhaka-2.2.1/test/core/compiled_parser_test.rb +0 -44
  105. data/test/vendor/dhaka-2.2.1/test/core/dfa_test.rb +0 -170
  106. data/test/vendor/dhaka-2.2.1/test/core/evaluator_test.rb +0 -22
  107. data/test/vendor/dhaka-2.2.1/test/core/grammar_test.rb +0 -83
  108. data/test/vendor/dhaka-2.2.1/test/core/lalr_but_not_slr_grammar.rb +0 -19
  109. data/test/vendor/dhaka-2.2.1/test/core/lexer_test.rb +0 -139
  110. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar.rb +0 -7
  111. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar_test.rb +0 -8
  112. data/test/vendor/dhaka-2.2.1/test/core/nullable_grammar.rb +0 -21
  113. data/test/vendor/dhaka-2.2.1/test/core/parse_result_test.rb +0 -44
  114. data/test/vendor/dhaka-2.2.1/test/core/parser_state_test.rb +0 -24
  115. data/test/vendor/dhaka-2.2.1/test/core/parser_test.rb +0 -131
  116. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar.rb +0 -17
  117. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar_test.rb +0 -9
  118. data/test/vendor/dhaka-2.2.1/test/core/rr_conflict_grammar.rb +0 -21
  119. data/test/vendor/dhaka-2.2.1/test/core/simple_grammar.rb +0 -22
  120. data/test/vendor/dhaka-2.2.1/test/core/sr_conflict_grammar.rb +0 -16
  121. data/test/vendor/dhaka-2.2.1/test/dhaka_test_helper.rb +0 -17
  122. data/test/vendor/dhaka-2.2.1/test/fake_logger.rb +0 -17
  123. data/test/vendor/simplerdb-0.2/lib/simplerdb/client_exception.rb +0 -10
  124. data/test/vendor/simplerdb-0.2/lib/simplerdb/db.rb +0 -146
  125. data/test/vendor/simplerdb-0.2/lib/simplerdb/query_language.rb +0 -266
  126. data/test/vendor/simplerdb-0.2/lib/simplerdb/server.rb +0 -33
  127. data/test/vendor/simplerdb-0.2/lib/simplerdb/servlet.rb +0 -191
  128. data/test/vendor/simplerdb-0.2/lib/simplerdb.rb +0 -3
  129. data/test/vendor/simplerdb-0.2/test/functional_test.rb +0 -81
  130. data/test/vendor/simplerdb-0.2/test/query_evaluator_test.rb +0 -73
  131. data/test/vendor/simplerdb-0.2/test/query_parser_test.rb +0 -64
  132. data/test/vendor/simplerdb-0.2/test/simplerdb_test.rb +0 -80
data/CHANGELOG.md CHANGED
@@ -1,6 +1,44 @@
1
1
  Changelog
2
2
  =============
3
3
 
4
+ 0.5.0
5
+
6
+ - Add support for has_and_belongs_to_many relations:
7
+
8
+ n:m relations where the IDs are stored on one part as an array:
9
+
10
+ class Server
11
+ include SimplyStored::Couch
12
+
13
+ property :hostname
14
+
15
+ has_and_belongs_to_many :networks, :storing_keys => true
16
+ end
17
+
18
+ class Network
19
+ include SimplyStored::Couch
20
+
21
+ property :klass
22
+
23
+ has_and_belongs_to_many :servers, :storing_keys => false
24
+ end
25
+
26
+ network = Network.create(:klass => "A")
27
+ server = Server.new(:hostname => 'www.example.com')
28
+ network.add_server(server)
29
+ server.network_ids # => [network.id]
30
+ network.servers # => [server]
31
+ server.networks # => [network]
32
+
33
+ The array property holding the IDs of the other item will be used to constuct two view to lookup
34
+ the other part. Soft deleting is only supported on the class holding the IDs.
35
+
36
+ - Add support for .last - which is the same as first by reverse order
37
+
38
+ User.last # => User.find(:first, :order => :desc)
39
+
40
+ - Drop support for SimpleDB
41
+
4
42
  0.3.8
5
43
 
6
44
  - Fix loading of has_many/has_one associations for inherited relations.
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- Convenience layer for CouchDB and SimpleDB. Requires CouchPotato and RightAWS library respectively.
1
+ Convenience layer for CouchDB on top of CouchPotato.
2
2
 
3
- SimplyStored allows you to persist your objects to CouchDB (or SimpleDB) using an ActiveRecord-like syntax.
3
+ SimplyStored allows you to persist your objects to CouchDB using an ActiveRecord-like syntax.
4
4
 
5
5
  In contrast to [CouchPotato](http://github.com/langalex/couch_potato) (on top of it is build)
6
6
  it supports associations and other syntactic sugar that makes ActiveRecord so appealing.
7
7
 
8
- Both backends have also support for S3 attachments.
8
+ SimplyStored has also support for S3 attachments.
9
9
 
10
10
  See also [RockingChair](http://github.com/jweiss/rocking_chair) on how to speed-up your unit tests
11
11
  by using an in-memory CouchDB backend.
@@ -20,25 +20,17 @@ Installation
20
20
  Usage
21
21
  =============
22
22
 
23
- Require the SimplyStored and decide with backend you want (CouchDB or SimpleDB).
23
+ Require SimplyStored:
24
24
 
25
- require 'simply_stored/couch'
25
+ require 'simply_stored'
26
26
  CouchPotato::Config.database_name = "http://example.com:5984/name_of_the_db"
27
-
28
- or
29
-
30
- require 'simply_stored/simpledb'
31
- SimplyStored::Simple.aws_access_key = 'foo'
32
- SimplyStored::Simple.aws_secret_access_key = 'bar'
33
- RightAws::ActiveSdb.establish_connection(SimplyStored::Simple.aws_access_key, SimplyStored::Simple.aws_secret_access_key, :protocol => 'https')
34
-
27
+
35
28
  From now on you can define classes that use SimplyStored.
36
29
 
37
- CouchDB - Intro
30
+ Intro
38
31
  =============
39
32
 
40
- The CouchDB backend is better supported and has more features. It auto-generates views for you and handles
41
- all the serialization and de-serialization stuff.
33
+ SimplyStored auto-generates views for you and handles all the serialization and de-serialization stuff.
42
34
 
43
35
  class User
44
36
  include SimplyStored::Couch
@@ -85,10 +77,10 @@ all the serialization and de-serialization stuff.
85
77
  # => []
86
78
 
87
79
 
88
- CouchDB - Associations
80
+ Associations
89
81
  =============
90
82
 
91
- The supported associations are: belongs_to, has_one, has_many, and has_many :through
83
+ The supported associations are: belongs_to, has_one, has_many, has_many :through, and has_and_belongs_to_many:
92
84
 
93
85
  class Post
94
86
  include SimplyStored::Couch
@@ -136,8 +128,36 @@ The supported associations are: belongs_to, has_one, has_many, and has_many :thr
136
128
  post.user_count
137
129
  # => 2
138
130
 
131
+ n:m relations where the IDs are stored on one part as an array:
132
+
133
+ class Server
134
+ include SimplyStored::Couch
135
+
136
+ property :hostname
137
+
138
+ has_and_belongs_to_many :networks, :storing_keys => true
139
+ end
140
+
141
+ class Network
142
+ include SimplyStored::Couch
143
+
144
+ property :klass
145
+
146
+ has_and_belongs_to_many :servers, :storing_keys => false
147
+ end
148
+
149
+ network = Network.create(:klass => "A")
150
+ server = Server.new(:hostname => 'www.example.com')
151
+ network.add_server(server)
152
+ server.network_ids # => [network.id]
153
+ network.servers # => [server]
154
+ server.networks # => [network]
139
155
 
140
- CouchDB - Custom Associations
156
+ The array property holding the IDs of the other item will be used to constuct two view to lookup
157
+ the other part. Soft deleting is only supported on the class holding the IDs.
158
+
159
+ Custom Associations
160
+ =============
141
161
 
142
162
  class Document
143
163
  include SimplyStored::Couch
@@ -150,7 +170,7 @@ CouchDB - Custom Associations
150
170
  d.creator = User.first
151
171
 
152
172
 
153
- CouchDB - Validations
173
+ Validations
154
174
  =============
155
175
 
156
176
  Further, you can have validations (using the validatable gem)
@@ -181,10 +201,11 @@ Further, you can have validations (using the validatable gem)
181
201
  # => raises CouchPotato::Database::ValidationsFailedError: #<CouchPotato::Database::ValidationsFailedError:0x102571130>
182
202
 
183
203
 
184
- CouchDB - S3 Attachments
204
+ S3 Attachments
185
205
  =============
186
206
 
187
- Both the CouchDB backend and the SimpleDB backend have support for S3 attachments:
207
+ SimplyStored supports storing large attachments in Amazon S3.
208
+ It uses RightAWS for the interaction with the EC2 API:
188
209
 
189
210
  class Log
190
211
  include SimplyStored::Couch
@@ -205,10 +226,10 @@ Both the CouchDB backend and the SimpleDB backend have support for S3 attachment
205
226
  log.data_size
206
227
  # => 11238132
207
228
 
208
- This will create an item on S3 in the specified bucket. The item will use the ID of the log object as the key and the body will be the data attribute. This way you can store big files outside of CouchDB or SimpleDB.
229
+ This will create an item on S3 in the specified bucket. The item will use the ID of the log object as the key and the body will be the data attribute. This way you can store big files outside of CouchDB.
209
230
 
210
231
 
211
- CouchDB - Soft delete
232
+ Soft delete
212
233
  =============
213
234
 
214
235
  SimplyStored also has support for "soft deleting" - much like acts_as_paranoid. Items will then not be deleted but only marked as deleted. This way you can recover them later.
@@ -0,0 +1,27 @@
1
+ module SimplyStored
2
+ module Couch
3
+ class AssociationProperty
4
+ attr_reader :name, :options
5
+
6
+ def dirty?(object)
7
+ false
8
+ end
9
+
10
+ def build(object, json)
11
+ end
12
+
13
+ def serialize(json, object)
14
+ end
15
+ alias :value :serialize
16
+
17
+ def supports_dirty?
18
+ false
19
+ end
20
+
21
+ def association?
22
+ true
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -2,8 +2,11 @@
2
2
  module SimplyStored
3
3
  module Couch
4
4
  module BelongsTo
5
-
5
+ include SimplyStored::Couch::Properties
6
+
6
7
  def belongs_to(name, options = {})
8
+ check_existing_properties(name, SimplyStored::Couch::BelongsTo::Property)
9
+
7
10
  map_definition_without_deleted = <<-eos
8
11
  function(doc) {
9
12
  if (doc['ruby_class'] == '#{self.to_s}' && doc['#{name.to_s}_id'] != null) {
@@ -17,11 +20,6 @@ module SimplyStored
17
20
  eos
18
21
 
19
22
  reduce_definition = "_sum"
20
- # reduce_definition = <<-eos
21
- # function(key, values) {
22
- # return sum(values);
23
- # }
24
- # eos
25
23
 
26
24
  view "association_#{self.name.underscore}_belongs_to_#{name}",
27
25
  :map => map_definition_without_deleted,
@@ -119,4 +117,4 @@ module SimplyStored
119
117
  end
120
118
  end
121
119
  end
122
- end
120
+ end
@@ -40,7 +40,11 @@ module SimplyStored
40
40
  def first(*args)
41
41
  find(:first, *args)
42
42
  end
43
-
43
+
44
+ def last(*args)
45
+ find(:first, :order => :desc, *args)
46
+ end
47
+
44
48
  def count(options = {})
45
49
  options.assert_valid_keys(:with_deleted)
46
50
  with_deleted = options[:with_deleted]
@@ -0,0 +1,202 @@
1
+ module SimplyStored
2
+ module Couch
3
+ module HasAndBelongsToMany
4
+ def has_and_belongs_to_many(name, options = {})
5
+ check_existing_properties(name, SimplyStored::Couch::HasAndBelongsToMany::Property)
6
+ properties << SimplyStored::Couch::HasAndBelongsToMany::Property.new(self, name, options)
7
+ end
8
+
9
+ def define_has_and_belongs_to_many_property(foreign_key)
10
+ property foreign_key
11
+ end
12
+
13
+ def define_has_and_belongs_to_many_views(name, options)
14
+ key_order = options[:class_storing_keys] == self.name ? "doc.#{options[:foreign_key]}[index], doc._id" : "doc._id, doc.#{options[:foreign_key]}[index]"
15
+ value = options[:class_storing_keys] == self.name ? 1 : "{ _id :doc.#{options[:foreign_key]}[index]}"
16
+
17
+ map_definition_without_deleted = <<-eos
18
+ function(doc) {
19
+ if (doc['ruby_class'] == '#{options[:class_storing_keys]}' && doc['#{options[:foreign_key]}'] != null) {
20
+ if (doc['#{soft_delete_attribute}'] && doc['#{soft_delete_attribute}'] != null){
21
+ // "soft" deleted
22
+ }else{
23
+ for (var index in doc.#{options[:foreign_key]}) {
24
+ emit([#{key_order}], #{value});
25
+ }
26
+ }
27
+ }
28
+ }
29
+ eos
30
+
31
+ reduce_definition = options[:class_storing_keys] == self.name ? "_sum" : <<-eos
32
+ function(key, values) {
33
+ var sum = 0;
34
+ for (var i in values){
35
+ if (typeof(i) == 'number'){
36
+ sum = sum + i;
37
+ } else {
38
+ sum = sum + 1;
39
+ }
40
+ }
41
+ return sum;
42
+ }
43
+ eos
44
+
45
+ view "association_#{self.name.underscore}_has_and_belongs_to_many_#{name}",
46
+ :map => map_definition_without_deleted,
47
+ :reduce => reduce_definition,
48
+ :type => "custom",
49
+ :include_docs => true
50
+
51
+ map_definition_with_deleted = <<-eos
52
+ function(doc) {
53
+ if (doc['ruby_class'] == '#{options[:class_storing_keys]}' && doc['#{options[:foreign_key]}'] != null) {
54
+ for (var index in doc.#{options[:foreign_key]}) {
55
+ emit([#{key_order}], #{value});
56
+ }
57
+ }
58
+ }
59
+ eos
60
+
61
+ view "association_#{self.name.underscore}_has_and_belongs_to_many_#{name}_with_deleted",
62
+ :map => map_definition_with_deleted,
63
+ :reduce => reduce_definition,
64
+ :type => "custom",
65
+ :include_docs => true
66
+ end
67
+
68
+ def define_has_and_belongs_to_many_getter(name, options)
69
+ define_method(name) do |*args|
70
+ local_options = args.first && args.first.is_a?(Hash) && args.first
71
+ forced_reload, with_deleted, limit, descending = extract_association_options(local_options)
72
+
73
+ cached_results = send("_get_cached_#{name}")
74
+ cache_key = _cache_key_for(local_options)
75
+ if forced_reload || cached_results[cache_key].nil?
76
+ cached_results[cache_key] = find_associated_via_join_view(options[:class_name], self.class, :with_deleted => with_deleted, :limit => limit, :descending => descending, :foreign_key => options[:foreign_key])
77
+ instance_variable_set("@#{name}", cached_results)
78
+ end
79
+ cached_results[cache_key]
80
+ end
81
+ end
82
+
83
+ def define_has_and_belongs_to_many_setter_add(name, options)
84
+ define_method("add_#{name.to_s.singularize}") do |value|
85
+ klass = self.class.get_class_from_name(name)
86
+ raise ArgumentError, "expected #{klass} got #{value.class}" unless value.is_a?(klass)
87
+
88
+ if options[:class_storing_keys] == self.class.name
89
+ self.send("#{options[:foreign_key]}=", ((send(options[:foreign_key]) || []) + [value.id]).uniq )
90
+ self.save(false)
91
+ else
92
+ value.send("#{options[:foreign_key]}=", ((value.send(options[:foreign_key]) || []) + [self.id]).uniq )
93
+ value.save(false)
94
+ end
95
+
96
+ cached_results = send("_get_cached_#{name}")[:all]
97
+ send("_set_cached_#{name}", (cached_results || []) << value, :all)
98
+ nil
99
+ end
100
+ end
101
+
102
+ def define_has_and_belongs_to_many_setter_remove(name, options)
103
+ define_method "remove_#{name.to_s.singularize}" do |value|
104
+ klass = self.class.get_class_from_name(name)
105
+ raise ArgumentError, "expected #{klass} got #{value.class}" unless value.is_a?(klass)
106
+
107
+ if options[:class_storing_keys] == self.class.name
108
+ raise ArgumentError, "cannot remove not mine" unless (send(options[:foreign_key]) || []).include?(value.id)
109
+ else
110
+ raise ArgumentError, "cannot remove not mine" unless (value.send(options[:foreign_key]) || []).include?(id)
111
+ end
112
+
113
+ if options[:class_storing_keys] == self.class.name
114
+ foreign_keys = (send(options[:foreign_key]) || []) - [value.id]
115
+ send("#{options[:foreign_key]}=", foreign_keys)
116
+ save(false)
117
+ else
118
+ foreign_keys = (value.send(options[:foreign_key]) || []) - [self.id]
119
+ value.send("#{options[:foreign_key]}=", foreign_keys)
120
+ value.save(false)
121
+ end
122
+
123
+ cached_results = send("_get_cached_#{name}")[:all]
124
+ send("_set_cached_#{name}", (cached_results || []).delete_if{|item| item.id == value.id}, :all)
125
+ nil
126
+ end
127
+ end
128
+
129
+ def define_has_and_belongs_to_many_setter_remove_all(name, options)
130
+ define_method "remove_all_#{name}" do
131
+ all = send("#{name}", :force_reload => true)
132
+
133
+ all.collect{|i| i}.each do |item|
134
+ send("remove_#{name.to_s.singularize}", item)
135
+ end
136
+ end
137
+ end
138
+
139
+ def define_has_and_belongs_to_many_count(name, options, through = nil)
140
+ method_name = name.to_s.singularize.underscore + "_count"
141
+ define_method(method_name) do |*args|
142
+ local_options = args.first && args.first.is_a?(Hash) && args.first
143
+ forced_reload, with_deleted, limit, descending = extract_association_options(local_options)
144
+
145
+ if forced_reload || instance_variable_get("@#{method_name}").nil?
146
+ instance_variable_set("@#{method_name}", count_associated_via_join_view(through || options[:class_name], self.class, :with_deleted => with_deleted, :foreign_key => options[:foreign_key]))
147
+ end
148
+ instance_variable_get("@#{method_name}")
149
+ end
150
+ end
151
+
152
+ def define_has_and_belongs_to_many_after_destroy_cleanup(name, options)
153
+ if options[:class_storing_keys] == self.name
154
+ define_method "has_and_belongs_to_many_clean_up_after_destroy" do |property|
155
+ nil # deleting is enough as we store the keys
156
+ end
157
+ else
158
+ define_method "has_and_belongs_to_many_clean_up_after_destroy" do |property|
159
+ send("remove_all_#{property.name}")
160
+ end
161
+ end
162
+ end
163
+
164
+ class Property < SimplyStored::Couch::AssociationProperty
165
+
166
+ def initialize(owner_clazz, name, options = {})
167
+ options = {
168
+ :storing_keys => false,
169
+ :class_name => name.to_s.singularize.camelize,
170
+ :foreign_key => nil,
171
+ }.update(options)
172
+
173
+ # there is only one pair of foreign_keys and it usualy the name of the class not storing the keys
174
+ if options[:foreign_key].blank?
175
+ if options[:storing_keys]
176
+ options[:foreign_key] = options[:class_name].singularize.underscore.foreign_key.pluralize
177
+ else
178
+ options[:foreign_key] = owner_clazz.name.singularize.underscore.foreign_key.pluralize
179
+ end
180
+ end
181
+ options[:class_storing_keys] = options[:storing_keys] ? owner_clazz.name : options[:class_name]
182
+ @name, @options = name, options
183
+
184
+ options.assert_valid_keys(:class_name, :foreign_key, :storing_keys, :class_storing_keys)
185
+
186
+ owner_clazz.class_eval do
187
+ _define_cache_accessors(name, options)
188
+ define_has_and_belongs_to_many_property(options[:foreign_key]) if options[:storing_keys]
189
+ define_has_and_belongs_to_many_views(name, options)
190
+ define_has_and_belongs_to_many_getter(name, options)
191
+ define_has_and_belongs_to_many_setter_add(name, options)
192
+ define_has_and_belongs_to_many_setter_remove(name, options)
193
+ define_has_and_belongs_to_many_setter_remove_all(name, options)
194
+ define_has_and_belongs_to_many_count(name, options)
195
+ define_has_and_belongs_to_many_after_destroy_cleanup(name, options)
196
+ end
197
+ end
198
+
199
+ end
200
+ end
201
+ end
202
+ end
@@ -2,24 +2,14 @@ module SimplyStored
2
2
  module Couch
3
3
  module HasMany
4
4
  def has_many(name, options = {})
5
+ check_existing_properties(name, SimplyStored::Couch::HasMany::Property)
5
6
  properties << SimplyStored::Couch::HasMany::Property.new(self, name, options)
6
7
  end
7
8
 
8
9
  def define_has_many_getter(name, options)
9
10
  define_method(name) do |*args|
10
11
  local_options = args.first && args.first.is_a?(Hash) && args.first
11
- if local_options
12
- local_options.assert_valid_keys(:force_reload, :with_deleted, :limit, :order)
13
- forced_reload = local_options.delete(:force_reload)
14
- with_deleted = local_options[:with_deleted]
15
- limit = local_options[:limit]
16
- descending = (local_options[:order] == :desc) ? true : false
17
- else
18
- forced_reload = false
19
- with_deleted = false
20
- limit = nil
21
- descending = false
22
- end
12
+ forced_reload, with_deleted, limit, descending = extract_association_options(local_options)
23
13
 
24
14
  cached_results = send("_get_cached_#{name}")
25
15
  cache_key = _cache_key_for(local_options)
@@ -131,22 +121,6 @@ module SimplyStored
131
121
  end
132
122
  end
133
123
 
134
- def define_cache_accessors(name, options)
135
- define_method "_get_cached_#{name}" do
136
- instance_variable_get("@#{name}") || {}
137
- end
138
-
139
- define_method "_set_cached_#{name}" do |value, cache_key|
140
- cached = send("_get_cached_#{name}")
141
- cached[cache_key] = value
142
- instance_variable_set("@#{name}", cached)
143
- end
144
-
145
- define_method "_cache_key_for" do |opt|
146
- opt.blank? ? :all : opt.to_s
147
- end
148
- end
149
-
150
124
  def set_parent_has_many_association_object(parent, child_collection)
151
125
  child_collection.each do |child|
152
126
  if child.respond_to?("#{parent.class.name.to_s.singularize.downcase}=")
@@ -155,8 +129,7 @@ module SimplyStored
155
129
  end
156
130
  end
157
131
 
158
- class Property
159
- attr_reader :name, :options
132
+ class Property < SimplyStored::Couch::AssociationProperty
160
133
 
161
134
  def initialize(owner_clazz, name, options = {})
162
135
  options = {
@@ -171,13 +144,13 @@ module SimplyStored
171
144
 
172
145
  if options[:through]
173
146
  owner_clazz.class_eval do
174
- define_cache_accessors(name, options)
147
+ _define_cache_accessors(name, options)
175
148
  define_has_many_through_getter(name, options, options[:through])
176
149
  define_has_many_count(name, options, options[:through])
177
150
  end
178
151
  else
179
152
  owner_clazz.class_eval do
180
- define_cache_accessors(name, options)
153
+ _define_cache_accessors(name, options)
181
154
  define_has_many_getter(name, options)
182
155
  define_has_many_setter_add(name, options)
183
156
  define_has_many_setter_remove(name, options)
@@ -187,25 +160,7 @@ module SimplyStored
187
160
  end
188
161
  end
189
162
 
190
- def dirty?(object)
191
- false
192
- end
193
-
194
- def build(object, json)
195
- end
196
-
197
- def serialize(json, object)
198
- end
199
- alias :value :serialize
200
-
201
- def supports_dirty?
202
- false
203
- end
204
-
205
- def association?
206
- true
207
- end
208
163
  end
209
164
  end
210
165
  end
211
- end
166
+ end
@@ -2,6 +2,7 @@ module SimplyStored
2
2
  module Couch
3
3
  module HasOne
4
4
  def has_one(name, options = {})
5
+ check_existing_properties(name, SimplyStored::Couch::HasOne::Property)
5
6
  properties << SimplyStored::Couch::HasOne::Property.new(self, name, options)
6
7
  end
7
8
 
@@ -33,14 +34,7 @@ module SimplyStored
33
34
  def define_has_one_getter(name, options)
34
35
  define_method(name) do |*args|
35
36
  local_options = args.first && args.first.is_a?(Hash) && args.first
36
- if local_options
37
- local_options.assert_valid_keys(:force_reload, :with_deleted)
38
- forced_reload = local_options[:force_reload]
39
- with_deleted = local_options[:with_deleted]
40
- else
41
- forced_reload = false
42
- with_deleted = false
43
- end
37
+ forced_reload, with_deleted, limit, descending = extract_association_options(local_options)
44
38
 
45
39
  if forced_reload || instance_variable_get("@#{name}").nil?
46
40
  found_object = find_one_associated(options[:class_name], self.class, :with_deleted => with_deleted, :foreign_key => options[:foreign_key])
@@ -57,8 +51,7 @@ module SimplyStored
57
51
  end
58
52
  end
59
53
 
60
- class Property
61
- attr_reader :name, :options
54
+ class Property < SimplyStored::Couch::AssociationProperty
62
55
 
63
56
  def initialize(owner_clazz, name, options = {})
64
57
  options = {
@@ -76,25 +69,7 @@ module SimplyStored
76
69
  end
77
70
  end
78
71
 
79
- def dirty?(object)
80
- false
81
- end
82
-
83
- def build(object, json)
84
- end
85
-
86
- def serialize(json, object)
87
- end
88
- alias :value :serialize
89
-
90
- def supports_dirty?
91
- false
92
- end
93
-
94
- def association?
95
- true
96
- end
97
72
  end
98
73
  end
99
74
  end
100
- end
75
+ end
@@ -0,0 +1,11 @@
1
+ module SimplyStored
2
+ module Couch
3
+ module Properties
4
+ def check_existing_properties(name, type)
5
+ if properties.find{|property| name.to_sym == property.name.to_sym && property.class != type}
6
+ raise "Property with the name (#{name}) already defined"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end