yodel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|