yodel 0.0.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.
Files changed (200) hide show
  1. data/.document +5 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +63 -0
  5. data/LICENSE +1 -0
  6. data/README.rdoc +20 -0
  7. data/Rakefile +1 -0
  8. data/bin/yodel +4 -0
  9. data/lib/yodel.rb +22 -0
  10. data/lib/yodel/application/application.rb +44 -0
  11. data/lib/yodel/application/extension.rb +59 -0
  12. data/lib/yodel/application/request_handler.rb +48 -0
  13. data/lib/yodel/application/yodel.rb +25 -0
  14. data/lib/yodel/command/command.rb +94 -0
  15. data/lib/yodel/command/deploy.rb +67 -0
  16. data/lib/yodel/command/dns_server.rb +16 -0
  17. data/lib/yodel/command/installer.rb +229 -0
  18. data/lib/yodel/config/config.rb +30 -0
  19. data/lib/yodel/config/environment.rb +16 -0
  20. data/lib/yodel/config/yodel.rb +21 -0
  21. data/lib/yodel/exceptions/destroyed_record.rb +2 -0
  22. data/lib/yodel/exceptions/domain_not_found.rb +16 -0
  23. data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
  24. data/lib/yodel/exceptions/exceptions.rb +3 -0
  25. data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
  26. data/lib/yodel/exceptions/invalid_field.rb +2 -0
  27. data/lib/yodel/exceptions/invalid_index.rb +2 -0
  28. data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
  29. data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
  30. data/lib/yodel/exceptions/layout_not_found.rb +2 -0
  31. data/lib/yodel/exceptions/mass_assignment.rb +2 -0
  32. data/lib/yodel/exceptions/missing_migration.rb +2 -0
  33. data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
  34. data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
  35. data/lib/yodel/exceptions/unauthorised.rb +2 -0
  36. data/lib/yodel/exceptions/unknown_field.rb +2 -0
  37. data/lib/yodel/middleware/development_server.rb +180 -0
  38. data/lib/yodel/middleware/error_pages.rb +72 -0
  39. data/lib/yodel/middleware/public_assets.rb +78 -0
  40. data/lib/yodel/middleware/request.rb +16 -0
  41. data/lib/yodel/middleware/site_detector.rb +22 -0
  42. data/lib/yodel/mime_types/default_mime_set.rb +28 -0
  43. data/lib/yodel/mime_types/mime_type.rb +68 -0
  44. data/lib/yodel/mime_types/mime_type_set.rb +41 -0
  45. data/lib/yodel/mime_types/mime_types.rb +6 -0
  46. data/lib/yodel/mime_types/yodel.rb +15 -0
  47. data/lib/yodel/models/api/api.rb +1 -0
  48. data/lib/yodel/models/api/api_call.rb +87 -0
  49. data/lib/yodel/models/core/associations/association.rb +37 -0
  50. data/lib/yodel/models/core/associations/associations.rb +22 -0
  51. data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
  52. data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
  53. data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
  54. data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
  55. data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
  56. data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
  57. data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
  58. data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
  59. data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
  60. data/lib/yodel/models/core/associations/record_association.rb +38 -0
  61. data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
  62. data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
  63. data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
  64. data/lib/yodel/models/core/attachments/attachment.rb +73 -0
  65. data/lib/yodel/models/core/attachments/image.rb +38 -0
  66. data/lib/yodel/models/core/core.rb +15 -0
  67. data/lib/yodel/models/core/fields/alias_field.rb +32 -0
  68. data/lib/yodel/models/core/fields/array_field.rb +64 -0
  69. data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
  70. data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
  71. data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
  72. data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
  73. data/lib/yodel/models/core/fields/color_field.rb +4 -0
  74. data/lib/yodel/models/core/fields/date_field.rb +35 -0
  75. data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
  76. data/lib/yodel/models/core/fields/email_field.rb +10 -0
  77. data/lib/yodel/models/core/fields/enum_field.rb +33 -0
  78. data/lib/yodel/models/core/fields/field.rb +154 -0
  79. data/lib/yodel/models/core/fields/fields.rb +29 -0
  80. data/lib/yodel/models/core/fields/fields_field.rb +31 -0
  81. data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
  82. data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
  83. data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
  84. data/lib/yodel/models/core/fields/function_field.rb +28 -0
  85. data/lib/yodel/models/core/fields/hash_field.rb +54 -0
  86. data/lib/yodel/models/core/fields/html_field.rb +15 -0
  87. data/lib/yodel/models/core/fields/image_field.rb +11 -0
  88. data/lib/yodel/models/core/fields/integer_field.rb +25 -0
  89. data/lib/yodel/models/core/fields/password_field.rb +21 -0
  90. data/lib/yodel/models/core/fields/self_field.rb +27 -0
  91. data/lib/yodel/models/core/fields/string_field.rb +15 -0
  92. data/lib/yodel/models/core/fields/tags_field.rb +7 -0
  93. data/lib/yodel/models/core/fields/text_field.rb +7 -0
  94. data/lib/yodel/models/core/fields/time_field.rb +36 -0
  95. data/lib/yodel/models/core/functions/function.rb +471 -0
  96. data/lib/yodel/models/core/functions/functions.rb +2 -0
  97. data/lib/yodel/models/core/functions/trigger.rb +14 -0
  98. data/lib/yodel/models/core/log/log.rb +33 -0
  99. data/lib/yodel/models/core/log/log_entry.rb +12 -0
  100. data/lib/yodel/models/core/model/abstract_model.rb +59 -0
  101. data/lib/yodel/models/core/model/model.rb +460 -0
  102. data/lib/yodel/models/core/model/mongo_model.rb +25 -0
  103. data/lib/yodel/models/core/model/site_model.rb +17 -0
  104. data/lib/yodel/models/core/mongo/mongo.rb +3 -0
  105. data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
  106. data/lib/yodel/models/core/mongo/query.rb +68 -0
  107. data/lib/yodel/models/core/mongo/record_index.rb +89 -0
  108. data/lib/yodel/models/core/record/abstract_record.rb +411 -0
  109. data/lib/yodel/models/core/record/embedded_record.rb +47 -0
  110. data/lib/yodel/models/core/record/mongo_record.rb +83 -0
  111. data/lib/yodel/models/core/record/record.rb +386 -0
  112. data/lib/yodel/models/core/record/section.rb +21 -0
  113. data/lib/yodel/models/core/record/site_record.rb +31 -0
  114. data/lib/yodel/models/core/site/migration.rb +52 -0
  115. data/lib/yodel/models/core/site/remote.rb +61 -0
  116. data/lib/yodel/models/core/site/site.rb +202 -0
  117. data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
  118. data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
  119. data/lib/yodel/models/core/validations/errors.rb +51 -0
  120. data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
  121. data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
  122. data/lib/yodel/models/core/validations/format_validation.rb +10 -0
  123. data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
  124. data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
  125. data/lib/yodel/models/core/validations/length_validation.rb +28 -0
  126. data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
  127. data/lib/yodel/models/core/validations/required_validation.rb +9 -0
  128. data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
  129. data/lib/yodel/models/core/validations/validation.rb +39 -0
  130. data/lib/yodel/models/core/validations/validations.rb +15 -0
  131. data/lib/yodel/models/email/email.rb +79 -0
  132. data/lib/yodel/models/migrations/01_record_model.rb +29 -0
  133. data/lib/yodel/models/migrations/02_page_model.rb +45 -0
  134. data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
  135. data/lib/yodel/models/migrations/04_group_model.rb +61 -0
  136. data/lib/yodel/models/migrations/05_user_model.rb +24 -0
  137. data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
  138. data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
  139. data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
  140. data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
  141. data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
  142. data/lib/yodel/models/migrations/11_email_model.rb +28 -0
  143. data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
  144. data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
  145. data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
  146. data/lib/yodel/models/models.rb +8 -0
  147. data/lib/yodel/models/pages/form_builder.rb +379 -0
  148. data/lib/yodel/models/pages/html_decorator.rb +132 -0
  149. data/lib/yodel/models/pages/layout.rb +120 -0
  150. data/lib/yodel/models/pages/menu.rb +32 -0
  151. data/lib/yodel/models/pages/page.rb +378 -0
  152. data/lib/yodel/models/pages/pages.rb +7 -0
  153. data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
  154. data/lib/yodel/models/pages/redirect_page.rb +11 -0
  155. data/lib/yodel/models/search/search.rb +1 -0
  156. data/lib/yodel/models/search/search_page.rb +58 -0
  157. data/lib/yodel/models/security/facebook_login_page.rb +55 -0
  158. data/lib/yodel/models/security/group.rb +10 -0
  159. data/lib/yodel/models/security/guests_group.rb +5 -0
  160. data/lib/yodel/models/security/login_page.rb +20 -0
  161. data/lib/yodel/models/security/logout_page.rb +13 -0
  162. data/lib/yodel/models/security/noone_group.rb +5 -0
  163. data/lib/yodel/models/security/owner_group.rb +8 -0
  164. data/lib/yodel/models/security/password.rb +5 -0
  165. data/lib/yodel/models/security/password_reset_page.rb +47 -0
  166. data/lib/yodel/models/security/security.rb +10 -0
  167. data/lib/yodel/models/security/user.rb +33 -0
  168. data/lib/yodel/public/core/css/core.css +257 -0
  169. data/lib/yodel/public/core/css/reset.css +48 -0
  170. data/lib/yodel/public/core/images/cross.png +0 -0
  171. data/lib/yodel/public/core/images/spinner.gif +0 -0
  172. data/lib/yodel/public/core/images/tick.png +0 -0
  173. data/lib/yodel/public/core/images/yodel.png +0 -0
  174. data/lib/yodel/public/core/js/jquery.min.js +18 -0
  175. data/lib/yodel/public/core/js/json2.js +480 -0
  176. data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
  177. data/lib/yodel/request/authentication.rb +76 -0
  178. data/lib/yodel/request/flash.rb +28 -0
  179. data/lib/yodel/request/request.rb +4 -0
  180. data/lib/yodel/requires.rb +47 -0
  181. data/lib/yodel/task_queue/queue_daemon.rb +33 -0
  182. data/lib/yodel/task_queue/queue_worker.rb +32 -0
  183. data/lib/yodel/task_queue/stats_thread.rb +27 -0
  184. data/lib/yodel/task_queue/task.rb +62 -0
  185. data/lib/yodel/task_queue/task_queue.rb +40 -0
  186. data/lib/yodel/types/date.rb +5 -0
  187. data/lib/yodel/types/object_id.rb +11 -0
  188. data/lib/yodel/types/time.rb +5 -0
  189. data/lib/yodel/version.rb +3 -0
  190. data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
  191. data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
  192. data/system/etc/resolver/yodel +2 -0
  193. data/system/usr/local/bin/yodel_command_runner +2 -0
  194. data/system/usr/local/etc/yodel/development_settings.rb +28 -0
  195. data/system/usr/local/etc/yodel/production_settings.rb +27 -0
  196. data/system/var/log/yodel.log +0 -0
  197. data/test/helper.rb +18 -0
  198. data/test/test_yodel.rb +4 -0
  199. data/yodel.gemspec +47 -0
  200. metadata +501 -0
@@ -0,0 +1,37 @@
1
+ class Association < Field
2
+ def json_action(action, value, record)
3
+ store = record.get_raw(name)
4
+
5
+ case action
6
+ when 'set'
7
+ clear(store, record)
8
+ process_json_items(value, record, store, :associate)
9
+ when 'add'
10
+ process_json_items(value, record, store, :associate)
11
+ when 'remove'
12
+ process_json_items(value, record, store, :unassociate)
13
+ when 'clear'
14
+ clear(store, record)
15
+ end
16
+
17
+ record.changed!(name)
18
+ end
19
+
20
+ def from_json(value, record)
21
+ store = record.get_raw(name)
22
+ clear(store, record)
23
+ process_json_items(value, record, store, :associate)
24
+ record.get(name)
25
+ end
26
+
27
+
28
+ private
29
+ def process_json_items(items, record, store, method)
30
+ items = [items] unless items.is_a?(Array)
31
+ items.each do |raw_item|
32
+ item = process_json_item(raw_item, store, record)
33
+ next if item.nil?
34
+ send(method, item, store, record)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ require './model/abstract_model'
2
+
3
+ Dir.chdir(File.dirname(__FILE__)) do
4
+ require './association'
5
+ require './record_association'
6
+
7
+ require './counts/many_association'
8
+ require './counts/one_association'
9
+
10
+ require './query/query_association'
11
+ require './query/many_query_association'
12
+ require './query/one_query_association'
13
+
14
+ require './store/store_association'
15
+ require './store/many_store_association'
16
+ require './store/one_store_association'
17
+
18
+ require './embedded/embedded_record_array'
19
+ require './embedded/embedded_association'
20
+ require './embedded/many_embedded_association'
21
+ require './embedded/one_embedded_association'
22
+ end
@@ -0,0 +1,18 @@
1
+ module ManyAssociation
2
+ def search_terms_set(record)
3
+ return [] unless include_in_search_keywords?
4
+ record.get(name).collect do |embedded_record|
5
+ embedded_record.search_terms
6
+ end.flatten
7
+ end
8
+
9
+ def before_destroy(record)
10
+ if @options['destroy'] == true
11
+ record.get(name).each(&:destroy)
12
+ end
13
+ end
14
+
15
+ def typecast(value, record)
16
+ ChangeSensitiveArray.new(record, name, all(value, record))
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module OneAssociation
2
+ def search_terms_set(record)
3
+ return [] unless include_in_search_keywords?
4
+ record.get(name).try(:search_terms) || []
5
+ end
6
+
7
+ def before_destroy(record)
8
+ if @options['destroy'] == true
9
+ record.get(name).try(:destroy)
10
+ end
11
+ end
12
+
13
+ def typecast(value, record)
14
+ return default if value.blank?
15
+ associated(value, record)
16
+ end
17
+
18
+ private
19
+ def clear(store, record)
20
+ unassociate(associated(store, record), store, record)
21
+ end
22
+ end
@@ -0,0 +1,47 @@
1
+ module EmbeddedAssociation
2
+ include AbstractModel
3
+
4
+ def fields_field
5
+ @fields_field ||= FieldsField.new(name)
6
+ end
7
+
8
+ def fields
9
+ @fields ||= fields_field.typecast(@options['fields'], nil)
10
+ end
11
+
12
+ def options
13
+ @options.merge({'fields' => fields_field.untypecast(fields, nil)})
14
+ end
15
+
16
+ def validate(record, errors)
17
+ EmbeddedRecordsValidation.validate(self, record.get(name), record, errors)
18
+ super(record, errors)
19
+ end
20
+
21
+ def default_input_type
22
+ :embedded
23
+ end
24
+
25
+ private
26
+ def process_json_item(embedded_record, store, record)
27
+ return unless embedded_record.respond_to?(:to_hash)
28
+ EmbeddedRecord.new(self, record, {}).tap {|new_record| new_record.from_json(embedded_record)}
29
+ end
30
+
31
+ def clear(store, record)
32
+ if store.is_a?(Array)
33
+ store.clear
34
+ else
35
+ record.set_raw(name, {})
36
+ return {}
37
+ end
38
+ end
39
+
40
+ def all(store, record)
41
+ return [] if store.blank?
42
+ store = [store] unless store.respond_to?(:collect)
43
+ store.collect do |values|
44
+ EmbeddedRecord.new(self, record, values, false)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ class EmbeddedRecordArray < ChangeSensitiveArray
2
+ def new(values={})
3
+ EmbeddedRecord.new(@field, @record).tap {|record| record.update(values, false)}
4
+ end
5
+
6
+ private
7
+ def notify!
8
+ return if @notified
9
+ @record.try(:changed!, @field.name)
10
+ @notified = true
11
+ end
12
+ end
@@ -0,0 +1,62 @@
1
+ class ManyEmbeddedAssociation < Association
2
+ include EmbeddedAssociation
3
+ include ManyAssociation
4
+
5
+ # remove ManyAssociation's destroy behaviour since embedded
6
+ # records are destroyed as part of the parent anyway
7
+ undef :before_destroy
8
+
9
+ def search_terms_set(record)
10
+ record.get(name).collect do |embedded_record|
11
+ embedded_record.search_terms
12
+ end.flatten
13
+ end
14
+
15
+ def options
16
+ super.merge({'type' => 'many_embedded'})
17
+ end
18
+
19
+ def save(embedded_record, parent_record)
20
+ if embedded_record.new?
21
+ parent_record.get(name) << embedded_record
22
+ end
23
+ end
24
+
25
+ def destroy(embedded_record, parent_record)
26
+ parent_record.get(name).delete(embedded_record)
27
+ end
28
+
29
+ def associate(embedded_record, store, record)
30
+ raise "Associated record must be an Embedded Record" unless embedded_record.is_a?(EmbeddedRecord)
31
+ embedded_record.save
32
+ store << embedded_record.values
33
+ end
34
+
35
+ def unassociate(embedded_record, store, record)
36
+ store.delete(embedded_record)
37
+ end
38
+
39
+ def untypecast(value, record)
40
+ return nil if value.blank?
41
+ raise "ManyEmbeddedAssociation values must be enumerable (#{name})" unless value.respond_to?(:each)
42
+
43
+ store = record.get_raw(name)
44
+ clear(store, record)
45
+ value.each do |associated_record|
46
+ associate(associated_record, store, record)
47
+ end
48
+ store
49
+ end
50
+
51
+ def default
52
+ @options['default'] || []
53
+ end
54
+
55
+ def typecast(value, record)
56
+ return EmbeddedRecordArray.new(record, self, []) if value.blank?
57
+ raise "ManyEmbedded values must be enumerable (#{name})" unless value.respond_to?(:each)
58
+ EmbeddedRecordArray.new(record, self, all(value, record))
59
+ end
60
+ end
61
+
62
+ Field::TYPES['many_embedded'] = ManyEmbeddedAssociation
@@ -0,0 +1,49 @@
1
+ class OneEmbeddedAssociation < Association
2
+ include EmbeddedAssociation
3
+ include OneAssociation
4
+
5
+ # remove OneAssociation's destroy behaviour since embedded
6
+ # records are destroyed as part of the parent anyway
7
+ undef :before_destroy
8
+
9
+ def search_terms_set(record)
10
+ record.get(name).search_terms
11
+ end
12
+
13
+ def default
14
+ @options['default'] || EmbeddedRecord.new(self, nil).default_values
15
+ end
16
+
17
+ def options
18
+ super.merge({'type' => 'one_embedded'})
19
+ end
20
+
21
+ def save(embedded_record, parent_record)
22
+ # noop
23
+ end
24
+
25
+ def destroy(embedded_record, parent_record)
26
+ embedded_record.initialize(self, parent_record)
27
+ parent_record.changed!(name)
28
+ end
29
+
30
+ def untypecast(value, record)
31
+ return {} unless value.is_a?(EmbeddedRecord)
32
+ value.save
33
+ value.values
34
+ end
35
+
36
+ private
37
+ def associated(store, record)
38
+ store = {} unless store.is_a?(Hash)
39
+ EmbeddedRecord.new(self, record, store, false)
40
+ end
41
+
42
+ def associate(embedded_record, store, record)
43
+ raise "Associated record must be an Embedded Record" unless embedded_record.is_a?(EmbeddedRecord)
44
+ embedded_record.save
45
+ record.set(name, embedded_record)
46
+ end
47
+ end
48
+
49
+ Field::TYPES['one_embedded'] = OneEmbeddedAssociation
@@ -0,0 +1,10 @@
1
+ class ManyQueryAssociation < Association
2
+ include QueryAssociation
3
+ include ManyAssociation
4
+
5
+ def untypecast(value, record)
6
+ nil
7
+ end
8
+ end
9
+
10
+ Field::TYPES['many_query'] = ManyQueryAssociation
@@ -0,0 +1,10 @@
1
+ class OneQueryAssociation < Association
2
+ include QueryAssociation
3
+ include OneAssociation
4
+
5
+ def untypecast(value, record)
6
+ nil
7
+ end
8
+ end
9
+
10
+ Field::TYPES['one_query'] = OneQueryAssociation
@@ -0,0 +1,64 @@
1
+ module QueryAssociation
2
+ include RecordAssociation
3
+
4
+ def initialize(name, options={})
5
+ super
6
+ if @options['where']
7
+ @options['where'] = Plucky::CriteriaHash.new(@options['where']).to_hash
8
+ end
9
+ end
10
+
11
+ def validate(record, errors)
12
+ # noop
13
+ end
14
+
15
+ def default
16
+ nil
17
+ end
18
+
19
+ def strip_nil?
20
+ true
21
+ end
22
+
23
+ def associate(associated_record, store, record)
24
+ associated_record.set_meta(foreign_key(record), record.id)
25
+ associated_record.save_without_validation
26
+ end
27
+
28
+ def unassociate(associated_record, store, record)
29
+ return unless associated_record.get_meta(foreign_key) == record.id
30
+ associated_record.set_meta(foreign_key(record), nil)
31
+ associated_record.save_without_validation
32
+ end
33
+
34
+ protected
35
+ def clear(store, record)
36
+ all(store, record).each {|associated_record| unassociate(associated_record, store, record)}
37
+ end
38
+
39
+ def all(store, record)
40
+ scope(record).all
41
+ end
42
+
43
+ def scope(record)
44
+ if @options['through']
45
+ through_query = record.field(@options['through'].to_s).scope(record).only('_id')
46
+ cursor = Record.collection.find(through_query.criteria.to_hash, through_query.options.to_hash)
47
+ ids = cursor.to_a.collect {|doc| doc['_id']}
48
+ scope = record.site.model(model_name).where(foreign_key(record) => ids)
49
+ elsif @options['extends']
50
+ scope = record.field(@options['extends'].to_s).scope(record)
51
+ else
52
+ scope = record.site.model(model_name).where(foreign_key(record) => record.id)
53
+ end
54
+ scope = scope.where(@options['where'].to_hash) if @options['where']
55
+ scope = scope.sort(@options['order'].to_s) if @options['order']
56
+ scope = scope.limit(@options['limit'].to_i) if @options['limit']
57
+ scope = scope.skip(@options['skip'].to_i) if @options['skip']
58
+ scope
59
+ end
60
+
61
+ def associated(store, record)
62
+ record.site.model(model_name).first(foreign_key(record) => record.id)
63
+ end
64
+ end
@@ -0,0 +1,38 @@
1
+ module RecordAssociation
2
+ private
3
+ def process_json_item(raw_id, store, record)
4
+ return nil if raw_id.blank?
5
+ associated_record = model(record).find(BSON::ObjectId.from_string(raw_id))
6
+ if !associated_record.nil?
7
+ if model_name == 'Model'
8
+ return associated_record if associated_record.is_a?(Model)
9
+ elsif associated_record.model.ancestors.include?(model(record))
10
+ return associated_record
11
+ end
12
+ end
13
+ nil
14
+ end
15
+
16
+ def foreign_key(record)
17
+ @foreign_key ||= (options['foreign_key'] || model_name.to_s.underscore) # FIXME: should be foreign_key || record.model.name.underscore
18
+ end
19
+
20
+ def model_name
21
+ @model_name ||= (options['model'] || name).to_s.classify
22
+ end
23
+
24
+ def model(record)
25
+ # caching was causing a bug where running migrations on a new site would fail because
26
+ # calls to NewModel.parent were generating the query:
27
+ # {id: parent_id, _site_id: yodel_dev_site_id NOT new_site.id}
28
+ #@model ||=
29
+ case model_name
30
+ when 'Site'
31
+ Site
32
+ when 'Remote'
33
+ Remote
34
+ else
35
+ record.site.model(model_name)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ class ManyStoreAssociation < Association
2
+ include StoreAssociation
3
+ include ManyAssociation
4
+
5
+ def default_input_type
6
+ :store_many
7
+ end
8
+
9
+ def typecast(value, record)
10
+ return ChangeSensitiveArray.new(record, name, []) if value.blank?
11
+ raise "ManyStoreAssociation values must be enumerable (#{name})" unless value.respond_to?(:each)
12
+ ChangeSensitiveArray.new(record, name, all(value, record))
13
+ end
14
+
15
+ def untypecast(value, record)
16
+ return nil if value.blank?
17
+ raise "ManyStoreAssociation values must be enumerable (#{name})" unless value.respond_to?(:each)
18
+
19
+ store = record.get_raw(name) || []
20
+ store.clear
21
+ value.each do |associated_record|
22
+ store << associated_record.id
23
+ end
24
+ store
25
+ end
26
+
27
+ def default
28
+ @options['default'] || []
29
+ end
30
+ end
31
+
32
+ Field::TYPES['many_store'] = ManyStoreAssociation
@@ -0,0 +1,14 @@
1
+ class OneStoreAssociation < Association
2
+ include StoreAssociation
3
+ include OneAssociation
4
+
5
+ def default_input_type
6
+ :store_one
7
+ end
8
+
9
+ def untypecast(value, record)
10
+ value.respond_to?(:id) ? value.id : nil
11
+ end
12
+ end
13
+
14
+ Field::TYPES['one_store'] = OneStoreAssociation