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,53 @@
|
|
|
1
|
+
class ChangeSensitiveHash
|
|
2
|
+
attr_reader :hash
|
|
3
|
+
def initialize(record, field, hash)
|
|
4
|
+
@record = record
|
|
5
|
+
@field = field
|
|
6
|
+
@hash = hash
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def inspect
|
|
10
|
+
@hash.inspect
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_hash
|
|
14
|
+
@hash
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def merge!(other_hash)
|
|
18
|
+
notify!
|
|
19
|
+
@hash.merge!(other_hash)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete(key)
|
|
23
|
+
notify!
|
|
24
|
+
@hash.delete(key)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def clear
|
|
28
|
+
notify!
|
|
29
|
+
@hash.clear
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def []=(key, value)
|
|
33
|
+
notify!
|
|
34
|
+
@hash[key] = value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def method_missing(name, *args, &block)
|
|
38
|
+
notify! if name.to_s.end_with?('!')
|
|
39
|
+
@hash.send(name, *args, &block)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# See ChangeSensitiveArray#dup for an explanation of this method
|
|
43
|
+
def dup
|
|
44
|
+
copy = ChangeSensitiveHash.new(@record.dup, @field.dup, @hash.dup)
|
|
45
|
+
@record.typecast[@field] = copy
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
def notify!
|
|
51
|
+
@record.try(:changed!, @field)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class DateField < Field
|
|
2
|
+
def default_input_type
|
|
3
|
+
:date
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def before_create(record)
|
|
7
|
+
return unless name == 'created_at' || name == 'updated_at'
|
|
8
|
+
record.set(name, Time.now.utc.to_date)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def before_update(record)
|
|
12
|
+
return unless name == 'updated_at'
|
|
13
|
+
record.set(name, Time.now.utc.to_date)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def typecast(value, record)
|
|
17
|
+
value.blank? ? nil : value.to_date
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def untypecast(value, record)
|
|
21
|
+
value.blank? ? nil : Time.utc(value.year, value.month, value.day)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def from_json(value, record)
|
|
25
|
+
return nil unless value.present? && (value.is_a?(String) || value.is_a?(Hash))
|
|
26
|
+
if value.is_a?(Hash)
|
|
27
|
+
return nil unless ['year', 'month', 'day'].all? {|field| value.key?(field) && !value[field].blank?}
|
|
28
|
+
Time.new(value['year'], value['month'], value['day'])
|
|
29
|
+
else
|
|
30
|
+
Time.parse(value)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Field::TYPES['date'] = DateField
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class DecimalField < Field
|
|
2
|
+
def numeric?
|
|
3
|
+
true
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def typecast(value, record)
|
|
7
|
+
BigDecimal.new(value.to_s)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def untypecast(value, record)
|
|
11
|
+
BigDecimal.new(value.to_s).to_s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def from_json(value, record)
|
|
15
|
+
BigDecimal.new(value.to_s).to_s
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Field::TYPES['decimal'] = DecimalField
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class EnumField < StringField
|
|
2
|
+
# class << self
|
|
3
|
+
# undef search_terms_set
|
|
4
|
+
# end
|
|
5
|
+
#
|
|
6
|
+
# def self.to_html_field(record, field, value)
|
|
7
|
+
# if value.nil? && !field.default.nil?
|
|
8
|
+
# value = field.default
|
|
9
|
+
# end
|
|
10
|
+
# value = value.to_s
|
|
11
|
+
#
|
|
12
|
+
# select_options = field.values.collect do |enum_value|
|
|
13
|
+
# options = {value: enum_value}
|
|
14
|
+
# options[:selected] = 'selected' if value == enum_value
|
|
15
|
+
# Hpricot::Elem.new('option', options, [Hpricot::Text.new(enum_value)])
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# if field.show_blank || !field.required
|
|
19
|
+
# options = {value: ''}
|
|
20
|
+
# text = field.blank_text || 'None'
|
|
21
|
+
# options[:selected] = 'selected' if value == ''
|
|
22
|
+
# select_options.unshift(Hpricot::Elem.new('option', options, [Hpricot::Text.new(text)]))
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# Hpricot::Elem.new('select', {name: field.name}, select_options)
|
|
26
|
+
# end
|
|
27
|
+
|
|
28
|
+
def default_input_type
|
|
29
|
+
:enum
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Field::TYPES['enum'] = EnumField
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
class Field
|
|
2
|
+
attr_accessor :name, :options
|
|
3
|
+
TYPES = {}
|
|
4
|
+
|
|
5
|
+
def self.from_options(name, options)
|
|
6
|
+
field_from_type(options['type']).new(name, options)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.field_from_type(type)
|
|
10
|
+
TYPES[type]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# options is a hash of string keys to values as described. Type
|
|
14
|
+
# is the only required option. All others are optional.
|
|
15
|
+
#
|
|
16
|
+
# type => field type
|
|
17
|
+
# default => value (value for newly created records)
|
|
18
|
+
# display => true/false
|
|
19
|
+
# searchable => true/false
|
|
20
|
+
# protected => true/false (unable to mass assign to this field if true)
|
|
21
|
+
# section => string (nil or a section name used in admin)
|
|
22
|
+
#
|
|
23
|
+
# Some field types may have other options, such as embedded fields.
|
|
24
|
+
def initialize(name, options={})
|
|
25
|
+
@name = name
|
|
26
|
+
@options = options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_str
|
|
30
|
+
"#<#{self.class.name} #{name}>"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def display?
|
|
34
|
+
@options['display'] != false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def searchable?
|
|
38
|
+
@options['searchable'] != false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def required?
|
|
42
|
+
@options['required'] == true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def unique?
|
|
46
|
+
@options['unique'] == true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def strip_nil?
|
|
50
|
+
@options['strip_nil'] == true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def index?
|
|
54
|
+
@options['index'] == true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def inherited?
|
|
58
|
+
@options['inherited'] == true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def numeric?
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def include_in_search_keywords?
|
|
66
|
+
@options.key?('include_in_search_keywords')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def method_missing(name, *args, &block)
|
|
70
|
+
@options[name.to_s]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate(record, errors)
|
|
74
|
+
return if @options['validations'].blank?
|
|
75
|
+
value = record.get(name)
|
|
76
|
+
field_name = name.humanize
|
|
77
|
+
@options['validations'].each do |type, params|
|
|
78
|
+
Validation.validate(type, params, self, field_name, value, record, errors)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def default_input_type
|
|
83
|
+
:text
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Convert from an untypecast (raw) representation of a value to a
|
|
87
|
+
# more complex version of the same value. For instance, BigDecimals
|
|
88
|
+
# are stored as strings, but are BigDecimal objects when typecast.
|
|
89
|
+
def typecast(value, record)
|
|
90
|
+
value
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Convert from a complex (typecast) representation of a value to a
|
|
94
|
+
# simpler version of the same value. For instance, BigDecimals are
|
|
95
|
+
# BigDecimal objects when typecast, but are strings when untypecast.
|
|
96
|
+
def untypecast(value, record)
|
|
97
|
+
value
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Example json_action implementation; only implement this method if
|
|
101
|
+
# the field type supports actions other than just 'set'
|
|
102
|
+
# def json_action(action, value, record)
|
|
103
|
+
# record.set_raw(name, from_json(value, record))
|
|
104
|
+
# end
|
|
105
|
+
|
|
106
|
+
# Take a raw JSON value and encode it in an untypecast (raw) value
|
|
107
|
+
# ready for storage in a mongo record. Non mongo records can still
|
|
108
|
+
# use this method since it performs complex->simple type conversion.
|
|
109
|
+
def from_json(value, record)
|
|
110
|
+
value
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# JSON converter for the field object itself; this method does not
|
|
114
|
+
# encode a field's value. Fields convert values to and from mongo
|
|
115
|
+
# (untypecast/typecast) and from json (from_json). When converting
|
|
116
|
+
# a record to JSON, the raw (untypecast) value is used. If this
|
|
117
|
+
# value needs special encoding for JSON, specify a to_json(*a)
|
|
118
|
+
# method on the class the value is an instance of. For instance,
|
|
119
|
+
# Time fields store values as Time objects in mongo. For conversion
|
|
120
|
+
# to JSON, the Time class is extended with a to_json(*a) method to
|
|
121
|
+
# perform the necessary conversion. This is done so fields can be
|
|
122
|
+
# ignored by the JSON converter, and a simple conversion done between
|
|
123
|
+
# untypecast values and JSON. Since mongo stores values in BSON (which
|
|
124
|
+
# is similar to JSON), this makes conversion much faster.
|
|
125
|
+
def to_json(*a)
|
|
126
|
+
@options.to_json(*a)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Record callbacks. These methods are called at various stages of a
|
|
130
|
+
# record's life cycle. Only implement them when necessary.
|
|
131
|
+
# def before_create(record)
|
|
132
|
+
# end
|
|
133
|
+
#
|
|
134
|
+
# def before_update(record)
|
|
135
|
+
# end
|
|
136
|
+
#
|
|
137
|
+
# def before_save(record)
|
|
138
|
+
# end
|
|
139
|
+
#
|
|
140
|
+
# def before_destroy(record)
|
|
141
|
+
# end
|
|
142
|
+
#
|
|
143
|
+
# def after_create(record)
|
|
144
|
+
# end
|
|
145
|
+
#
|
|
146
|
+
# def after_update(record)
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# def after_save(record)
|
|
150
|
+
# end
|
|
151
|
+
#
|
|
152
|
+
# def after_destroy(record)
|
|
153
|
+
# end
|
|
154
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Dir.chdir(File.dirname(__FILE__)) do
|
|
2
|
+
require './field'
|
|
3
|
+
require './string_field'
|
|
4
|
+
require './text_field'
|
|
5
|
+
require './alias_field'
|
|
6
|
+
require './array_field'
|
|
7
|
+
require './boolean_field'
|
|
8
|
+
require './change_sensitive_array'
|
|
9
|
+
require './change_sensitive_hash'
|
|
10
|
+
require './color_field'
|
|
11
|
+
require './date_field'
|
|
12
|
+
require './decimal_field'
|
|
13
|
+
require './email_field'
|
|
14
|
+
require './enum_field'
|
|
15
|
+
require './fields_field'
|
|
16
|
+
require './filter_mixin'
|
|
17
|
+
require './filtered_string_field'
|
|
18
|
+
require './filtered_text_field'
|
|
19
|
+
require './function_field'
|
|
20
|
+
require './hash_field'
|
|
21
|
+
require './html_field'
|
|
22
|
+
require './integer_field'
|
|
23
|
+
require './password_field'
|
|
24
|
+
require './self_field'
|
|
25
|
+
require './tags_field'
|
|
26
|
+
require './time_field'
|
|
27
|
+
require './attachment_field'
|
|
28
|
+
require './image_field'
|
|
29
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class FieldsField < Field
|
|
2
|
+
def typecast(hash, record)
|
|
3
|
+
ChangeSensitiveHash.new(record, name, fields_from_hash(hash, record))
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def untypecast(hash, record)
|
|
7
|
+
return {} unless hash.respond_to?(:to_hash)
|
|
8
|
+
hash.to_hash.each_with_object({}) do |(name, field_obj), fields|
|
|
9
|
+
next unless field_obj.is_a?(Field)
|
|
10
|
+
fields[name.to_s] = field_obj.options
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def from_json(hash, record)
|
|
15
|
+
# return {} unless hash.is_a?(Hash)
|
|
16
|
+
# hash.each_with_object({}) do |(name, options), fields|
|
|
17
|
+
# fields[name.to_s] = Field.from_options(name, options).options
|
|
18
|
+
# end
|
|
19
|
+
fields_from_hash(hash, record)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
def fields_from_hash(hash, record)
|
|
24
|
+
return {} unless hash.is_a?(Hash)
|
|
25
|
+
hash.each_with_object({}) do |(name, options), fields|
|
|
26
|
+
fields[name.to_s] = Field.from_options(name, options)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Field::TYPES['fields'] = FieldsField
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class FunctionField < Field
|
|
2
|
+
def strip_nil?
|
|
3
|
+
true
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def default_input_type
|
|
7
|
+
nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def validate(record, errors)
|
|
11
|
+
# noop
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def typecast(value, record)
|
|
15
|
+
compiled_fn = Function.new(@options['fn'])
|
|
16
|
+
compiled_fn.execute(record)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def untypecast(value, record)
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def from_json(value, record)
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Field::TYPES['function'] = FunctionField
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class HashField < Field
|
|
2
|
+
# TODO: validate should defer to @element_type over each element
|
|
3
|
+
def initialize(name, options={})
|
|
4
|
+
@element_type = Field.from_options(name, 'type' => options['of'].to_s) if options['of']
|
|
5
|
+
super
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def json_action(action, value, record)
|
|
9
|
+
hash = record.get_raw(name)
|
|
10
|
+
|
|
11
|
+
if action == 'remove'
|
|
12
|
+
value = [value] unless value.is_a?(Array)
|
|
13
|
+
keys = value.collect(&:to_s)
|
|
14
|
+
hash.delete_if {|key, _| keys.include?(key)}
|
|
15
|
+
else
|
|
16
|
+
raise "Set or merge actions on a hash must be passed a hash" unless value.is_a?(Hash)
|
|
17
|
+
value = process(value, record, :from_json)
|
|
18
|
+
if action == 'set'
|
|
19
|
+
hash = value
|
|
20
|
+
elsif action == 'merge'
|
|
21
|
+
hash.merge!(value)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
record.set_raw(name, hash)
|
|
26
|
+
record.changed!(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def typecast(value, record)
|
|
30
|
+
value = {} unless value.is_a?(Hash)
|
|
31
|
+
ChangeSensitiveHash.new(record, name, process(value, record, :typecast))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def untypecast(value, record)
|
|
35
|
+
return {} if value.blank? || !value.respond_to?(:to_hash)
|
|
36
|
+
process(value, record, :untypecast)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def from_json(value, record)
|
|
40
|
+
return {} if value.blank? || !value.is_a?(Hash)
|
|
41
|
+
process(value, record, :from_json)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
def process(hash, record, method)
|
|
47
|
+
return hash.to_hash if @element_type.nil?
|
|
48
|
+
hash.to_hash.each do |key, value|
|
|
49
|
+
hash[key.to_s] = @element_type.send(method, value, record)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Field::TYPES['hash'] = HashField
|