spontaneous 0.2.0.beta4 → 0.2.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/Gemfile +11 -6
  4. data/Readme.markdown +136 -69
  5. data/application/css/core.css.scss +27 -7
  6. data/application/css/editing.css.scss +4 -26
  7. data/application/css/schema_error.css.scss +22 -0
  8. data/application/js/content.js +11 -3
  9. data/application/js/edit_panel.js +1 -4
  10. data/application/js/field/file.js +17 -0
  11. data/application/js/field/image.js +30 -21
  12. data/application/js/field/string.js +4 -1
  13. data/application/js/field_preview.js +21 -16
  14. data/application/js/publish.js +6 -6
  15. data/application/js/types.js +5 -13
  16. data/application/js/views.js +2 -2
  17. data/application/js/views/box_view.js +3 -2
  18. data/application/js/views/page_piece_view.js +1 -1
  19. data/application/js/views/piece_view.js +1 -1
  20. data/application/views/schema_modification_error.html.erb +13 -3
  21. data/db/migrations/20131104101935_site_must_publish_all.rb +14 -0
  22. data/lib/spontaneous.rb +0 -1
  23. data/lib/spontaneous/box_style.rb +15 -9
  24. data/lib/spontaneous/capistrano/deploy.rb +13 -1
  25. data/lib/spontaneous/change.rb +11 -13
  26. data/lib/spontaneous/cli.rb +5 -2
  27. data/lib/spontaneous/cli/assets.rb +7 -1
  28. data/lib/spontaneous/cli/console.rb +7 -1
  29. data/lib/spontaneous/cli/content.rb +35 -0
  30. data/lib/spontaneous/cli/fields.rb +3 -2
  31. data/lib/spontaneous/cli/generate.rb +5 -2
  32. data/lib/spontaneous/cli/server.rb +12 -8
  33. data/lib/spontaneous/cli/site.rb +12 -12
  34. data/lib/spontaneous/cli/user.rb +28 -14
  35. data/lib/spontaneous/collections/box_set.rb +4 -4
  36. data/lib/spontaneous/collections/field_set.rb +4 -4
  37. data/lib/spontaneous/collections/prototype_set.rb +12 -4
  38. data/lib/spontaneous/data_mapper.rb +11 -7
  39. data/lib/spontaneous/data_mapper/content_model.rb +8 -0
  40. data/lib/spontaneous/data_mapper/content_model/associations.rb +1 -1
  41. data/lib/spontaneous/data_mapper/dataset.rb +14 -2
  42. data/lib/spontaneous/data_mapper/scope.rb +33 -13
  43. data/lib/spontaneous/facet.rb +4 -0
  44. data/lib/spontaneous/field.rb +12 -12
  45. data/lib/spontaneous/field/base.rb +27 -22
  46. data/lib/spontaneous/field/boolean.rb +4 -4
  47. data/lib/spontaneous/field/date.rb +2 -2
  48. data/lib/spontaneous/field/file.rb +24 -18
  49. data/lib/spontaneous/field/html.rb +1 -1
  50. data/lib/spontaneous/field/image.rb +6 -19
  51. data/lib/spontaneous/field/location.rb +1 -1
  52. data/lib/spontaneous/field/long_string.rb +3 -3
  53. data/lib/spontaneous/field/markdown.rb +3 -3
  54. data/lib/spontaneous/field/select.rb +2 -2
  55. data/lib/spontaneous/field/string.rb +2 -2
  56. data/lib/spontaneous/field/tags.rb +2 -2
  57. data/lib/spontaneous/field/update.rb +21 -20
  58. data/lib/spontaneous/field/webvideo.rb +6 -6
  59. data/lib/spontaneous/field/webvideo/fallback.rb +2 -2
  60. data/lib/spontaneous/field/webvideo/vimeo.rb +7 -7
  61. data/lib/spontaneous/generators/site.rb +2 -2
  62. data/lib/spontaneous/generators/site/Gemfile.tt +5 -1
  63. data/lib/spontaneous/layout.rb +2 -2
  64. data/lib/spontaneous/media.rb +1 -0
  65. data/lib/spontaneous/media/file.rb +6 -5
  66. data/lib/spontaneous/media/image/attributes.rb +4 -0
  67. data/lib/spontaneous/media/image/renderable.rb +4 -4
  68. data/lib/spontaneous/media/store.rb +22 -0
  69. data/lib/spontaneous/{storage → media/store}/backend.rb +1 -1
  70. data/lib/spontaneous/{storage → media/store}/cloud.rb +1 -1
  71. data/lib/spontaneous/{storage → media/store}/local.rb +1 -1
  72. data/lib/spontaneous/media/temp_file.rb +1 -1
  73. data/lib/spontaneous/model.rb +10 -7
  74. data/lib/spontaneous/model/action.rb +7 -0
  75. data/lib/spontaneous/model/action/clean.rb +87 -0
  76. data/lib/spontaneous/model/box/allowed_types.rb +15 -1
  77. data/lib/spontaneous/model/core.rb +10 -0
  78. data/lib/spontaneous/model/core/aliases.rb +1 -1
  79. data/lib/spontaneous/model/core/content_groups.rb +1 -1
  80. data/lib/spontaneous/model/core/fields.rb +1 -1
  81. data/lib/spontaneous/model/core/modifications.rb +2 -2
  82. data/lib/spontaneous/model/core/page_search.rb +4 -0
  83. data/lib/spontaneous/model/core/publishing.rb +4 -17
  84. data/lib/spontaneous/model/core/render.rb +4 -4
  85. data/lib/spontaneous/model/core/styles.rb +2 -2
  86. data/lib/spontaneous/model/core/visibility.rb +6 -2
  87. data/lib/spontaneous/model/page.rb +6 -2
  88. data/lib/spontaneous/model/page/controllers.rb +55 -17
  89. data/lib/spontaneous/model/page/formats.rb +12 -7
  90. data/lib/spontaneous/model/page/layouts.rb +2 -2
  91. data/lib/spontaneous/model/page/locks.rb +4 -1
  92. data/lib/spontaneous/model/page/page_tree.rb +40 -6
  93. data/lib/spontaneous/output.rb +14 -52
  94. data/lib/spontaneous/output/context.rb +11 -39
  95. data/lib/spontaneous/output/context/navigation.rb +31 -0
  96. data/lib/spontaneous/output/format.rb +15 -19
  97. data/lib/spontaneous/output/renderable.rb +99 -0
  98. data/lib/spontaneous/output/store.rb +24 -0
  99. data/lib/spontaneous/output/store/backend.rb +52 -0
  100. data/lib/spontaneous/output/store/file.rb +77 -0
  101. data/lib/spontaneous/output/store/moneta.rb +117 -0
  102. data/lib/spontaneous/output/store/revision.rb +34 -0
  103. data/lib/spontaneous/output/store/store.rb +15 -0
  104. data/lib/spontaneous/output/store/transaction.rb +44 -0
  105. data/lib/spontaneous/output/template/engine.rb +17 -7
  106. data/lib/spontaneous/output/template/renderer.rb +66 -40
  107. data/lib/spontaneous/page_lock.rb +5 -7
  108. data/lib/spontaneous/page_piece.rb +2 -2
  109. data/lib/spontaneous/permissions/user.rb +14 -7
  110. data/lib/spontaneous/plugins/application/features.rb +8 -4
  111. data/lib/spontaneous/plugins/application/state.rb +12 -6
  112. data/lib/spontaneous/prototypes/box_prototype.rb +9 -10
  113. data/lib/spontaneous/prototypes/field_prototype.rb +66 -15
  114. data/lib/spontaneous/publishing/immediate.rb +30 -26
  115. data/lib/spontaneous/rack.rb +12 -7
  116. data/lib/spontaneous/rack/back.rb +43 -37
  117. data/lib/spontaneous/rack/back/base.rb +4 -4
  118. data/lib/spontaneous/rack/back/changes.rb +2 -2
  119. data/lib/spontaneous/rack/back/file.rb +16 -24
  120. data/lib/spontaneous/rack/back/map.rb +5 -5
  121. data/lib/spontaneous/rack/back/preview.rb +3 -4
  122. data/lib/spontaneous/rack/back/schema.rb +1 -1
  123. data/lib/spontaneous/rack/back/site.rb +6 -7
  124. data/lib/spontaneous/rack/front.rb +19 -16
  125. data/lib/spontaneous/rack/middleware/authenticate.rb +3 -3
  126. data/lib/spontaneous/rack/middleware/reloader.rb +3 -2
  127. data/lib/spontaneous/rack/middleware/scope.rb +25 -19
  128. data/lib/spontaneous/rack/page_controller.rb +164 -13
  129. data/lib/spontaneous/rack/public.rb +23 -62
  130. data/lib/spontaneous/rack/static.rb +2 -3
  131. data/lib/spontaneous/schema.rb +27 -8
  132. data/lib/spontaneous/schema/schema_modification.rb +9 -1
  133. data/lib/spontaneous/schema/uid.rb +2 -2
  134. data/lib/spontaneous/schema/uid_map.rb +3 -2
  135. data/lib/spontaneous/search/database.rb +2 -2
  136. data/lib/spontaneous/search/field.rb +5 -3
  137. data/lib/spontaneous/search/index.rb +12 -7
  138. data/lib/spontaneous/search/results.rb +5 -3
  139. data/lib/spontaneous/server.rb +2 -2
  140. data/lib/spontaneous/site.rb +10 -3
  141. data/lib/spontaneous/site/features.rb +26 -6
  142. data/lib/spontaneous/site/helpers.rb +9 -12
  143. data/lib/spontaneous/site/level.rb +7 -9
  144. data/lib/spontaneous/site/map.rb +9 -11
  145. data/lib/spontaneous/site/paths.rb +5 -5
  146. data/lib/spontaneous/site/publishing.rb +83 -80
  147. data/lib/spontaneous/site/schema.rb +1 -7
  148. data/lib/spontaneous/site/search.rb +8 -18
  149. data/lib/spontaneous/site/selectors.rb +60 -54
  150. data/lib/spontaneous/site/state.rb +36 -30
  151. data/lib/spontaneous/site/storage.rb +10 -16
  152. data/lib/spontaneous/state.rb +8 -0
  153. data/lib/spontaneous/style.rb +32 -33
  154. data/lib/spontaneous/version.rb +1 -1
  155. data/spontaneous.gemspec +22 -21
  156. data/test/fixtures/public/templates/layouts/default.html.cut +1 -1
  157. data/test/fixtures/public/templates/layouts/default.pdf.cut +1 -1
  158. data/test/fixtures/public/templates/layouts/default.rss.cut +1 -1
  159. data/test/fixtures/search/config/indexes.rb +1 -1
  160. data/test/fixtures/serialisation/class_hash.yaml.erb +13 -1
  161. data/test/fixtures/serialisation/root_hash.yaml.erb +10 -0
  162. data/test/functional/test_application.rb +20 -24
  163. data/test/functional/test_back.rb +26 -27
  164. data/test/functional/test_cli.rb +146 -0
  165. data/test/functional/test_front.rb +287 -216
  166. data/test/functional/test_user_manager.rb +1 -1
  167. data/test/test_helper.rb +15 -11
  168. data/test/unit/test_alias.rb +32 -25
  169. data/test/unit/test_asset_bundler.rb +1 -1
  170. data/test/unit/test_assets.rb +34 -33
  171. data/test/unit/test_authentication.rb +1 -1
  172. data/test/unit/test_boxes.rb +16 -2
  173. data/test/unit/test_changesets.rb +23 -11
  174. data/test/unit/test_content.rb +15 -0
  175. data/test/unit/test_context.rb +139 -0
  176. data/test/unit/test_controllers.rb +374 -0
  177. data/test/{experimental → unit}/test_crypt.rb +0 -0
  178. data/test/unit/test_datamapper.rb +260 -237
  179. data/test/unit/test_datamapper_content.rb +42 -12
  180. data/test/{experimental → unit}/test_features.rb +85 -3
  181. data/test/unit/test_fields.rb +117 -42
  182. data/test/unit/test_formats.rb +11 -1
  183. data/test/unit/test_generators.rb +2 -2
  184. data/test/unit/test_helpers.rb +7 -8
  185. data/test/unit/test_images.rb +39 -2
  186. data/test/unit/test_layouts.rb +14 -12
  187. data/test/unit/test_media.rb +32 -23
  188. data/test/unit/test_output_store.rb +342 -0
  189. data/test/unit/test_page.rb +8 -1
  190. data/test/unit/test_permissions.rb +11 -7
  191. data/test/unit/test_plugins.rb +3 -3
  192. data/test/unit/test_prototype_set.rb +8 -1
  193. data/test/unit/test_publishing.rb +67 -54
  194. data/test/unit/test_render.rb +91 -38
  195. data/test/unit/test_revisions.rb +4 -4
  196. data/test/unit/test_schema.rb +109 -84
  197. data/test/unit/test_search.rb +42 -42
  198. data/test/unit/test_serialisation.rb +3 -2
  199. data/test/unit/test_site.rb +39 -27
  200. data/test/unit/test_storage.rb +9 -6
  201. data/test/unit/test_styles.rb +25 -32
  202. data/test/unit/test_templates.rb +8 -4
  203. metadata +89 -54
  204. data/lib/spontaneous/model/page/request.rb +0 -105
  205. data/lib/spontaneous/storage.rb +0 -22
@@ -7,8 +7,8 @@ module Spontaneous::Field
7
7
  "fallback"
8
8
  end
9
9
 
10
- def to_html(options = {})
11
- params = player_attributes(options)
10
+ def to_html(locals = {})
11
+ params = player_attributes(locals)
12
12
  attributes = hash_to_attributes(params[:attr])
13
13
  %(<iframe #{attributes}></iframe>)
14
14
  end
@@ -25,18 +25,18 @@ module Spontaneous::Field
25
25
  response = \
26
26
  begin
27
27
  open(url).read
28
- rescue => e
29
- logger.error("Unable to retrieve metadata for video ##{video_id} from Vimeo: '#{e}'")
30
- "[{}]"
31
- end
28
+ rescue => e
29
+ logger.error("Unable to retrieve metadata for video ##{video_id} from Vimeo: '#{e}'")
30
+ fallback_response
31
+ end
32
32
  metadata = Spontaneous.parse_json(response) rescue [{}]
33
- metadata = metadata.first || {}
33
+ metadata = (metadata || [{}]).first || {}
34
34
  end
35
35
 
36
36
  def to_html(options = {})
37
37
  params = player_attributes(options)
38
38
  attributes = hash_to_attributes(params[:attr])
39
- %(<iframe #{attributes}></iframe>)
39
+ "<iframe #{attributes}></iframe>"
40
40
  end
41
41
 
42
42
  def as_json(options = {})
@@ -101,7 +101,7 @@ module Spontaneous::Field
101
101
  "autoplay" => o[:autoplay],
102
102
  "loop" => o[:loop],
103
103
  "api" => o[:api],
104
- "player_id" => o[:player_id] }
104
+ "player_id" => o[:player_id] }
105
105
  params.update("color" => o[:color]) if o.key?(:color)
106
106
  params = ::Rack::Utils.build_query(params)
107
107
  "http://player.vimeo.com/video/#{video_id}?#{params}"
@@ -6,8 +6,8 @@ module Spontaneous
6
6
  module Generators
7
7
  class Site < Thor::Group
8
8
  def self.available_dbs
9
- postgres = { :gem => "pg", :adapter => "postgres", :user => nil }
10
- { "mysql" => { :gem => "mysql2", :adapter => "mysql2", :user => "root" },
9
+ postgres = { :gem => "sequel_pg", :adapter => "postgres", :user => nil }
10
+ { "mysql" => { :gem => "mysql2", :adapter => "mysql2", :user => "root" },
11
11
  "pg" => postgres, "postgresql" => postgres, "postgres" => postgres }
12
12
  end
13
13
 
@@ -5,7 +5,11 @@ gem 'spontaneous', '~> <%= Spontaneous::VERSION %>'
5
5
  # Or point it at a git repository to use the cutting edge or your custom version
6
6
  # gem 'spontaneous', :git => "https://github.com/SpontaneousCMS/spontaneous.git"
7
7
 
8
- gem '<%= @database[:gem].name %>', '<%= @database[:gem].requirement %>'
8
+ gem '<%= @database[:gem].name %>', '<%= @database[:gem].requirement %>'<%
9
+ if (requires = @database[:gem].autorequire) && (requires.length > 0)
10
+ requires = requires.first if requires.length == 1 -%>
11
+ , require: '<%= requires %>'
12
+ <%- end -%>
9
13
 
10
14
  # Your favorite ExecJS compatible engine
11
15
  gem 'therubyracer', '~> 0.11.1'
@@ -11,7 +11,7 @@ module Spontaneous
11
11
  # matching the class of our owner, then default to the 'standard'
12
12
  # layout.
13
13
  def try_paths
14
- named_layout = self.class.to_directory_name(owner)
14
+ named_layout = to_directory_name(owner)
15
15
  [["layouts", named_layout], ["layouts", "standard"]]
16
16
  end
17
17
  end
@@ -21,7 +21,7 @@ module Spontaneous
21
21
  @templates = templates
22
22
  end
23
23
 
24
- def template(format = :html)
24
+ def template(format = :html, renderer)
25
25
  template = @templates[format]
26
26
  # a layout without a format is used as a fallback
27
27
  template ||= @templates[nil]
@@ -8,6 +8,7 @@ module Spontaneous
8
8
  autoload :File, "spontaneous/media/file"
9
9
  autoload :Image, "spontaneous/media/image"
10
10
  autoload :TempFile, "spontaneous/media/temp_file"
11
+ autoload :Store, "spontaneous/media/store"
11
12
 
12
13
  include Spontaneous::Constants
13
14
 
@@ -8,9 +8,10 @@ module Spontaneous::Media
8
8
 
9
9
  attr_reader :filename, :owner, :source
10
10
 
11
- def initialize(owner, filename, headers = {})
11
+ def initialize(site, owner, filename, headers = {})
12
12
  headers = { content_type: headers } if headers.is_a?(String)
13
- @owner, @filename, @headers = owner, Spontaneous::Media.to_filename(filename), headers
13
+ headers ||= {}
14
+ @site, @owner, @filename, @headers = site, owner, Spontaneous::Media.to_filename(filename), headers
14
15
  end
15
16
 
16
17
  # Create a new File instance with a new name.
@@ -20,7 +21,7 @@ module Spontaneous::Media
20
21
  def rename(new_filename)
21
22
  headers = storage_headers
22
23
  headers.delete(:content_type)
23
- self.class.new(owner, new_filename, headers)
24
+ self.class.new(@site, owner, new_filename, headers)
24
25
  end
25
26
 
26
27
  def open(mode = 'wb', &block)
@@ -58,7 +59,7 @@ module Spontaneous::Media
58
59
  end
59
60
 
60
61
  def storage
61
- @storage ||= Spontaneous::Site.storage(mimetype)
62
+ @storage ||= @site.storage(mimetype)
62
63
  end
63
64
 
64
65
  def padded_id
@@ -70,7 +71,7 @@ module Spontaneous::Media
70
71
  end
71
72
 
72
73
  def revision
73
- Spontaneous::Site.working_revision
74
+ @site.working_revision
74
75
  end
75
76
 
76
77
  def media_dir
@@ -28,6 +28,10 @@ module Spontaneous::Media
28
28
  end
29
29
 
30
30
  alias_method :empty?, :blank?
31
+
32
+ def value(format = :html)
33
+ src
34
+ end
31
35
  end
32
36
  end
33
37
  end
@@ -3,10 +3,10 @@ module Spontaneous::Media::Image
3
3
  module Renderable
4
4
  attr_accessor :template_params
5
5
 
6
- def render(format=:html, *args)
6
+ def render(format=:html, params = {}, parent_context = nil)
7
7
  case format
8
8
  when "html", :html
9
- to_html(*args)
9
+ to_html(params)
10
10
  else
11
11
  value
12
12
  end
@@ -19,8 +19,8 @@ module Spontaneous::Media::Image
19
19
  :height => height,
20
20
  :alt => ""
21
21
  }
22
- default_attr.delete(:width) if width.nil?
23
- default_attr.delete(:height) if height.nil?
22
+ default_attr.delete(:width) if (width.nil? || width == 0)
23
+ default_attr.delete(:height) if (height.nil? || height == 0)
24
24
  if template_params && template_params.length > 0 && template_params[0].is_a?(Hash)
25
25
  attr = template_params[0].merge(attr)
26
26
  end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ module Spontaneous::Media
4
+ module Store
5
+ autoload :Backend, "spontaneous/media/store/backend"
6
+ autoload :Local, "spontaneous/media/store/local"
7
+ autoload :Cloud, "spontaneous/media/store/cloud"
8
+
9
+ extend self
10
+
11
+ def create(config)
12
+ case config[:provider]
13
+ when "Local", "local"
14
+ Local.new(config[:local_root], config[:url], config[:accepts])
15
+ else
16
+ bucket = config.delete(:bucket)
17
+ accepts = config.delete(:accepts)
18
+ Cloud.new(config, bucket, accepts)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
- module Spontaneous::Storage
3
+ module Spontaneous::Media::Store
4
4
  class Backend
5
5
  def accepts?(mimetype)
6
6
  return true if @accepts.nil?
@@ -3,7 +3,7 @@
3
3
  require 'fog'
4
4
  require 'tempfile'
5
5
 
6
- module Spontaneous::Storage
6
+ module Spontaneous::Media::Store
7
7
  class Cloud < Backend
8
8
  # Thanks thoughtbot (via paperclip)
9
9
  # http://rdoc.info/github/thoughtbot/paperclip/Paperclip/Storage/Fog
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
- module Spontaneous::Storage
3
+ module Spontaneous::Media::Store
4
4
  class Local < Backend
5
5
  attr_reader :root
6
6
 
@@ -9,7 +9,7 @@ module Spontaneous::Media
9
9
  class TempFile < File
10
10
 
11
11
  def storage
12
- Spontaneous::Site.default_storage
12
+ @site.default_storage
13
13
  end
14
14
 
15
15
  def media_dir
@@ -1,21 +1,22 @@
1
1
  module Spontaneous
2
2
 
3
3
  module Model
4
- autoload :Core, "spontaneous/model/core"
5
- autoload :Page, "spontaneous/model/page"
6
- autoload :Piece, "spontaneous/model/piece"
7
- autoload :Box, "spontaneous/model/box"
4
+ autoload :Core, "spontaneous/model/core"
5
+ autoload :Page, "spontaneous/model/page"
6
+ autoload :Piece, "spontaneous/model/piece"
7
+ autoload :Box, "spontaneous/model/box"
8
+ autoload :Action, "spontaneous/model/action"
8
9
  end
9
10
 
10
11
  def self.models
11
12
  @models ||= {}
12
13
  end
13
14
 
14
- def self.Model(table_name, database = Spontaneous.database, schema = Site.schema)
15
- define_content_model(table_name, database, schema)
15
+ def self.Model(table_name)
16
+ Model!(table_name, Spontaneous.database, Spontaneous.instance.schema)
16
17
  end
17
18
 
18
- def self.define_content_model(table_name, database = Spontaneous.database, schema = Site.schema)
19
+ def self.Model!(table_name, database, schema)
19
20
  model = Spontaneous::DataMapper::Model(table_name, database, schema) do
20
21
  serialize_columns :field_store, :entry_store, :box_store, :serialized_modifications
21
22
  include_all_types
@@ -42,6 +43,8 @@ module Spontaneous
42
43
  class_variable_get(:@@content_model)
43
44
  end
44
45
 
46
+ alias_method :model, :content_model
47
+
45
48
  # This is fiddly because we want the Content.content_model to refer to the
46
49
  # first subclass of the Spontaneous::Content call as that is going to be our
47
50
  # ::Content model. In this particular instance I want the behaviour of Ruby's
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ module Spontaneous::Model
4
+ module Action
5
+ autoload :Clean, "spontaneous/model/action/clean"
6
+ end
7
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'set'
4
+
5
+ module Spontaneous::Model::Action
6
+ # Designed to be run after any schema modifications that delete types or boxes
7
+ # this action deletes all instances with invalid type ids and then cleans
8
+ # up the remaining content by testing the visibility path of each remaining instance
9
+ # and deletes any that are 'orphaned' i.e. they have a hole in their ancestry.
10
+ #
11
+ # This would normally be managed by the recursive destroy but when we're dealing
12
+ # with content that has no matching schema type it's hard to instantiate them & hence
13
+ # hard to invoke a destroy (and we resort to db level deletes)
14
+ #
15
+ # This is an argument for moving hierarcy maintenance into db level triggers
16
+ # rather than keeping them at the ORM level.
17
+ class Clean
18
+ extend Spontaneous::Concern
19
+
20
+ def self.run(site)
21
+ new(site).run
22
+ end
23
+
24
+ attr_reader :stats
25
+
26
+ def initialize(site)
27
+ @site = site
28
+ @model = site.model
29
+ @dirty = false
30
+ end
31
+
32
+ def run
33
+ stats = {}
34
+ @model.db.transaction do
35
+ stats[:invalid] = delete_invalid_type_instances
36
+ stats[:orphans] = delete_orphans
37
+ stats[:publish] = configure_force_publish
38
+ end
39
+ stats
40
+ end
41
+
42
+ # Delete all instances whose schema id is invalid
43
+ def delete_invalid_type_instances
44
+ invalid = mapper.filter(nil).invert
45
+ count = invalid.delete
46
+ @dirty ||= (count > 0)
47
+ count
48
+ end
49
+
50
+ # Delete all orphaned content items i.e. entries that have an ancestor
51
+ # who is missing, probably as a result of a schema type deletion
52
+ def delete_orphans
53
+ count = 0
54
+ mapper.dataset.each do |content|
55
+ unless valid_path?(content)
56
+ content.destroy
57
+ count += 1
58
+ end
59
+ end
60
+ @dirty ||= (count > 0)
61
+ count
62
+ end
63
+
64
+ # If we've deleted content items at the db level then we need to force
65
+ # a full publish to reset our change tracking and make sure the live
66
+ # site is a proper reflection of the edited content
67
+ def configure_force_publish
68
+ @site.must_publish_all! if @dirty
69
+ @site.must_publish_all?
70
+ end
71
+
72
+ # Use our set of existing ids to check that every entry in an instance's
73
+ # visibility path exists.
74
+ def valid_path?(content)
75
+ content.visibility_ancestor_ids.all? { |id| existing_ids.include?(id) }
76
+ end
77
+
78
+ # Get the id of every valid instance in the db
79
+ def existing_ids
80
+ @existing_ids ||= Set.new(mapper.select(nil, :id).map{ |row| row[:id] })
81
+ end
82
+
83
+ def mapper
84
+ @model.mapper
85
+ end
86
+ end
87
+ end
@@ -14,6 +14,10 @@ module Spontaneous::Model::Box
14
14
  define_instance_class(definition) if definition
15
15
  end
16
16
 
17
+ def schema
18
+ @box_class.mapper.schema
19
+ end
20
+
17
21
  def check_instance_class
18
22
  instance_class
19
23
  end
@@ -84,6 +88,17 @@ module Spontaneous::Model::Box
84
88
  end
85
89
  end
86
90
 
91
+ def export
92
+ if allow_subclasses
93
+ # can't configure interface name using allow_subclasses
94
+ instance_class.subclasses.map { |c| { type: c.ui_class } }
95
+ else
96
+ exported = {type: instance_class.ui_class }
97
+ exported[:as] = @options[:as] if @options.key?(:as)
98
+ [exported]
99
+ end
100
+ end
101
+
87
102
  def prototype
88
103
  @options[:prototype]
89
104
  end
@@ -120,7 +135,6 @@ module Spontaneous::Model::Box
120
135
  end
121
136
 
122
137
  def instance_classes
123
- schema = Spontaneous::Site.schema
124
138
  names = groups.flat_map { |name| schema.groups[name] }
125
139
  names.map { |name| resolve_instance_class(name) }.uniq
126
140
  end
@@ -56,6 +56,16 @@ module Spontaneous::Model
56
56
  ensure
57
57
  mapper.logger = nil
58
58
  end
59
+
60
+ # Provide a Content.to_proc implementation to
61
+ # enable filtering of content lists like so:
62
+ #
63
+ # Content.all.select(&ContentClass)
64
+ # => [#<ContentClass...>, #<ContentClass...>]
65
+ #
66
+ def to_proc
67
+ Proc.new { |obj| self === obj }
68
+ end
59
69
  end
60
70
 
61
71
  include Enumerable
@@ -167,7 +167,7 @@ module Spontaneous::Model::Core
167
167
 
168
168
  def exported_target(user = nil)
169
169
  case target
170
- when Spontaneous::Content
170
+ when content_model
171
171
  target.shallow_export(user)
172
172
  else
173
173
  target.to_json
@@ -9,7 +9,7 @@ module Spontaneous::Model::Core
9
9
 
10
10
  def group(*group_names)
11
11
  @group_memberships = group_names
12
- Spontaneous::Site.schema.add_group_member(self, group_names)
12
+ model.schema.add_group_member(self, group_names)
13
13
  end
14
14
  end
15
15
  end