browsercms 3.1.4 → 3.1.5

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 (164) hide show
  1. data/app/controllers/cms/content_block_controller.rb +2 -2
  2. data/app/controllers/cms/section_nodes_controller.rb +6 -1
  3. data/app/controllers/cms/sections_controller.rb +1 -1
  4. data/app/helpers/cms/application_helper.rb +1 -1
  5. data/app/helpers/cms/content_block_helper.rb +27 -0
  6. data/app/helpers/cms/section_nodes_helper.rb +43 -5
  7. data/app/models/abstract_file_block.rb +16 -1
  8. data/app/models/attachment.rb +17 -35
  9. data/app/models/file_block.rb +0 -12
  10. data/app/models/image_block.rb +0 -12
  11. data/app/models/link.rb +4 -21
  12. data/app/models/page.rb +31 -34
  13. data/app/models/section.rb +82 -44
  14. data/app/models/section_node.rb +39 -24
  15. data/app/models/user.rb +5 -0
  16. data/app/views/cms/blocks/index.html.erb +4 -4
  17. data/app/views/cms/file_blocks/_form.html.erb +1 -1
  18. data/app/views/cms/image_blocks/_form.html.erb +1 -1
  19. data/app/views/cms/section_nodes/_link.html.erb +6 -3
  20. data/app/views/cms/section_nodes/_node.html.erb +11 -1
  21. data/app/views/cms/section_nodes/_page.html.erb +13 -7
  22. data/app/views/cms/section_nodes/_section.html.erb +24 -8
  23. data/app/views/cms/section_nodes/index.html.erb +28 -16
  24. data/app/views/layouts/templates/default.html.erb +17 -0
  25. data/browsercms.gemspec +28 -1413
  26. data/db/migrate/20120117144039_browsercms315.rb +94 -0
  27. data/db/migrate/{20081114172307_load_seed_data.rb → 20121114172307_load_seeds.rb} +8 -1
  28. data/lib/acts_as_list.rb +1 -1
  29. data/lib/browsercms.rb +2 -0
  30. data/lib/cms/addressable.rb +83 -0
  31. data/lib/cms/behaviors/attaching.rb +44 -24
  32. data/lib/cms/behaviors/connecting.rb +2 -1
  33. data/lib/cms/behaviors/publishing.rb +12 -3
  34. data/lib/cms/behaviors/versioning.rb +83 -53
  35. data/lib/cms/content_rendering_support.rb +3 -3
  36. data/lib/cms/error_pages.rb +8 -0
  37. data/lib/cms/init.rb +5 -3
  38. data/lib/cms/version.rb +1 -1
  39. data/templates/blank.rb +2 -0
  40. data/templates/demo.rb +2 -0
  41. data/templates/module.rb +2 -0
  42. data/test/custom_assertions.rb +7 -1
  43. data/test/factories.rb +3 -1
  44. data/test/factories/sitemap_factories.rb +28 -0
  45. data/test/fixtures/connectors.yml +97 -0
  46. data/test/fixtures/content_type_groups.yml +13 -0
  47. data/test/fixtures/content_types.yml +50 -0
  48. data/test/fixtures/dynamic_view_versions.yml +26 -0
  49. data/test/fixtures/dynamic_views.yml +26 -0
  50. data/test/fixtures/group_permissions.yml +16 -0
  51. data/test/fixtures/group_sections.yml +31 -0
  52. data/test/fixtures/group_type_permissions.yml +11 -0
  53. data/test/fixtures/group_types.yml +25 -0
  54. data/test/fixtures/groups.yml +25 -0
  55. data/test/fixtures/html_block_versions.yml +67 -0
  56. data/test/fixtures/html_blocks.yml +63 -0
  57. data/test/fixtures/page_versions.yml +265 -0
  58. data/test/fixtures/pages.yml +85 -0
  59. data/test/fixtures/permissions.yml +28 -0
  60. data/test/fixtures/section_nodes.yml +46 -0
  61. data/test/fixtures/sections.yml +19 -0
  62. data/test/fixtures/sites.yml +9 -0
  63. data/test/fixtures/user_group_memberships.yml +11 -0
  64. data/test/fixtures/users.yml +15 -0
  65. data/test/functional/cms/content_controller_test.rb +6 -1
  66. data/test/functional/cms/file_blocks_controller_test.rb +1 -0
  67. data/test/functional/cms/html_blocks_controller_test.rb +1 -0
  68. data/test/functional/cms/image_blocks_controller_test.rb +39 -32
  69. data/test/functional/cms/section_nodes_controller_test.rb +48 -20
  70. data/test/functional/cms/sections_controller_test.rb +3 -1
  71. data/test/functional/tests/pretend_controller_test.rb +6 -3
  72. data/test/integration/cms/ckeditor_test.rb +5 -2
  73. data/test/integration/sitemap_performance_test.rb +26 -0
  74. data/test/selenium-core/Blank.html +7 -0
  75. data/test/selenium-core/InjectedRemoteRunner.html +8 -0
  76. data/test/selenium-core/RemoteRunner.html +110 -0
  77. data/test/selenium-core/SeleniumLog.html +109 -0
  78. data/test/selenium-core/TestPrompt.html +145 -0
  79. data/test/selenium-core/TestRunner-splash.html +55 -0
  80. data/test/selenium-core/TestRunner.hta +176 -0
  81. data/test/selenium-core/TestRunner.html +176 -0
  82. data/test/selenium-core/domviewer/butmin.gif +0 -0
  83. data/test/selenium-core/domviewer/butplus.gif +0 -0
  84. data/test/selenium-core/domviewer/domviewer.css +298 -0
  85. data/test/selenium-core/domviewer/domviewer.html +16 -0
  86. data/test/selenium-core/domviewer/selenium-domviewer.js +205 -0
  87. data/test/selenium-core/icons/all.png +0 -0
  88. data/test/selenium-core/icons/continue.png +0 -0
  89. data/test/selenium-core/icons/continue_disabled.png +0 -0
  90. data/test/selenium-core/icons/pause.png +0 -0
  91. data/test/selenium-core/icons/pause_disabled.png +0 -0
  92. data/test/selenium-core/icons/selected.png +0 -0
  93. data/test/selenium-core/icons/step.png +0 -0
  94. data/test/selenium-core/icons/step_disabled.png +0 -0
  95. data/test/selenium-core/iedoc-core.xml +1515 -0
  96. data/test/selenium-core/iedoc.xml +1469 -0
  97. data/test/selenium-core/lib/cssQuery/cssQuery-p.js +6 -0
  98. data/test/selenium-core/lib/cssQuery/src/cssQuery-level2.js +142 -0
  99. data/test/selenium-core/lib/cssQuery/src/cssQuery-level3.js +150 -0
  100. data/test/selenium-core/lib/cssQuery/src/cssQuery-standard.js +53 -0
  101. data/test/selenium-core/lib/cssQuery/src/cssQuery.js +356 -0
  102. data/test/selenium-core/lib/prototype.js +2006 -0
  103. data/test/selenium-core/lib/scriptaculous/builder.js +101 -0
  104. data/test/selenium-core/lib/scriptaculous/controls.js +815 -0
  105. data/test/selenium-core/lib/scriptaculous/dragdrop.js +915 -0
  106. data/test/selenium-core/lib/scriptaculous/effects.js +958 -0
  107. data/test/selenium-core/lib/scriptaculous/scriptaculous.js +47 -0
  108. data/test/selenium-core/lib/scriptaculous/slider.js +283 -0
  109. data/test/selenium-core/lib/scriptaculous/unittest.js +383 -0
  110. data/test/selenium-core/scripts/find_matching_child.js +69 -0
  111. data/test/selenium-core/scripts/htmlutils.js +894 -0
  112. data/test/selenium-core/scripts/injection.html +72 -0
  113. data/test/selenium-core/scripts/js2html.js +70 -0
  114. data/test/selenium-core/scripts/narcissus-defs.js +175 -0
  115. data/test/selenium-core/scripts/narcissus-exec.js +1054 -0
  116. data/test/selenium-core/scripts/narcissus-parse.js +1003 -0
  117. data/test/selenium-core/scripts/se2html.js +63 -0
  118. data/test/selenium-core/scripts/selenium-api.js +2409 -0
  119. data/test/selenium-core/scripts/selenium-browserbot.js +2203 -0
  120. data/test/selenium-core/scripts/selenium-browserdetect.js +150 -0
  121. data/test/selenium-core/scripts/selenium-commandhandlers.js +377 -0
  122. data/test/selenium-core/scripts/selenium-executionloop.js +175 -0
  123. data/test/selenium-core/scripts/selenium-logging.js +147 -0
  124. data/test/selenium-core/scripts/selenium-remoterunner.js +571 -0
  125. data/test/selenium-core/scripts/selenium-testrunner.js +1333 -0
  126. data/test/selenium-core/scripts/selenium-version.js +5 -0
  127. data/test/selenium-core/scripts/user-extensions.js +3 -0
  128. data/test/selenium-core/scripts/user-extensions.js.sample +75 -0
  129. data/test/selenium-core/scripts/xmlextras.js +153 -0
  130. data/test/selenium-core/selenium-logo.png +0 -0
  131. data/test/selenium-core/selenium-test.css +43 -0
  132. data/test/selenium-core/selenium.css +299 -0
  133. data/test/selenium-core/xpath/dom.js +428 -0
  134. data/test/selenium-core/xpath/misc.js +252 -0
  135. data/test/selenium-core/xpath/xpath.js +2223 -0
  136. data/test/selenium/_login_as_cmsadmin.rsel +4 -0
  137. data/test/selenium/dashboard.rsel +5 -0
  138. data/test/selenium/html_blocks.rsel +4 -0
  139. data/test/selenium/login/failed_login.rsel +8 -0
  140. data/test/selenium/login/successful_login.rsel +9 -0
  141. data/test/selenium/page_templates.rsel +12 -0
  142. data/test/selenium/pages/edit_properties.rsel +5 -0
  143. data/test/selenium/site/view_home_page.rsel +4 -0
  144. data/test/selenium/sitemap/move_page.rsel +9 -0
  145. data/test/selenium/sitemap/open_section.rsel +6 -0
  146. data/test/selenium/sitemap/select_page.rsel +12 -0
  147. data/test/selenium/sitemap/select_section.rsel +17 -0
  148. data/test/test_helper.rb +30 -12
  149. data/test/unit/behaviors/attaching_test.rb +4 -6
  150. data/test/unit/behaviors/connectable_test.rb +29 -0
  151. data/test/unit/behaviors/publishable_test.rb +40 -9
  152. data/test/unit/behaviors/versioning_test.rb +36 -0
  153. data/test/unit/helpers/menu_helper_test.rb +5 -2
  154. data/test/unit/helpers/page_helper_test.rb +2 -0
  155. data/test/unit/lib/cms/sitemap_test.rb +206 -0
  156. data/test/unit/models/attachment_test.rb +51 -31
  157. data/test/unit/models/file_block_test.rb +74 -55
  158. data/test/unit/models/link_test.rb +44 -0
  159. data/test/unit/models/page_test.rb +290 -224
  160. data/test/unit/models/sections_test.rb +144 -44
  161. data/test/unit/models/user_test.rb +28 -18
  162. metadata +581 -350
  163. data/app/views/cms/section_nodes/_section_node.html.erb +0 -10
  164. data/test/unit/models/section_node_test.rb +0 -92
@@ -0,0 +1,94 @@
1
+ class Browsercms315 < ActiveRecord::Migration
2
+ def self.up
3
+ generate_ancestry_from_section_id
4
+ update_latest_version_cache
5
+
6
+ INDEXES.each do |index|
7
+ add_index *index
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ # This migration is not reversible since it removes the original section_id column.
13
+ end
14
+
15
+ # Add some very commonly used indexes to improve the site performance as the # of pages/content grows (i.e. several thousand pages)
16
+ INDEXES = [
17
+ [:pages, :deleted],
18
+ [:pages, :path],
19
+ [:pages, :version],
20
+ [:page_versions, :page_id],
21
+ [:groups, :code],
22
+ [:groups, :group_type_id],
23
+ [:group_types, :cms_access],
24
+ [:group_sections, :section_id],
25
+ [:group_sections, :group_id],
26
+ [:users, :expires_at],
27
+ [:user_group_memberships, :group_id],
28
+ [:user_group_memberships, :user_id],
29
+ [:group_permissions, :group_id],
30
+ [:group_permissions, :permission_id],
31
+ [:group_permissions, [:group_id, :permission_id]],
32
+ [:section_nodes, :node_type],
33
+ [:section_nodes, :ancestry],
34
+ [:connectors, :page_id],
35
+ [:connectors, :page_version],
36
+ [:html_blocks, :deleted],
37
+ [:html_block_versions, :html_block_id],
38
+ [:html_block_versions, :version],
39
+ [:portlet_attributes, :portlet_id],
40
+ [:portlets, :name],
41
+ [:sections, :path],
42
+ [:redirects, :from_path],
43
+ [:connectors, :connectable_version],
44
+ [:connectors, :connectable_type],
45
+ [:content_types, :content_type_group_id],
46
+ [:content_types, :name],
47
+ [:file_block_versions, :file_block_id],
48
+ [:file_block_versions, :version],
49
+ [:file_blocks, :deleted],
50
+ [:file_blocks, :type],
51
+ [:attachment_versions, :attachment_id],
52
+ [:tasks, :page_id],
53
+ [:tasks, :completed_at],
54
+ [:tasks, :assigned_to_id],
55
+ ]
56
+
57
+ private
58
+
59
+ # v3.1.5 uses Ancestry to manage the parent child relationship between sections and their children.
60
+ # This converts the data from the old section_id to use the ancestry column.
61
+ def self.generate_ancestry_from_section_id
62
+ add_column :section_nodes, :ancestry, :string
63
+ add_column :section_nodes, :temp_parent_id, :integer
64
+
65
+ SectionNode.reset_column_information
66
+ root_section = Section.root.first
67
+ SectionNode.create!(:node => root_section) if root_section
68
+
69
+ all_nodes_but_root = SectionNode.find(:all, :conditions=>["section_id IS NOT NULL"])
70
+ all_nodes_but_root.each do |sn|
71
+ parent_node = SectionNode.find(:first, :conditions => ["node_id = ? and node_type = 'Section'", sn.section_id])
72
+ sn.temp_parent_id = parent_node.id
73
+ sn.save!
74
+ end
75
+ rename_column :section_nodes, :temp_parent_id, :parent_id # Ancestry works off the 'parent_id' column.
76
+
77
+ SectionNode.build_ancestry_from_parent_ids!
78
+ remove_column :section_nodes, :section_id
79
+ remove_column :section_nodes, :parent_id
80
+ SectionNode.reset_column_information
81
+ end
82
+
83
+ # Adds a 'latest_version' pointer to pages and links. Greatly reduces the number of queries the sitemap requires to determine if pages are in draft/published mode
84
+ def self.update_latest_version_cache
85
+ add_column :pages, :latest_version, :integer
86
+ add_column :links, :latest_version, :integer
87
+ Page.all.each do |p|
88
+ p.update_latest_version
89
+ end
90
+ Link.all.each do |link|
91
+ link.update_latest_version
92
+ end
93
+ end
94
+ end
@@ -1,6 +1,13 @@
1
- class LoadSeedData < ActiveRecord::Migration
1
+ # This is a new seed data file (new timestamp and name) so migrations will work in order.
2
+ # It should be able to peacefully coexist next to the previous load_seed_data
3
+ class LoadSeeds < ActiveRecord::Migration
2
4
  extend Cms::DataLoader
3
5
  def self.up
6
+ if User.count > 0
7
+ puts "Database has already been seeded. So skip reloading."
8
+ return
9
+ end
10
+
4
11
  if %w[development test dev local].include?(Rails.env)
5
12
  pwd = "cmsadmin"
6
13
  else
data/lib/acts_as_list.rb CHANGED
@@ -7,7 +7,7 @@ module ActsAsList
7
7
  # The class that has this specified needs to have a +position+ column defined as an integer on
8
8
  # the mapped database table.
9
9
  #
10
- # Todo list example:
10
+ # To Do list example:
11
11
  #
12
12
  # class TodoList < ActiveRecord::Base
13
13
  # has_many :todo_items, :order => "position"
data/lib/browsercms.rb CHANGED
@@ -2,6 +2,8 @@ require 'cms/extensions'
2
2
  require 'cms/init'
3
3
  require 'cms/routes'
4
4
  require 'cms/caching'
5
+ require 'cms/addressable'
6
+ require 'cms/error_pages'
5
7
 
6
8
  #Load libraries that are included with CMS
7
9
  require 'acts_as_list'
@@ -0,0 +1,83 @@
1
+ # Represents any object which exists in a Sitemap.
2
+ #
3
+ # Can have parents (using SectionNodes) and children.
4
+ module Addressable
5
+
6
+ # Returns a list of all Addressable objects that are ancestors to this record.
7
+ # @param [Hash] options
8
+ # @option [Symbol] :include_self If this object should be included in the Array
9
+ # @return [Array<Addressable]
10
+ #
11
+ def ancestors(options={})
12
+ ancestor_nodes = node.ancestors
13
+ ancestors = ancestor_nodes.collect { |node| node.node }
14
+ ancestors << self if options[:include_self]
15
+ ancestors
16
+ end
17
+
18
+ def parent
19
+ @parent if @parent
20
+ node ? node.section : nil
21
+ end
22
+
23
+ def cache_parent(section)
24
+ @parent = section
25
+ end
26
+
27
+ def parent=(sec)
28
+ if node
29
+ node.move_to_end(sec)
30
+ else
31
+ build_section_node(:node => self, :section => sec)
32
+ end
33
+ end
34
+
35
+ # Computes the name of the partial used to render this object in the sitemap.
36
+ def partial_for
37
+ self.class.name.underscore
38
+ end
39
+
40
+ # Pages/Links/Attachments use their parent to determine access
41
+ module LeafNode
42
+ def access_status
43
+ parent.status
44
+ end
45
+ end
46
+
47
+ module NodeAccessors
48
+ def node
49
+ section_node
50
+ end
51
+
52
+ def node=(n)
53
+ self.section_node = n
54
+ end
55
+ end
56
+ # These exist for backwards compatibility to avoid having to change tests.
57
+ # I want to get rid of these in favor of parent and parent_id
58
+ module DeprecatedPageAccessors
59
+ include LeafNode
60
+ include NodeAccessors
61
+
62
+ def build_node(opts)
63
+ build_section_node(opts)
64
+ end
65
+
66
+ def section_id
67
+ section ? section.id : nil
68
+ end
69
+
70
+ def section_id=(sec_id)
71
+ self.section = Section.find(sec_id)
72
+ end
73
+
74
+ def section
75
+ parent
76
+ end
77
+
78
+ def section=(sec)
79
+ self.parent = sec
80
+
81
+ end
82
+ end
83
+ end
@@ -1,34 +1,36 @@
1
1
  module Cms
2
2
  module Behaviors
3
3
  module Attaching
4
- SANITIZATION_REGEXES = [ [/\s/, '_'], [/[&+()]/, '-'], [/[=?!'"{}\[\]#<>%]/, ''] ]
5
-
4
+ SANITIZATION_REGEXES = [[/\s/, '_'], [/[&+()]/, '-'], [/[=?!'"{}\[\]#<>%]/, '']]
5
+
6
6
  def self.included(model_class)
7
7
  model_class.extend(MacroMethods)
8
8
  end
9
- module MacroMethods
9
+
10
+ module MacroMethods
10
11
  def belongs_to_attachment?
11
12
  !!@belongs_to_attachment
12
13
  end
14
+
13
15
  def belongs_to_attachment(options={})
14
16
  @belongs_to_attachment = true
15
17
  include InstanceMethods
16
- before_validation :process_attachment
18
+ before_validation :process_attachment
17
19
  before_save :update_attachment_if_changed
18
20
  after_save :clear_attachment_ivars
19
- belongs_to :attachment, :dependent => :destroy
20
-
21
+ belongs_to :attachment, :dependent => :destroy
22
+
21
23
  validates_each :attachment_file do |record, attr, value|
22
24
  if record.attachment && !record.attachment.valid?
23
25
  record.attachment.errors.each do |err_field, err_value|
24
26
  if err_field.to_sym == :file_path
25
27
  record.errors.add(:attachment_file_path, err_value)
26
- else
28
+ else
27
29
  record.errors.add(:attachment_file, err_value)
28
30
  end
29
- end
31
+ end
30
32
  end
31
- end
33
+ end
32
34
  end
33
35
  end
34
36
  module InstanceMethods
@@ -92,20 +94,30 @@ module Cms
92
94
  unless attachment_section_id.blank?
93
95
  errors.add(:attachment_file, "You must upload a file")
94
96
  return false
95
- end
97
+ end
96
98
  else
97
- build_attachment if attachment.nil?
98
- attachment.temp_file = attachment_file
99
- set_attachment_file_path
99
+ build_attachment if attachment.nil?
100
+ attachment.temp_file = attachment_file
101
+ handle_setting_attachment_path
100
102
  if attachment.file_path.blank?
101
103
  errors.add(:attachment_file_path, "File Name is required for attachment")
102
104
  return false
103
105
  end
104
- set_attachment_section
105
- if attachment.section_id.blank?
106
+ handle_setting_attachment_section
107
+ unless attachment.section
106
108
  errors.add(:attachment_file, "Section is required for attachment")
107
109
  return false
108
110
  end
111
+
112
+ end
113
+ end
114
+
115
+ # Define at :set_attachment_path if you would like to override the way file_path is set
116
+ def handle_setting_attachment_path
117
+ if self.respond_to? :set_attachment_path
118
+ set_attachment_path
119
+ else
120
+ use_default_attachment_path
109
121
  end
110
122
  end
111
123
 
@@ -113,18 +125,26 @@ module Cms
113
125
  @attachment_file = nil
114
126
  @attachment_file_path = nil
115
127
  @attachment_section_id = nil
116
- @attachment_section = nil
128
+ @attachment_section = nil
129
+ end
130
+
131
+ # Implement a :set_attachment_section method if you would like to override the way the section is set
132
+ def handle_setting_attachment_section
133
+ if self.respond_to? :set_attachment_section
134
+ set_attachment_section
135
+ else
136
+ use_default_attachment_section
137
+ end
117
138
  end
118
139
 
119
- # Override this method if you would like to override the way the section is set
120
- def set_attachment_section
140
+ # Default behavior for assigning a section, if a block does not define its own.
141
+ def use_default_attachment_section
121
142
  if !attachment_file.blank?
122
143
  attachment.section = Section.root.first
123
144
  end
124
145
  end
125
146
 
126
- # Override this method if you would like to override the way file_path is set
127
- def set_attachment_file_path
147
+ def use_default_attachment_path
128
148
  if !attachment_file.blank?
129
149
  attachment.file_path = "/attachments/#{File.basename(attachment_file.original_filename).to_s.downcase}"
130
150
  end
@@ -168,16 +188,16 @@ module Cms
168
188
  (published? && live_version?) ? attachment_file_path : "/cms/attachments/#{attachment_id}?version=#{attachment_version}"
169
189
  else
170
190
  nil
171
- end
191
+ end
172
192
  end
173
-
193
+
174
194
  # Forces this record to be changed, even if nothing has changed
175
195
  # This is necessary if just the section.id has changed, for example
176
196
  def dirty!
177
197
  # Seems like a hack, is there a better way?
178
198
  self.updated_at = Time.now
179
- end
180
-
199
+ end
200
+
181
201
  end
182
202
  end
183
203
  end
@@ -39,7 +39,8 @@ module Cms
39
39
  module InstanceMethods
40
40
 
41
41
  def connected_pages
42
- Page.connected_to(self)
42
+ return @connected_pages if @connected_pages
43
+ @connected_pages = Page.connected_to(self)
43
44
  end
44
45
 
45
46
  def connected_page_count
@@ -106,15 +106,24 @@ module Cms
106
106
  end
107
107
 
108
108
  def status
109
- live? ? :published : :draft
110
- end
109
+ return @status if @status
110
+ @status = live? ? :published : :draft
111
+ end
111
112
 
112
113
  def status_name
113
114
  status.to_s.titleize
114
115
  end
115
116
 
116
117
  def live?
117
- self.class.versioned? ? live_version.version == draft.version && published? : true
118
+ if self.class.versioned?
119
+ if (respond_to?(:latest_version) && self.latest_version)
120
+ version == latest_version && published?
121
+ else
122
+ live_version.version == draft.version && published?
123
+ end
124
+ else
125
+ true
126
+ end
118
127
  end
119
128
 
120
129
  end
@@ -4,10 +4,12 @@ module Cms
4
4
  def self.included(model_class)
5
5
  model_class.extend(MacroMethods)
6
6
  end
7
- module MacroMethods
7
+
8
+ module MacroMethods
8
9
  def versioned?
9
10
  !!@is_versioned
10
11
  end
12
+
11
13
  def is_versioned(options={})
12
14
  @is_versioned = true
13
15
 
@@ -17,25 +19,29 @@ module Cms
17
19
  extend ClassMethods
18
20
  include InstanceMethods
19
21
 
20
- has_many :versions, :class_name => version_class_name, :foreign_key => version_foreign_key
22
+ has_many :versions, :class_name => version_class_name, :foreign_key => version_foreign_key
21
23
 
22
24
  before_validation_on_create :initialize_version
23
25
 
24
26
  attr_accessor :revert_to_version
25
27
 
26
28
  #Define the version class
27
- const_set("Version", Class.new(ActiveRecord::Base)).class_eval do
28
- class << self; attr_accessor :versioned_class end
29
+ const_set("Version", Class.new(ActiveRecord::Base)).class_eval do
30
+ class << self;
31
+ attr_accessor :versioned_class
32
+ end
29
33
 
30
34
  def versioned_class
31
35
  self.class.versioned_class
32
36
  end
37
+
33
38
  def versioned_object_id
34
39
  send("#{versioned_class.name.underscore}_id")
35
40
  end
41
+
36
42
  def versioned_object
37
43
  send(versioned_class.name.underscore.to_sym)
38
- end
44
+ end
39
45
  end unless self.const_defined?("Version")
40
46
 
41
47
  version_class.versioned_class = self
@@ -46,33 +52,47 @@ module Cms
46
52
 
47
53
  end
48
54
  end
49
- module ClassMethods
55
+ module ClassMethods
50
56
  def version_class
51
57
  const_get "Version"
52
58
  end
53
59
 
54
60
  def version_class_name
55
61
  "#{name}::Version"
56
- end
62
+ end
57
63
 
58
64
  def version_foreign_key
59
65
  @version_foreign_key
60
66
  end
61
67
 
62
- def version_table_name
68
+ def version_table_name
63
69
  @version_table_name
64
70
  end
65
71
 
66
72
  def versioned_columns
67
- @versioned_columns ||= (version_class.new.attributes.keys -
68
- (%w[id lock_version position version_comment created_at updated_at created_by_id updated_by_id type] + [version_foreign_key]))
69
- end
73
+ @versioned_columns ||= (version_class.new.attributes.keys -
74
+ (%w[id lock_version position version_comment created_at updated_at created_by_id updated_by_id type] + [version_foreign_key]))
75
+ end
70
76
  end
71
77
  module InstanceMethods
72
78
  def initialize_version
73
79
  self.version = 1
74
80
  end
75
81
 
82
+ def after_save
83
+ update_latest_version
84
+ end
85
+
86
+ # Used in migrations and as a callback.
87
+ def update_latest_version
88
+ #Rails 3 could use update_column here instead
89
+ if respond_to? :latest_version
90
+ sql = "UPDATE #{self.class.table_name} SET latest_version = #{draft.version} where id = #{self.id}"
91
+ connection.execute sql
92
+ self.latest_version = draft.version # So we don't need to #reload this object. Probably marks it as dirty though, which could have weird side effects.
93
+ end
94
+ end
95
+
76
96
  def build_new_version
77
97
  # First get the values from the draft
78
98
  attrs = draft_attributes
@@ -83,7 +103,7 @@ module Cms
83
103
  end
84
104
 
85
105
  attrs[:version_comment] = @version_comment || default_version_comment
86
- @version_comment = nil
106
+ @version_comment = nil
87
107
  new_version = versions.build(attrs)
88
108
  new_version.version = new_record? ? 1 : (draft.version.to_i + 1)
89
109
  after_build_new_version(new_version) if respond_to?(:after_build_new_version)
@@ -94,8 +114,8 @@ module Cms
94
114
  # When there is no draft, we'll just copy the attibutes from this object
95
115
  # Otherwise we need to use the draft
96
116
  d = new_record? ? self : draft
97
- self.class.versioned_columns.inject({}){|attrs, col| attrs[col] = d.send(col); attrs }
98
- end
117
+ self.class.versioned_columns.inject({}) { |attrs, col| attrs[col] = d.send(col); attrs }
118
+ end
99
119
 
100
120
  def default_version_comment
101
121
  if new_record?
@@ -108,22 +128,22 @@ module Cms
108
128
  def save(perform_validations=true)
109
129
  transaction do
110
130
  #logger.info "..... Calling valid?"
111
- return false unless !perform_validations || valid?
112
-
131
+ return false unless !perform_validations || valid?
132
+
113
133
  if different_from_last_draft?
114
134
  #logger.info "..... Changes => #{changes.inspect}"
115
135
  else
116
136
  #logger.info "..... No Changes"
117
137
  return true
118
138
  end
119
-
139
+
120
140
  #logger.info "..... Calling before_save"
121
141
  return false if callback(:before_save) == false
122
142
 
123
143
  if new_record?
124
144
  #logger.info "..... Calling before_create"
125
145
  return false if callback(:before_create) == false
126
- else
146
+ else
127
147
  #logger.info "..... Calling before_update"
128
148
  return false if callback(:before_update) == false
129
149
  end
@@ -140,12 +160,12 @@ module Cms
140
160
  #logger.info "..... Calling after_save"
141
161
  callback(:after_save)
142
162
  end
143
-
163
+
144
164
  if @publish_on_save
145
165
  publish
146
166
  @publish_on_save = nil
147
- end
148
- changed_attributes.clear
167
+ end
168
+ changed_attributes.clear
149
169
  end
150
170
  result
151
171
  elsif new_version
@@ -156,11 +176,11 @@ module Cms
156
176
  #logger.info "..... Calling after_update"
157
177
  callback(:after_save)
158
178
  end
159
-
179
+
160
180
  if @publish_on_save
161
181
  publish
162
182
  @publish_on_save = nil
163
- end
183
+ end
164
184
  changed_attributes.clear
165
185
  end
166
186
  result
@@ -174,13 +194,13 @@ module Cms
174
194
  end
175
195
 
176
196
  def draft
177
- versions.first(:order => "version desc")
197
+ versions.first(:order => "version desc")
178
198
  end
179
-
199
+
180
200
  def draft_version?
181
201
  version == draft.version
182
202
  end
183
-
203
+
184
204
  def live_version
185
205
  find_version(self.class.find(id).version)
186
206
  end
@@ -192,38 +212,19 @@ module Cms
192
212
  def current_version
193
213
  find_version(self.version)
194
214
  end
195
-
215
+
196
216
  def find_version(number)
197
- versions.first(:conditions => { :version => number })
217
+ versions.first(:conditions => {:version => number})
198
218
  end
199
219
 
200
220
  def as_of_draft_version
201
- as_of_version(draft.version)
221
+ build_object_from_version(draft)
202
222
  end
203
223
 
204
224
  def as_of_version(version)
205
225
  v = find_version(version)
206
226
  raise ActiveRecord::RecordNotFound.new("version #{version.inspect} does not exist for <#{self.class}:#{id}>") unless v
207
- obj = self.class.new
208
-
209
- (self.class.versioned_columns + [:version, :created_at, :created_by_id, :updated_at, :updated_by_id]).each do |a|
210
- obj.send("#{a}=", v.send(a))
211
- end
212
- obj.id = id
213
- obj.lock_version = lock_version
214
-
215
- # Need to do this so associations can be loaded
216
- obj.instance_variable_set("@new_record", false)
217
-
218
- # Callback to allow us to load other data when an older version is loaded
219
- obj.after_as_of_version if obj.respond_to?(:after_as_of_version)
220
-
221
- # Last but not least, clear the changed attributes
222
- if changed_attrs = obj.send(:changed_attributes)
223
- changed_attrs.clear
224
- end
225
-
226
- obj
227
+ build_object_from_version(v)
227
228
  end
228
229
 
229
230
  def revert
@@ -237,15 +238,15 @@ module Cms
237
238
  raise "Could not find version #{version}" unless revert_to_version
238
239
  (self.class.versioned_columns - ["version"]).each do |a|
239
240
  send("#{a}=", revert_to_version.send(a))
240
- end
241
+ end
241
242
  self.version_comment = "Reverted to version #{version}"
242
- self
243
- end
243
+ self
244
+ end
244
245
 
245
246
  def revert_to(version)
246
247
  revert_to_without_save(version)
247
248
  save
248
- end
249
+ end
249
250
 
250
251
  def version_comment
251
252
  @version_comment
@@ -266,6 +267,35 @@ module Cms
266
267
  return false
267
268
  end
268
269
 
270
+ private
271
+
272
+ # Given a ::Version object of a given type, create an original object from its attributes.
273
+ #
274
+ # @param [Class#name::Version] version (i.e. HtmlBlock::Version)
275
+ # @return [Class#name] i.e. HtmlBlock
276
+ def build_object_from_version(version_of_object)
277
+ obj = self.class.new
278
+
279
+ (self.class.versioned_columns + [:version, :created_at, :created_by_id, :updated_at, :updated_by_id]).each do |a|
280
+ obj.send("#{a}=", version_of_object.send(a))
281
+ end
282
+ obj.id = id
283
+ obj.lock_version = lock_version
284
+
285
+ # Need to do this so associations can be loaded
286
+ obj.instance_variable_set("@new_record", false)
287
+
288
+ # Callback to allow us to load other data when an older version is loaded
289
+ obj.after_as_of_version if obj.respond_to?(:after_as_of_version)
290
+
291
+ # Last but not least, clear the changed attributes
292
+ if changed_attrs = obj.send(:changed_attributes)
293
+ changed_attrs.clear
294
+ end
295
+
296
+ obj
297
+ end
298
+
269
299
  end
270
300
  end
271
301