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.
- data/.document +5 -0
- data/.gitignore +9 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +63 -0
- data/LICENSE +1 -0
- data/README.rdoc +20 -0
- data/Rakefile +1 -0
- data/bin/yodel +4 -0
- data/lib/yodel.rb +22 -0
- data/lib/yodel/application/application.rb +44 -0
- data/lib/yodel/application/extension.rb +59 -0
- data/lib/yodel/application/request_handler.rb +48 -0
- data/lib/yodel/application/yodel.rb +25 -0
- data/lib/yodel/command/command.rb +94 -0
- data/lib/yodel/command/deploy.rb +67 -0
- data/lib/yodel/command/dns_server.rb +16 -0
- data/lib/yodel/command/installer.rb +229 -0
- data/lib/yodel/config/config.rb +30 -0
- data/lib/yodel/config/environment.rb +16 -0
- data/lib/yodel/config/yodel.rb +21 -0
- data/lib/yodel/exceptions/destroyed_record.rb +2 -0
- data/lib/yodel/exceptions/domain_not_found.rb +16 -0
- data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
- data/lib/yodel/exceptions/exceptions.rb +3 -0
- data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
- data/lib/yodel/exceptions/invalid_field.rb +2 -0
- data/lib/yodel/exceptions/invalid_index.rb +2 -0
- data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
- data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
- data/lib/yodel/exceptions/layout_not_found.rb +2 -0
- data/lib/yodel/exceptions/mass_assignment.rb +2 -0
- data/lib/yodel/exceptions/missing_migration.rb +2 -0
- data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
- data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
- data/lib/yodel/exceptions/unauthorised.rb +2 -0
- data/lib/yodel/exceptions/unknown_field.rb +2 -0
- data/lib/yodel/middleware/development_server.rb +180 -0
- data/lib/yodel/middleware/error_pages.rb +72 -0
- data/lib/yodel/middleware/public_assets.rb +78 -0
- data/lib/yodel/middleware/request.rb +16 -0
- data/lib/yodel/middleware/site_detector.rb +22 -0
- data/lib/yodel/mime_types/default_mime_set.rb +28 -0
- data/lib/yodel/mime_types/mime_type.rb +68 -0
- data/lib/yodel/mime_types/mime_type_set.rb +41 -0
- data/lib/yodel/mime_types/mime_types.rb +6 -0
- data/lib/yodel/mime_types/yodel.rb +15 -0
- data/lib/yodel/models/api/api.rb +1 -0
- data/lib/yodel/models/api/api_call.rb +87 -0
- data/lib/yodel/models/core/associations/association.rb +37 -0
- data/lib/yodel/models/core/associations/associations.rb +22 -0
- data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
- data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
- data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
- data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
- data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
- data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
- data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
- data/lib/yodel/models/core/associations/record_association.rb +38 -0
- data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
- data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
- data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
- data/lib/yodel/models/core/attachments/attachment.rb +73 -0
- data/lib/yodel/models/core/attachments/image.rb +38 -0
- data/lib/yodel/models/core/core.rb +15 -0
- data/lib/yodel/models/core/fields/alias_field.rb +32 -0
- data/lib/yodel/models/core/fields/array_field.rb +64 -0
- data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
- data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
- data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
- data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
- data/lib/yodel/models/core/fields/color_field.rb +4 -0
- data/lib/yodel/models/core/fields/date_field.rb +35 -0
- data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
- data/lib/yodel/models/core/fields/email_field.rb +10 -0
- data/lib/yodel/models/core/fields/enum_field.rb +33 -0
- data/lib/yodel/models/core/fields/field.rb +154 -0
- data/lib/yodel/models/core/fields/fields.rb +29 -0
- data/lib/yodel/models/core/fields/fields_field.rb +31 -0
- data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
- data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
- data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
- data/lib/yodel/models/core/fields/function_field.rb +28 -0
- data/lib/yodel/models/core/fields/hash_field.rb +54 -0
- data/lib/yodel/models/core/fields/html_field.rb +15 -0
- data/lib/yodel/models/core/fields/image_field.rb +11 -0
- data/lib/yodel/models/core/fields/integer_field.rb +25 -0
- data/lib/yodel/models/core/fields/password_field.rb +21 -0
- data/lib/yodel/models/core/fields/self_field.rb +27 -0
- data/lib/yodel/models/core/fields/string_field.rb +15 -0
- data/lib/yodel/models/core/fields/tags_field.rb +7 -0
- data/lib/yodel/models/core/fields/text_field.rb +7 -0
- data/lib/yodel/models/core/fields/time_field.rb +36 -0
- data/lib/yodel/models/core/functions/function.rb +471 -0
- data/lib/yodel/models/core/functions/functions.rb +2 -0
- data/lib/yodel/models/core/functions/trigger.rb +14 -0
- data/lib/yodel/models/core/log/log.rb +33 -0
- data/lib/yodel/models/core/log/log_entry.rb +12 -0
- data/lib/yodel/models/core/model/abstract_model.rb +59 -0
- data/lib/yodel/models/core/model/model.rb +460 -0
- data/lib/yodel/models/core/model/mongo_model.rb +25 -0
- data/lib/yodel/models/core/model/site_model.rb +17 -0
- data/lib/yodel/models/core/mongo/mongo.rb +3 -0
- data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
- data/lib/yodel/models/core/mongo/query.rb +68 -0
- data/lib/yodel/models/core/mongo/record_index.rb +89 -0
- data/lib/yodel/models/core/record/abstract_record.rb +411 -0
- data/lib/yodel/models/core/record/embedded_record.rb +47 -0
- data/lib/yodel/models/core/record/mongo_record.rb +83 -0
- data/lib/yodel/models/core/record/record.rb +386 -0
- data/lib/yodel/models/core/record/section.rb +21 -0
- data/lib/yodel/models/core/record/site_record.rb +31 -0
- data/lib/yodel/models/core/site/migration.rb +52 -0
- data/lib/yodel/models/core/site/remote.rb +61 -0
- data/lib/yodel/models/core/site/site.rb +202 -0
- data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
- data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
- data/lib/yodel/models/core/validations/errors.rb +51 -0
- data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
- data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
- data/lib/yodel/models/core/validations/format_validation.rb +10 -0
- data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
- data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
- data/lib/yodel/models/core/validations/length_validation.rb +28 -0
- data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
- data/lib/yodel/models/core/validations/required_validation.rb +9 -0
- data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
- data/lib/yodel/models/core/validations/validation.rb +39 -0
- data/lib/yodel/models/core/validations/validations.rb +15 -0
- data/lib/yodel/models/email/email.rb +79 -0
- data/lib/yodel/models/migrations/01_record_model.rb +29 -0
- data/lib/yodel/models/migrations/02_page_model.rb +45 -0
- data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
- data/lib/yodel/models/migrations/04_group_model.rb +61 -0
- data/lib/yodel/models/migrations/05_user_model.rb +24 -0
- data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
- data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
- data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
- data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
- data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
- data/lib/yodel/models/migrations/11_email_model.rb +28 -0
- data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
- data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
- data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
- data/lib/yodel/models/models.rb +8 -0
- data/lib/yodel/models/pages/form_builder.rb +379 -0
- data/lib/yodel/models/pages/html_decorator.rb +132 -0
- data/lib/yodel/models/pages/layout.rb +120 -0
- data/lib/yodel/models/pages/menu.rb +32 -0
- data/lib/yodel/models/pages/page.rb +378 -0
- data/lib/yodel/models/pages/pages.rb +7 -0
- data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
- data/lib/yodel/models/pages/redirect_page.rb +11 -0
- data/lib/yodel/models/search/search.rb +1 -0
- data/lib/yodel/models/search/search_page.rb +58 -0
- data/lib/yodel/models/security/facebook_login_page.rb +55 -0
- data/lib/yodel/models/security/group.rb +10 -0
- data/lib/yodel/models/security/guests_group.rb +5 -0
- data/lib/yodel/models/security/login_page.rb +20 -0
- data/lib/yodel/models/security/logout_page.rb +13 -0
- data/lib/yodel/models/security/noone_group.rb +5 -0
- data/lib/yodel/models/security/owner_group.rb +8 -0
- data/lib/yodel/models/security/password.rb +5 -0
- data/lib/yodel/models/security/password_reset_page.rb +47 -0
- data/lib/yodel/models/security/security.rb +10 -0
- data/lib/yodel/models/security/user.rb +33 -0
- data/lib/yodel/public/core/css/core.css +257 -0
- data/lib/yodel/public/core/css/reset.css +48 -0
- data/lib/yodel/public/core/images/cross.png +0 -0
- data/lib/yodel/public/core/images/spinner.gif +0 -0
- data/lib/yodel/public/core/images/tick.png +0 -0
- data/lib/yodel/public/core/images/yodel.png +0 -0
- data/lib/yodel/public/core/js/jquery.min.js +18 -0
- data/lib/yodel/public/core/js/json2.js +480 -0
- data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
- data/lib/yodel/request/authentication.rb +76 -0
- data/lib/yodel/request/flash.rb +28 -0
- data/lib/yodel/request/request.rb +4 -0
- data/lib/yodel/requires.rb +47 -0
- data/lib/yodel/task_queue/queue_daemon.rb +33 -0
- data/lib/yodel/task_queue/queue_worker.rb +32 -0
- data/lib/yodel/task_queue/stats_thread.rb +27 -0
- data/lib/yodel/task_queue/task.rb +62 -0
- data/lib/yodel/task_queue/task_queue.rb +40 -0
- data/lib/yodel/types/date.rb +5 -0
- data/lib/yodel/types/object_id.rb +11 -0
- data/lib/yodel/types/time.rb +5 -0
- data/lib/yodel/version.rb +3 -0
- data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
- data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
- data/system/etc/resolver/yodel +2 -0
- data/system/usr/local/bin/yodel_command_runner +2 -0
- data/system/usr/local/etc/yodel/development_settings.rb +28 -0
- data/system/usr/local/etc/yodel/production_settings.rb +27 -0
- data/system/var/log/yodel.log +0 -0
- data/test/helper.rb +18 -0
- data/test/test_yodel.rb +4 -0
- data/yodel.gemspec +47 -0
- 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,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
|