yodel 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. data/.document +5 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +63 -0
  5. data/LICENSE +1 -0
  6. data/README.rdoc +20 -0
  7. data/Rakefile +1 -0
  8. data/bin/yodel +4 -0
  9. data/lib/yodel.rb +22 -0
  10. data/lib/yodel/application/application.rb +44 -0
  11. data/lib/yodel/application/extension.rb +59 -0
  12. data/lib/yodel/application/request_handler.rb +48 -0
  13. data/lib/yodel/application/yodel.rb +25 -0
  14. data/lib/yodel/command/command.rb +94 -0
  15. data/lib/yodel/command/deploy.rb +67 -0
  16. data/lib/yodel/command/dns_server.rb +16 -0
  17. data/lib/yodel/command/installer.rb +229 -0
  18. data/lib/yodel/config/config.rb +30 -0
  19. data/lib/yodel/config/environment.rb +16 -0
  20. data/lib/yodel/config/yodel.rb +21 -0
  21. data/lib/yodel/exceptions/destroyed_record.rb +2 -0
  22. data/lib/yodel/exceptions/domain_not_found.rb +16 -0
  23. data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
  24. data/lib/yodel/exceptions/exceptions.rb +3 -0
  25. data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
  26. data/lib/yodel/exceptions/invalid_field.rb +2 -0
  27. data/lib/yodel/exceptions/invalid_index.rb +2 -0
  28. data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
  29. data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
  30. data/lib/yodel/exceptions/layout_not_found.rb +2 -0
  31. data/lib/yodel/exceptions/mass_assignment.rb +2 -0
  32. data/lib/yodel/exceptions/missing_migration.rb +2 -0
  33. data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
  34. data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
  35. data/lib/yodel/exceptions/unauthorised.rb +2 -0
  36. data/lib/yodel/exceptions/unknown_field.rb +2 -0
  37. data/lib/yodel/middleware/development_server.rb +180 -0
  38. data/lib/yodel/middleware/error_pages.rb +72 -0
  39. data/lib/yodel/middleware/public_assets.rb +78 -0
  40. data/lib/yodel/middleware/request.rb +16 -0
  41. data/lib/yodel/middleware/site_detector.rb +22 -0
  42. data/lib/yodel/mime_types/default_mime_set.rb +28 -0
  43. data/lib/yodel/mime_types/mime_type.rb +68 -0
  44. data/lib/yodel/mime_types/mime_type_set.rb +41 -0
  45. data/lib/yodel/mime_types/mime_types.rb +6 -0
  46. data/lib/yodel/mime_types/yodel.rb +15 -0
  47. data/lib/yodel/models/api/api.rb +1 -0
  48. data/lib/yodel/models/api/api_call.rb +87 -0
  49. data/lib/yodel/models/core/associations/association.rb +37 -0
  50. data/lib/yodel/models/core/associations/associations.rb +22 -0
  51. data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
  52. data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
  53. data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
  54. data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
  55. data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
  56. data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
  57. data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
  58. data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
  59. data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
  60. data/lib/yodel/models/core/associations/record_association.rb +38 -0
  61. data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
  62. data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
  63. data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
  64. data/lib/yodel/models/core/attachments/attachment.rb +73 -0
  65. data/lib/yodel/models/core/attachments/image.rb +38 -0
  66. data/lib/yodel/models/core/core.rb +15 -0
  67. data/lib/yodel/models/core/fields/alias_field.rb +32 -0
  68. data/lib/yodel/models/core/fields/array_field.rb +64 -0
  69. data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
  70. data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
  71. data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
  72. data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
  73. data/lib/yodel/models/core/fields/color_field.rb +4 -0
  74. data/lib/yodel/models/core/fields/date_field.rb +35 -0
  75. data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
  76. data/lib/yodel/models/core/fields/email_field.rb +10 -0
  77. data/lib/yodel/models/core/fields/enum_field.rb +33 -0
  78. data/lib/yodel/models/core/fields/field.rb +154 -0
  79. data/lib/yodel/models/core/fields/fields.rb +29 -0
  80. data/lib/yodel/models/core/fields/fields_field.rb +31 -0
  81. data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
  82. data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
  83. data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
  84. data/lib/yodel/models/core/fields/function_field.rb +28 -0
  85. data/lib/yodel/models/core/fields/hash_field.rb +54 -0
  86. data/lib/yodel/models/core/fields/html_field.rb +15 -0
  87. data/lib/yodel/models/core/fields/image_field.rb +11 -0
  88. data/lib/yodel/models/core/fields/integer_field.rb +25 -0
  89. data/lib/yodel/models/core/fields/password_field.rb +21 -0
  90. data/lib/yodel/models/core/fields/self_field.rb +27 -0
  91. data/lib/yodel/models/core/fields/string_field.rb +15 -0
  92. data/lib/yodel/models/core/fields/tags_field.rb +7 -0
  93. data/lib/yodel/models/core/fields/text_field.rb +7 -0
  94. data/lib/yodel/models/core/fields/time_field.rb +36 -0
  95. data/lib/yodel/models/core/functions/function.rb +471 -0
  96. data/lib/yodel/models/core/functions/functions.rb +2 -0
  97. data/lib/yodel/models/core/functions/trigger.rb +14 -0
  98. data/lib/yodel/models/core/log/log.rb +33 -0
  99. data/lib/yodel/models/core/log/log_entry.rb +12 -0
  100. data/lib/yodel/models/core/model/abstract_model.rb +59 -0
  101. data/lib/yodel/models/core/model/model.rb +460 -0
  102. data/lib/yodel/models/core/model/mongo_model.rb +25 -0
  103. data/lib/yodel/models/core/model/site_model.rb +17 -0
  104. data/lib/yodel/models/core/mongo/mongo.rb +3 -0
  105. data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
  106. data/lib/yodel/models/core/mongo/query.rb +68 -0
  107. data/lib/yodel/models/core/mongo/record_index.rb +89 -0
  108. data/lib/yodel/models/core/record/abstract_record.rb +411 -0
  109. data/lib/yodel/models/core/record/embedded_record.rb +47 -0
  110. data/lib/yodel/models/core/record/mongo_record.rb +83 -0
  111. data/lib/yodel/models/core/record/record.rb +386 -0
  112. data/lib/yodel/models/core/record/section.rb +21 -0
  113. data/lib/yodel/models/core/record/site_record.rb +31 -0
  114. data/lib/yodel/models/core/site/migration.rb +52 -0
  115. data/lib/yodel/models/core/site/remote.rb +61 -0
  116. data/lib/yodel/models/core/site/site.rb +202 -0
  117. data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
  118. data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
  119. data/lib/yodel/models/core/validations/errors.rb +51 -0
  120. data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
  121. data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
  122. data/lib/yodel/models/core/validations/format_validation.rb +10 -0
  123. data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
  124. data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
  125. data/lib/yodel/models/core/validations/length_validation.rb +28 -0
  126. data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
  127. data/lib/yodel/models/core/validations/required_validation.rb +9 -0
  128. data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
  129. data/lib/yodel/models/core/validations/validation.rb +39 -0
  130. data/lib/yodel/models/core/validations/validations.rb +15 -0
  131. data/lib/yodel/models/email/email.rb +79 -0
  132. data/lib/yodel/models/migrations/01_record_model.rb +29 -0
  133. data/lib/yodel/models/migrations/02_page_model.rb +45 -0
  134. data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
  135. data/lib/yodel/models/migrations/04_group_model.rb +61 -0
  136. data/lib/yodel/models/migrations/05_user_model.rb +24 -0
  137. data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
  138. data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
  139. data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
  140. data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
  141. data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
  142. data/lib/yodel/models/migrations/11_email_model.rb +28 -0
  143. data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
  144. data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
  145. data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
  146. data/lib/yodel/models/models.rb +8 -0
  147. data/lib/yodel/models/pages/form_builder.rb +379 -0
  148. data/lib/yodel/models/pages/html_decorator.rb +132 -0
  149. data/lib/yodel/models/pages/layout.rb +120 -0
  150. data/lib/yodel/models/pages/menu.rb +32 -0
  151. data/lib/yodel/models/pages/page.rb +378 -0
  152. data/lib/yodel/models/pages/pages.rb +7 -0
  153. data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
  154. data/lib/yodel/models/pages/redirect_page.rb +11 -0
  155. data/lib/yodel/models/search/search.rb +1 -0
  156. data/lib/yodel/models/search/search_page.rb +58 -0
  157. data/lib/yodel/models/security/facebook_login_page.rb +55 -0
  158. data/lib/yodel/models/security/group.rb +10 -0
  159. data/lib/yodel/models/security/guests_group.rb +5 -0
  160. data/lib/yodel/models/security/login_page.rb +20 -0
  161. data/lib/yodel/models/security/logout_page.rb +13 -0
  162. data/lib/yodel/models/security/noone_group.rb +5 -0
  163. data/lib/yodel/models/security/owner_group.rb +8 -0
  164. data/lib/yodel/models/security/password.rb +5 -0
  165. data/lib/yodel/models/security/password_reset_page.rb +47 -0
  166. data/lib/yodel/models/security/security.rb +10 -0
  167. data/lib/yodel/models/security/user.rb +33 -0
  168. data/lib/yodel/public/core/css/core.css +257 -0
  169. data/lib/yodel/public/core/css/reset.css +48 -0
  170. data/lib/yodel/public/core/images/cross.png +0 -0
  171. data/lib/yodel/public/core/images/spinner.gif +0 -0
  172. data/lib/yodel/public/core/images/tick.png +0 -0
  173. data/lib/yodel/public/core/images/yodel.png +0 -0
  174. data/lib/yodel/public/core/js/jquery.min.js +18 -0
  175. data/lib/yodel/public/core/js/json2.js +480 -0
  176. data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
  177. data/lib/yodel/request/authentication.rb +76 -0
  178. data/lib/yodel/request/flash.rb +28 -0
  179. data/lib/yodel/request/request.rb +4 -0
  180. data/lib/yodel/requires.rb +47 -0
  181. data/lib/yodel/task_queue/queue_daemon.rb +33 -0
  182. data/lib/yodel/task_queue/queue_worker.rb +32 -0
  183. data/lib/yodel/task_queue/stats_thread.rb +27 -0
  184. data/lib/yodel/task_queue/task.rb +62 -0
  185. data/lib/yodel/task_queue/task_queue.rb +40 -0
  186. data/lib/yodel/types/date.rb +5 -0
  187. data/lib/yodel/types/object_id.rb +11 -0
  188. data/lib/yodel/types/time.rb +5 -0
  189. data/lib/yodel/version.rb +3 -0
  190. data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
  191. data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
  192. data/system/etc/resolver/yodel +2 -0
  193. data/system/usr/local/bin/yodel_command_runner +2 -0
  194. data/system/usr/local/etc/yodel/development_settings.rb +28 -0
  195. data/system/usr/local/etc/yodel/production_settings.rb +27 -0
  196. data/system/var/log/yodel.log +0 -0
  197. data/test/helper.rb +18 -0
  198. data/test/test_yodel.rb +4 -0
  199. data/yodel.gemspec +47 -0
  200. metadata +501 -0
@@ -0,0 +1,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,4 @@
1
+ class ColorField < StringField
2
+ end
3
+
4
+ Field::TYPES['color'] = ColorField
@@ -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,10 @@
1
+ class EmailField < StringField
2
+
3
+ def validate(record, errors)
4
+ EmailAddressValidation.validate(nil, self, name, record.get(name), record, errors)
5
+ super
6
+ end
7
+
8
+ end
9
+
10
+ Field::TYPES['email'] = EmailField
@@ -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,9 @@
1
+ module FilterMixin
2
+ def typecast(value, record)
3
+ Hpricot(value.to_s).search('text()').collect(&:to_s).collect(&:strip).join(' ').strip
4
+ end
5
+
6
+ def untypecast(value, record)
7
+ value.to_s
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class FilteredStringField < StringField
2
+ include FilterMixin
3
+ end
4
+
5
+ Field::TYPES['filtered_string'] = FilteredStringField
@@ -0,0 +1,5 @@
1
+ class FilteredTextField < TextField
2
+ include FilterMixin
3
+ end
4
+
5
+ Field::TYPES['filtered_text'] = FilteredTextField
@@ -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