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,51 @@
|
|
|
1
|
+
module StoreAssociation
|
|
2
|
+
include RecordAssociation
|
|
3
|
+
|
|
4
|
+
def associate(associated_record, store, record)
|
|
5
|
+
if store.is_a?(Array)
|
|
6
|
+
store << associated_record.id
|
|
7
|
+
else
|
|
8
|
+
record.set_raw(name, associated_record.id)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def validate(record, errors)
|
|
13
|
+
# noop
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def unassociate(associated_record, store, record)
|
|
17
|
+
if store.is_a?(Array)
|
|
18
|
+
store.delete(associated_record.id)
|
|
19
|
+
else
|
|
20
|
+
record.set_raw(name, nil)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def record_options(record)
|
|
25
|
+
query = model(record).where()
|
|
26
|
+
query = query.sort(@options['order'].to_s) if @options['order']
|
|
27
|
+
query.all
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def clear(store, record)
|
|
33
|
+
if store.is_a?(Array)
|
|
34
|
+
store.clear
|
|
35
|
+
else
|
|
36
|
+
record.set_raw(name, nil)
|
|
37
|
+
return nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def all(store, record)
|
|
42
|
+
query = model(record).where(_id: store)
|
|
43
|
+
query = query.sort(@options['sort']) if @options['sort']
|
|
44
|
+
query.all
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def associated(store, record)
|
|
48
|
+
return nil if store.nil?
|
|
49
|
+
model(record).first(_id: store)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
class Attachment
|
|
2
|
+
attr_accessor :field, :record, :name, :mime
|
|
3
|
+
|
|
4
|
+
def initialize(value, record, field)
|
|
5
|
+
@field = field
|
|
6
|
+
@record = record
|
|
7
|
+
value ||= {}
|
|
8
|
+
@name = value['name']
|
|
9
|
+
@mime = value['mime']
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_hash
|
|
13
|
+
{'name' => name, 'mime' => mime}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def url
|
|
17
|
+
@url ||= Pathname.new('/').join(Yodel::ATTACHMENTS_DIRECTORY_NAME, relative_path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def relative_path
|
|
21
|
+
@relative_path ||= File.join(relative_directory_path, @name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def relative_directory_path
|
|
25
|
+
@relative_directory_path ||= File.join(@field.name, @record.id.to_s)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def path
|
|
29
|
+
@path ||= File.join(@record.site.attachments_directory, relative_path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def directory_path
|
|
33
|
+
@directory_path ||= File.join(@record.site.attachments_directory, relative_directory_path)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def exist?
|
|
37
|
+
return false if @name.nil?
|
|
38
|
+
File.exist?(path)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def remove_files
|
|
42
|
+
FileUtils.rm_r directory_path if exist?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reset_memoised_values
|
|
46
|
+
@url = @relative_path = @relative_directory_path = @path = @directory_path = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def set_file(file)
|
|
50
|
+
# delete the old file and reset memoised paths
|
|
51
|
+
unless @record.new?
|
|
52
|
+
remove_files
|
|
53
|
+
reset_memoised_values
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# reset the name and mime type of the attachment
|
|
57
|
+
@name = file[:filename]
|
|
58
|
+
@mime = file[:type]
|
|
59
|
+
temp = file[:tempfile]
|
|
60
|
+
temp_path = temp.path
|
|
61
|
+
temp.close
|
|
62
|
+
|
|
63
|
+
# for simplicity we move the uploaded file (from /tmp) rather than copying
|
|
64
|
+
FileUtils.mkpath directory_path
|
|
65
|
+
FileUtils.mv(temp_path, path)
|
|
66
|
+
FileUtils.chmod(0664, path) # (owner: rw, group: rw, other: r)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def length
|
|
70
|
+
return 0 unless self.exist?
|
|
71
|
+
return File.size(path)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class Image < Attachment
|
|
2
|
+
def set_file(file)
|
|
3
|
+
super(file)
|
|
4
|
+
crop_image
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def crop_image
|
|
8
|
+
sizes = @field.options['sizes'].to_hash.merge('admin_thumb' => '100x100')
|
|
9
|
+
return unless exist?
|
|
10
|
+
sizes.each do |size_name, size|
|
|
11
|
+
image = MiniMagick::Image.open(path.to_s)
|
|
12
|
+
image.resize("#{size}^")
|
|
13
|
+
image.format('jpeg')
|
|
14
|
+
image.write(resized_image_path(size_name, false).to_s)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# TODO: shouldn't always be .jpg; have image extension as an option
|
|
19
|
+
def resized_image_path(size, crop_if_required=true)
|
|
20
|
+
return path if size.nil? || size == :original
|
|
21
|
+
sized_path = File.join(@record.site.attachments_directory, relative_directory_path, "#{size}.jpg")
|
|
22
|
+
crop_image unless File.exist?(sized_path) || !crop_if_required
|
|
23
|
+
sized_path
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# TODO: relative path from is quite a complex method; we should optimise the whole path system here somehow
|
|
27
|
+
def relative_resized_image_path(name, crop_if_required=true)
|
|
28
|
+
Pathname.new(resized_image_path(name, crop_if_required)).relative_path_from(Pathname.new(@record.site.attachments_directory))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def url(size=:original, crop_if_required=true)
|
|
32
|
+
if size == :original
|
|
33
|
+
super()
|
|
34
|
+
else
|
|
35
|
+
Pathname.new('/').join(relative_resized_image_path(size, crop_if_required))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Dir.chdir(File.dirname(__FILE__)) do
|
|
2
|
+
require './attachments/attachment'
|
|
3
|
+
require './attachments/image'
|
|
4
|
+
require './fields/fields'
|
|
5
|
+
require './validations/validations'
|
|
6
|
+
require './associations/associations'
|
|
7
|
+
require './mongo/mongo'
|
|
8
|
+
require './record/record'
|
|
9
|
+
require './model/model'
|
|
10
|
+
require './functions/functions'
|
|
11
|
+
require './log/log'
|
|
12
|
+
require './site/migration'
|
|
13
|
+
require './site/remote'
|
|
14
|
+
require './site/site'
|
|
15
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class AliasField < Field
|
|
2
|
+
# FIXME: assignments don't work; alias = val won't be saved
|
|
3
|
+
# FIXME: changes to the original value aren't observed;
|
|
4
|
+
# original = val_1, alias => val_1; original = val_2; alias => val_1
|
|
5
|
+
def strip_nil?
|
|
6
|
+
true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def default_input_type
|
|
10
|
+
nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def validate(record, errors)
|
|
14
|
+
# noop
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def typecast(value, record)
|
|
18
|
+
field_name = @options['of'].to_s
|
|
19
|
+
raise InvalidField, "Alias fields must have a from property" if field_name.blank?
|
|
20
|
+
record.get(field_name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def untypecast(value, record)
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def from_json(value, record)
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Field::TYPES['alias'] = AliasField
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class ArrayField < 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.singularize) if options['of']
|
|
5
|
+
super
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def json_action(action, value, record)
|
|
9
|
+
array = record.get_raw(name)
|
|
10
|
+
value = process(value, record, :from_json)
|
|
11
|
+
value = [value] unless value.is_a?(Array)
|
|
12
|
+
|
|
13
|
+
case action
|
|
14
|
+
when 'set'
|
|
15
|
+
array = value
|
|
16
|
+
when 'add'
|
|
17
|
+
array += value
|
|
18
|
+
when 'add_unique'
|
|
19
|
+
array |= value
|
|
20
|
+
when 'remove'
|
|
21
|
+
array -= value
|
|
22
|
+
when 'clear'
|
|
23
|
+
array = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
record.set_raw(name, array)
|
|
27
|
+
record.changed!(name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def typecast(value, record)
|
|
31
|
+
value = [] unless value.is_a?(Array)
|
|
32
|
+
ChangeSensitiveArray.new(record, name, process(value, record, :typecast))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def untypecast(value, record)
|
|
36
|
+
return [] if value.blank?
|
|
37
|
+
process(value.to_a, record, :untypecast)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def from_json(value, record)
|
|
41
|
+
return [] if value.blank?
|
|
42
|
+
if value.is_a?(String)
|
|
43
|
+
if value.include?(',')
|
|
44
|
+
value = value.split(',')
|
|
45
|
+
else
|
|
46
|
+
value = value.split(' ')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
process(value.to_a, record, :from_json)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
def process(values, record, method)
|
|
55
|
+
return values if @element_type.nil?
|
|
56
|
+
if values.is_a?(Array)
|
|
57
|
+
values.collect {|element| @element_type.send(method, element, record)}
|
|
58
|
+
else
|
|
59
|
+
@element_type.send(method, values, record)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Field::TYPES['array'] = ArrayField
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class AttachmentField < Field
|
|
2
|
+
def default_input_type
|
|
3
|
+
:file
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def untypecast(value, record)
|
|
7
|
+
# expecting an Attachment object, or a hash of the original data
|
|
8
|
+
value.to_hash
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def typecast(value, record)
|
|
12
|
+
Attachment.new(value, record, self)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def json_action(action, value, record)
|
|
16
|
+
original = record.get(name)
|
|
17
|
+
case action
|
|
18
|
+
when 'set'
|
|
19
|
+
return if value.nil?
|
|
20
|
+
original.set_file(value)
|
|
21
|
+
record.set_raw(name, original.to_hash)
|
|
22
|
+
when 'clear'
|
|
23
|
+
original.remove_files
|
|
24
|
+
record.set_raw(name, nil)
|
|
25
|
+
end
|
|
26
|
+
record.changed!(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def from_json(value, record)
|
|
30
|
+
original = record.get(name)
|
|
31
|
+
original.set_file(value)
|
|
32
|
+
original
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def after_destroy(record)
|
|
36
|
+
if record.get(name).is_a?(Attachment)
|
|
37
|
+
record.get(name).remove_files
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Field::TYPES['attachment'] = AttachmentField
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class BooleanField < Field
|
|
2
|
+
def default_input_type
|
|
3
|
+
:radio
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def json_action(action, value, record)
|
|
7
|
+
case action
|
|
8
|
+
when 'set'
|
|
9
|
+
record.set_raw(name, !!value)
|
|
10
|
+
when 'toggle'
|
|
11
|
+
record.set_raw(name, !record.get(name))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
record.changed!(name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def from_json(value, record)
|
|
18
|
+
if value == 'true'
|
|
19
|
+
true
|
|
20
|
+
elsif value == 'false'
|
|
21
|
+
false
|
|
22
|
+
else
|
|
23
|
+
!!value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Field::TYPES['boolean'] = BooleanField
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Notify the record owning this value whenever the underlying array
|
|
2
|
+
# changes. Records rely on assignment to determine when a value has
|
|
3
|
+
# changed, so mutable objects need to notify the record when they are
|
|
4
|
+
# updated. This is not an exhaustive list of ways to mutate an array,
|
|
5
|
+
# just some common methods used in Yodel already.
|
|
6
|
+
class ChangeSensitiveArray
|
|
7
|
+
attr_reader :array
|
|
8
|
+
def initialize(record, field, array)
|
|
9
|
+
@record = record
|
|
10
|
+
@field = field
|
|
11
|
+
@array = array
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def inspect
|
|
15
|
+
@array.inspect
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_a
|
|
19
|
+
@array
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_s
|
|
23
|
+
@array.collect(&:to_s).join(', ')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def push(value)
|
|
27
|
+
notify!
|
|
28
|
+
@array.push(value)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def clear
|
|
32
|
+
notify!
|
|
33
|
+
@array.clear
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def pop
|
|
37
|
+
notify!
|
|
38
|
+
@array.pop
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def <<(value)
|
|
42
|
+
notify!
|
|
43
|
+
@array << value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def delete(value)
|
|
47
|
+
notify!
|
|
48
|
+
@array.delete(value)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def []=(index, value)
|
|
52
|
+
notify!
|
|
53
|
+
@array[index] = value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def each(&block)
|
|
57
|
+
@array.each(&block)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def collect(&block)
|
|
61
|
+
@array.collect(&block)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def count(*item, &block)
|
|
65
|
+
@array.count(*item, &block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def size
|
|
69
|
+
@array.size
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def include?(item)
|
|
73
|
+
@array.include?(item)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def method_missing(name, *args, &block)
|
|
77
|
+
notify! if name.to_s.end_with?('!')
|
|
78
|
+
@array.send(name, *args, &block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Calling changed! on @record will call dup on this array before any mutating
|
|
82
|
+
# operation has been performed. We need to store the original unedited version
|
|
83
|
+
# in typecast (the 'was' value), then return this array since the mutating
|
|
84
|
+
# operation is being performed on it. Since dup returns self, the array being
|
|
85
|
+
# operated on will be stored in @record.changed, and be modified by the op.
|
|
86
|
+
def dup
|
|
87
|
+
copy = ChangeSensitiveArray.new(@record.dup, @field.dup, @array.dup)
|
|
88
|
+
@record.typecast[@field] = copy
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
def notify!
|
|
94
|
+
@record.try(:changed!, @field)
|
|
95
|
+
end
|
|
96
|
+
end
|