diff --git a/Gemfile b/Gemfile index 23c96364ff..7ba5f8057d 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,7 @@ gem 'sassc-rails' group :development, :test do gem 'simplecov', require: false + gem 'bootsnap', require: false if ENV['TRAVIS'] gem 'codeclimate-test-reporter', '~> 1.0', require: false end @@ -24,8 +25,6 @@ group :development, :test do gem 'yard' gem 'redcarpet' gem 'pry-byebug' - gem 'spring' - gem 'spring-commands-rspec' gem 'rubocop', require: false gem 'listen' gem 'localeapp', '~> 3.0', require: false diff --git a/app/controllers/alchemy/admin/contents_controller.rb b/app/controllers/alchemy/admin/contents_controller.rb index 9bda0f2664..2d9ac0246d 100644 --- a/app/controllers/alchemy/admin/contents_controller.rb +++ b/app/controllers/alchemy/admin/contents_controller.rb @@ -8,15 +8,14 @@ class ContentsController < Alchemy::Admin::BaseController authorize_resource class: Alchemy::Content def create - @element = Element.find(params[:content][:element_id]) - @content = Content.create_from_scratch(@element, content_params) + @content = Content.create(content_params) @html_options = params[:html_options] || {} end private def content_params - params.require(:content).permit(:element_id, :name, :ingredient, :essence_type) + params.require(:content).permit(:element_id, :name, :ingredient) end end end diff --git a/app/controllers/alchemy/admin/elements_controller.rb b/app/controllers/alchemy/admin/elements_controller.rb index 4616eac2a1..8a15fddd77 100644 --- a/app/controllers/alchemy/admin/elements_controller.rb +++ b/app/controllers/alchemy/admin/elements_controller.rb @@ -41,7 +41,7 @@ def create @element = paste_element_from_clipboard @cell = @element.cell else - @element = Element.new_from_scratch(create_element_params) + @element = Element.new(create_element_params) if @page.can_have_cells? @cell = find_or_create_cell @element.cell = @cell diff --git a/app/models/alchemy/content/factory.rb b/app/models/alchemy/content/factory.rb index 1f1909e53f..9e366035da 100644 --- a/app/models/alchemy/content/factory.rb +++ b/app/models/alchemy/content/factory.rb @@ -11,19 +11,21 @@ module ClassMethods # Builds a new content as descriped in the elements.yml file. # - # @param [Alchemy::Element] - # The element the content is for # @param [Hash] # The content definition used for finding the content in +elements.yml+ file # - def build(element, essence_hash) - definition = content_definition(element, essence_hash) + def new(attributes = {}) + element = attributes[:element] || Element.find_by(id: attributes[:element_id]) + return super if attributes.empty? || element.nil? + + definition = element.content_definition_for(attributes[:name]) if definition.blank? - raise ContentDefinitionError, "No definition found in elements.yml for #{essence_hash.inspect} and #{element.inspect}" - else - new(name: definition['name'], element_id: element.id) + raise ContentDefinitionError, "No definition found in elements.yml for #{attributes.inspect} and #{element.inspect}" end + super(name: definition['name'], element_id: element.id) end + alias_method :build, :new + deprecate build: :new, deprecator: Alchemy::Deprecation # Creates a new content from elements definition in the +elements.yml+ file. # @@ -32,12 +34,20 @@ def build(element, essence_hash) # # @return [Alchemy::Content] # - def create_from_scratch(element, essence_hash) - if content = build(element, essence_hash) - content.create_essence!(essence_hash[:essence_type]) + def create(*args) + attributes = args.last || {} + if args.length > 1 + Alchemy::Deprecation.warn 'Passing an element as first argument to Alchemy::Content.create is deprecated! Pass an attribute hash with element inside instead.' + element = args.first + else + element = attributes[:element] + end + new(attributes.merge(element: element)).tap do |content| + content.create_essence!(attributes[:essence_type]) end - content end + alias_method :create_from_scratch, :create + deprecate create_from_scratch: :create, deprecator: Alchemy::Deprecation # Creates a copy of source and also copies the associated essence. # @@ -50,60 +60,26 @@ def create_from_scratch(element, essence_hash) # def copy(source, differences = {}) new_content = Content.new( - source.attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY).merge(differences) + source.attributes. + except(*SKIPPED_ATTRIBUTES_ON_COPY). + merge(differences.with_indifferent_access) ) - - new_essence = new_content.essence.class.create!( - new_content.essence.attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY) + new_essence = source.essence.class.create!( + source.essence.attributes. + except(*SKIPPED_ATTRIBUTES_ON_COPY) ) - - new_content.update!(essence_id: new_essence.id) - new_content - end - - # Returns the content definition for building a content. - # - # 1. It looks in the element's contents definition - # 2. It builds a definition hash from essence type, if the the name key is not present - # - def content_definition(element, essence_hash) - # No name given. We build the content from essence type. - if essence_hash[:name].blank? && essence_hash[:essence_type].present? - content_definition_from_essence_type(element, essence_hash[:essence_type]) - else - element.content_definition_for(essence_hash[:name]) + new_content.tap do |content| + content.essence = new_essence + content.save end end - # Returns a hash for building a content from essence type. - # - # @param [Alchemy::Element] - # The element the content is for. - # @param [String] - # The essence type the content is from - # - def content_definition_from_essence_type(element, essence_type) - { - 'type' => essence_type, - 'name' => content_name_from_element_and_essence_type(element, essence_type) - } - end - - # A name for content from its essence type and amount of same essences in element. - # - # Example: - # - # essence_picture_1 - # - def content_name_from_element_and_essence_type(element, essence_type) - essences_of_same_type = element.contents.where(essence_type: normalize_essence_type(essence_type)) - "#{essence_type.classify.demodulize.underscore}_#{essences_of_same_type.count + 1}" - end - # Returns all content definitions from elements.yml # def definitions - Element.definitions.collect { |e| e['contents'] }.flatten.compact + definitions = Element.definitions.flat_map { |e| e['contents'] } + definitions.compact! + definitions end # Returns a normalized Essence type diff --git a/app/models/alchemy/element.rb b/app/models/alchemy/element.rb index e0a9efc51e..98ad4051cd 100644 --- a/app/models/alchemy/element.rb +++ b/app/models/alchemy/element.rb @@ -28,10 +28,10 @@ class Element < BaseRecord FORBIDDEN_DEFINITION_ATTRIBUTES = [ "amount", + "autogenerate", "nestable_elements", "contents", "hint", - "picture_gallery", "taggable", "compact" ].freeze @@ -86,9 +86,11 @@ class Element < BaseRecord validates_presence_of :name, on: :create validates_format_of :name, on: :create, with: /\A[a-z0-9_-]+\z/ - attr_accessor :create_contents_after_create + attr_accessor :autogenerate_contents + attr_accessor :autogenerate_nested_elements + after_create :create_contents, unless: -> { autogenerate_contents == false } + after_create :generate_nested_elements, unless: -> { autogenerate_nested_elements == false } - after_create :create_contents, unless: proc { |e| e.create_contents_after_create == false } after_update :touch_touchable_pages scope :trashed, -> { where(position: nil).order('updated_at DESC') } @@ -122,23 +124,21 @@ class << self # - Raises Alchemy::ElementDefinitionError if no definition for given attributes[:name] # could be found # - def new_from_scratch(attributes = {}) - return new if attributes[:name].blank? - new_element_from_definition_by(attributes) || raise(ElementDefinitionError, attributes) - end + def new(attributes = {}) + return super if attributes[:name].blank? + element_attributes = attributes.to_h.merge(name: attributes[:name].split('#').first) + element_definition = Element.definition_by_name(element_attributes[:name]) + if element_definition.nil? + raise(ElementDefinitionError, attributes) + end - # Creates a new element as described in +/config/alchemy/elements.yml+ - # - # - Returns a new Alchemy::Element object if no name is given in attributes, - # because the definition can not be found w/o name - # - Raises Alchemy::ElementDefinitionError if no definition for given attributes[:name] - # could be found - # - def create_from_scratch(attributes) - element = new_from_scratch(attributes) - element.save if element - element + super(element_definition.merge(element_attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES)) end + alias_method :new_from_scratch, :new + deprecate new_from_scratch: :new, deprecator: Alchemy::Deprecation + + alias_method :create_from_scratch, :create + deprecate create_from_scratch: :create, deprecator: Alchemy::Deprecation # This methods does a copy of source and all depending contents and all of their depending essences. # @@ -156,7 +156,8 @@ def copy(source_element, differences = {}) .except(*SKIPPED_ATTRIBUTES_ON_COPY) .merge(differences) .merge({ - create_contents_after_create: false, + autogenerate_contents: false, + autogenerate_nested_elements: false, tag_list: source_element.tag_list }) @@ -186,16 +187,6 @@ def all_from_clipboard_for_page(clipboard, page) page.available_element_names.include?(ce.name) } end - - private - - def new_element_from_definition_by(attributes) - element_attributes = attributes.to_h.merge(name: attributes[:name].split('#').first) - element_definition = Element.definition_by_name(element_attributes[:name]) - return if element_definition.nil? - - new(element_definition.merge(element_attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES)) - end end # Returns next public element from same page. @@ -312,6 +303,16 @@ def copy_nested_elements_to(target_element) private + def generate_nested_elements + definition.fetch('autogenerate', []).each do |nestable_element| + if nestable_elements.include?(nestable_element) + Element.create(page: page, parent_element_id: id, name: nestable_element) + else + log_warning("Element '#{nestable_element}' not a nestable element for '#{name}'. Skipping!") + end + end + end + def select_element(elements, name, order) elements = elements.named(name) if name.present? elements.reorder(position: order).limit(1).first diff --git a/app/models/alchemy/element/element_contents.rb b/app/models/alchemy/element/element_contents.rb index e986dcf4b2..bd1cccd595 100644 --- a/app/models/alchemy/element/element_contents.rb +++ b/app/models/alchemy/element/element_contents.rb @@ -139,8 +139,8 @@ def content_for_rss_meta(type) # creates the contents for this element as described in the elements.yml def create_contents - definition.fetch("contents", []).each do |content_hash| - Content.create_from_scratch(self, content_hash) + definition.fetch('contents', []).each do |attributes| + Content.create(attributes.merge(element: self)) end end end diff --git a/app/models/alchemy/page.rb b/app/models/alchemy/page.rb index 810ec02475..4551e8d868 100644 --- a/app/models/alchemy/page.rb +++ b/app/models/alchemy/page.rb @@ -43,7 +43,7 @@ class Page < BaseRecord include Alchemy::Taggable DEFAULT_ATTRIBUTES_FOR_COPY = { - do_not_autogenerate: true, + autogenerate_elements: false, visible: false, public_on: nil, public_until: nil, @@ -206,7 +206,7 @@ def find_or_create_layout_root_for(language_id) name: "Layoutroot for #{language.name}", layoutpage: true, language: language, - do_not_autogenerate: true, + autogenerate_elements: false, parent_id: Page.root.id ) end diff --git a/app/models/alchemy/page/page_elements.rb b/app/models/alchemy/page/page_elements.rb index 8b46a5fb01..1d8365ed7a 100644 --- a/app/models/alchemy/page/page_elements.rb +++ b/app/models/alchemy/page/page_elements.rb @@ -5,7 +5,7 @@ module Page::PageElements extend ActiveSupport::Concern included do - attr_accessor :do_not_autogenerate + attr_accessor :autogenerate_elements has_many :elements, -> { where(parent_element_id: nil).not_trashed.order(:position) } has_many :trashed_elements, @@ -23,12 +23,13 @@ module Page::PageElements class_name: 'Alchemy::Element', join_table: ElementToPage.table_name - after_create :autogenerate_elements, unless: -> { systempage? || do_not_autogenerate } + after_create :generate_elements, + unless: -> { systempage? || autogenerate_elements == false } after_update :trash_not_allowed_elements!, if: :has_page_layout_changed? - after_update :autogenerate_elements, + after_update :generate_elements, if: :has_page_layout_changed? end @@ -249,13 +250,13 @@ def element_names_from_cell_definitions # # If the page has cells, it looks if there are elements to generate. # - def autogenerate_elements + def generate_elements elements_already_on_page = elements.available.pluck(:name) elements = definition["autogenerate"] if elements.present? elements.each do |element| next if elements_already_on_page.include?(element) - Element.create_from_scratch(attributes_for_element_name(element)) + Element.create(attributes_for_element_name(element)) end end end diff --git a/app/views/alchemy/admin/contents/create.js.erb b/app/views/alchemy/admin/contents/create.js.erb index c2bb120dfa..594cec04f4 100644 --- a/app/views/alchemy/admin/contents/create.js.erb +++ b/app/views/alchemy/admin/contents/create.js.erb @@ -2,7 +2,7 @@ var editor_html = '<%= j(render "alchemy/essences/#{@content.essence_partial_nam content: @content, options: options_from_params, html_options: @html_options }) %>'; -$("[data-element-<%= @element.id %>-missing-content=\"<%= @content.name %>\"]").replaceWith(editor_html); +$("[data-element-<%= @content.element_id %>-missing-content=\"<%= @content.name %>\"]").replaceWith(editor_html); <% if @content.essence_type == "Alchemy::EssencePicture" && @content.ingredient %> @@ -10,7 +10,7 @@ $('#picture_to_assign_<%= @content.ingredient.id %> a').attr('href', '#').off('c <% elsif @content.essence_type == "Alchemy::EssenceDate" %> -Alchemy.Datepicker('#element_<%= @element.id %>'); +Alchemy.Datepicker('#element_<%= @content.element_id %>'); <% elsif @content.essence_type == "Alchemy::EssenceRichtext" %> @@ -20,4 +20,4 @@ Alchemy.Tinymce.initEditor(<%= @content.id %>); Alchemy.reloadPreview(); Alchemy.closeCurrentDialog(); -Alchemy.SelectBox("#element_<%= @element.id %>"); +Alchemy.SelectBox("#element_<%= @content.element_id %>"); diff --git a/bin/rspec b/bin/rspec index d7be13f3f4..d72fadf396 100755 --- a/bin/rspec +++ b/bin/rspec @@ -1,8 +1,3 @@ #!/usr/bin/env ruby -begin - load File.expand_path('spring', __dir__) -rescue LoadError => e - raise unless e.message.include?('spring') -end require 'bundler/setup' load Gem.bin_path('rspec-core', 'rspec') diff --git a/bin/spring b/bin/spring deleted file mode 100755 index 9bc076b9ea..0000000000 --- a/bin/spring +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env ruby - -# This file loads spring without using Bundler, in order to be fast. -# It gets overwritten when you run the `spring binstub` command. - -unless defined?(Spring) - require 'rubygems' - require 'bundler' - - lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) - if spring = lockfile.specs.detect { |spec| spec.name == "spring" } - Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path - gem 'spring', spring.version - require 'spring/binstub' - end -end diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 6324978fad..3bc13cb74e 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -29,7 +29,7 @@ "file": "app/views/alchemy/admin/contents/create.js.erb", "line": 1, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", - "code": "render(action => \"alchemy/essences/#{Content.create_from_scratch(Element.find(params[:content][:element_id]), content_params).essence_partial_name}_editor\", { :content => Content.create_from_scratch(Element.find(params[:content][:element_id]), content_params), :options => options_from_params, :html_options => ((params[:html_options] or {})) })", + "code": "render(action => \"alchemy/essences/#{Content.create(Element.find(params[:content][:element_id]), content_params).essence_partial_name}_editor\", { :content => Content.create(Element.find(params[:content][:element_id]), content_params), :options => options_from_params, :html_options => ((params[:html_options] or {})) })", "render_path": [{"type":"controller","class":"Alchemy::Admin::ContentsController","method":"create","line":21,"file":"app/controllers/alchemy/admin/contents_controller.rb"}], "location": { "type": "template", diff --git a/lib/alchemy/test_support/factories/element_factory.rb b/lib/alchemy/test_support/factories/element_factory.rb index df4c41cfc4..3b68985d4c 100644 --- a/lib/alchemy/test_support/factories/element_factory.rb +++ b/lib/alchemy/test_support/factories/element_factory.rb @@ -3,7 +3,7 @@ FactoryBot.define do factory :alchemy_element, class: 'Alchemy::Element' do name { 'article' } - create_contents_after_create { false } + autogenerate_contents { false } association :page, factory: :alchemy_page trait :unique do @@ -21,7 +21,7 @@ end trait :with_contents do - create_contents_after_create { true } + autogenerate_contents { true } end end end diff --git a/lib/alchemy/test_support/factories/page_factory.rb b/lib/alchemy/test_support/factories/page_factory.rb index a82cb76d60..5b818530b8 100644 --- a/lib/alchemy/test_support/factories/page_factory.rb +++ b/lib/alchemy/test_support/factories/page_factory.rb @@ -13,8 +13,8 @@ end # This speeds up creating of pages dramatically. - # Pass do_not_autogenerate: false to generate elements - do_not_autogenerate { true } + # Pass autogenerate_elements: true to generate elements + autogenerate_elements { false } trait :root do name { 'Root' } diff --git a/lib/alchemy/upgrader/tasks/picture_gallery_migration.rb b/lib/alchemy/upgrader/tasks/picture_gallery_migration.rb index 3ae87bc93c..e749d1bcb8 100644 --- a/lib/alchemy/upgrader/tasks/picture_gallery_migration.rb +++ b/lib/alchemy/upgrader/tasks/picture_gallery_migration.rb @@ -41,7 +41,7 @@ def create_gallery_element(parent) creator: parent.creator, updater: parent.updater, page: parent.page, - create_contents_after_create: false + autogenerate_contents: false ) puts "Created new `#{new_element.name}` for `#{parent.name}`" new_element @@ -55,7 +55,7 @@ def create_element_for_content(content, parent) creator: parent.creator, updater: parent.updater, page: parent.page, - create_contents_after_create: false + autogenerate_contents: false ) content.update_columns(element_id: new_element.id, name: 'picture') diff --git a/spec/controllers/alchemy/admin/elements_controller_spec.rb b/spec/controllers/alchemy/admin/elements_controller_spec.rb index d5b2af74b0..21296ee752 100644 --- a/spec/controllers/alchemy/admin/elements_controller_spec.rb +++ b/spec/controllers/alchemy/admin/elements_controller_spec.rb @@ -218,7 +218,7 @@ module Alchemy end context "if page has cells" do - let(:page) { create(:alchemy_page, :public, do_not_autogenerate: false) } + let(:page) { create(:alchemy_page, :public, autogenerate_elements: true) } let(:cell) { page.cells.first } context "not pasting from clipboard" do @@ -275,7 +275,7 @@ module Alchemy context "with elements already in cell" do before do - cell.elements.create(page_id: page.id, name: "article", create_contents_after_create: false) + cell.elements.create(page_id: page.id, name: "article", autogenerate_contents: false) end it "should set the correct position for the element" do diff --git a/spec/controllers/alchemy/api/pages_controller_spec.rb b/spec/controllers/alchemy/api/pages_controller_spec.rb index be54326ddd..e4d82bc9ac 100644 --- a/spec/controllers/alchemy/api/pages_controller_spec.rb +++ b/spec/controllers/alchemy/api/pages_controller_spec.rb @@ -154,7 +154,7 @@ module Alchemy context "and elements is a comma separated list of element names" do before do - page.send(:autogenerate_elements) + page.send(:generate_elements) end it 'returns all pages as nested json tree with only these elements included' do diff --git a/spec/controllers/alchemy/pages_controller_spec.rb b/spec/controllers/alchemy/pages_controller_spec.rb index 5aefa09e55..66c1facb11 100644 --- a/spec/controllers/alchemy/pages_controller_spec.rb +++ b/spec/controllers/alchemy/pages_controller_spec.rb @@ -20,7 +20,7 @@ module Alchemy name: 'News', urlname: 'news', language: default_language, - do_not_autogenerate: false + autogenerate_elements: true end before do @@ -248,7 +248,7 @@ module Alchemy let(:catalog) { create(:alchemy_page, :public, name: "Catalog", urlname: 'catalog', parent: default_language_root, language: default_language, visible: true) } let(:products) { create(:alchemy_page, :public, name: "Products", urlname: 'products', parent: catalog, language: default_language, visible: true) } - let(:product) { create(:alchemy_page, :public, name: "Screwdriver", urlname: 'screwdriver', parent: products, language: default_language, do_not_autogenerate: false, visible: true) } + let(:product) { create(:alchemy_page, :public, name: "Screwdriver", urlname: 'screwdriver', parent: products, language: default_language, autogenerate_elements: true, visible: true) } before do allow(Alchemy.user_class).to receive(:admins).and_return(OpenStruct.new(count: 1)) @@ -307,7 +307,7 @@ module Alchemy context 'having two pages with the same url names in different languages' do render_views - let!(:klingon_page) { create(:alchemy_page, :public, language: klingon, name: "same-name", do_not_autogenerate: false) } + let!(:klingon_page) { create(:alchemy_page, :public, language: klingon, name: "same-name", autogenerate_elements: true) } let!(:english_page) { create(:alchemy_page, :public, language: default_language, name: "same-name") } before do diff --git a/spec/dummy/config/alchemy/elements.yml b/spec/dummy/config/alchemy/elements.yml index ad981ce9d0..a6b0fec9f9 100644 --- a/spec/dummy/config/alchemy/elements.yml +++ b/spec/dummy/config/alchemy/elements.yml @@ -124,6 +124,8 @@ - name: slider nestable_elements: - slide + autogenerate: + - slide - name: gallery nestable_elements: diff --git a/spec/dummy/config/boot.rb b/spec/dummy/config/boot.rb index 6d2cba07a2..ecd98e180d 100644 --- a/spec/dummy/config/boot.rb +++ b/spec/dummy/config/boot.rb @@ -4,4 +4,5 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +require 'bootsnap/setup' $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/spec/features/admin/attachment_assignment_overlay_spec.rb b/spec/features/admin/attachment_assignment_overlay_spec.rb index e2aa831aab..5a5858c354 100644 --- a/spec/features/admin/attachment_assignment_overlay_spec.rb +++ b/spec/features/admin/attachment_assignment_overlay_spec.rb @@ -8,7 +8,7 @@ end describe "filter by tags", js: true do - let(:a_page) { create(:alchemy_page, do_not_autogenerate: false) } + let(:a_page) { create(:alchemy_page, autogenerate_elements: true) } let(:element) { create(:alchemy_element, page: a_page, name: 'download') } let!(:file1) { create(:alchemy_attachment, file_name: "job_alert.png", tag_list: "jobs") } let!(:file2) { create(:alchemy_attachment, file_name: "keynote.png", tag_list: "presentations") } @@ -20,7 +20,7 @@ click_on "Assign a file" end - within ".alchemy-dialog.modal" do + within ".alchemy-dialog.modal", wait: 15 do # We expect to see both attachments expect(page).to have_selector("#assign_file_list .list a", count: 2) diff --git a/spec/features/admin/page_editing_feature_spec.rb b/spec/features/admin/page_editing_feature_spec.rb index b1799b4ba7..e264f3f2ea 100644 --- a/spec/features/admin/page_editing_feature_spec.rb +++ b/spec/features/admin/page_editing_feature_spec.rb @@ -119,7 +119,7 @@ context 'in element panel' do let!(:everything_page) do - create(:alchemy_page, page_layout: 'everything', do_not_autogenerate: false) + create(:alchemy_page, page_layout: 'everything', autogenerate_elements: true) end it "renders essence editors for all elements" do diff --git a/spec/features/admin/picture_assignment_overlay_spec.rb b/spec/features/admin/picture_assignment_overlay_spec.rb index ef0e1e55f3..01d52c1fb9 100644 --- a/spec/features/admin/picture_assignment_overlay_spec.rb +++ b/spec/features/admin/picture_assignment_overlay_spec.rb @@ -8,7 +8,7 @@ end describe "filter by tags", js: true do - let!(:a_page) { create(:alchemy_page, do_not_autogenerate: false) } + let!(:a_page) { create(:alchemy_page, autogenerate_elements: true) } let!(:pic1) { create(:alchemy_picture, name: "Hill", tag_list: "landscape") } let!(:pic2) { create(:alchemy_picture, name: "Skyscraper", tag_list: "city") } diff --git a/spec/features/page_feature_spec.rb b/spec/features/page_feature_spec.rb index 39b83d4f47..40b0184c65 100644 --- a/spec/features/page_feature_spec.rb +++ b/spec/features/page_feature_spec.rb @@ -36,7 +36,7 @@ module Alchemy end context 'rendered' do - let(:public_page) { create(:alchemy_page, :public, do_not_autogenerate: false) } + let(:public_page) { create(:alchemy_page, :public, autogenerate_elements: true) } let(:article) { public_page.elements.find_by_name('article') } let(:essence) { article.content_by_name('intro').essence } diff --git a/spec/helpers/alchemy/admin/essences_helper_spec.rb b/spec/helpers/alchemy/admin/essences_helper_spec.rb index 3b09af0f04..e3e705f3e5 100644 --- a/spec/helpers/alchemy/admin/essences_helper_spec.rb +++ b/spec/helpers/alchemy/admin/essences_helper_spec.rb @@ -6,7 +6,7 @@ include Alchemy::Admin::ElementsHelper let(:element) do - create(:alchemy_element, name: 'article', create_contents_after_create: true) + create(:alchemy_element, :with_contents, name: 'article') end describe 'essence rendering' do @@ -54,7 +54,7 @@ describe '#pages_for_select' do let(:contact_form) do - create(:alchemy_element, name: 'contactform', create_contents_after_create: true) + create(:alchemy_element, :with_contents, name: 'contactform') end let(:page_a) { create(:alchemy_page, :public, name: 'Page A') } diff --git a/spec/models/alchemy/content_spec.rb b/spec/models/alchemy/content_spec.rb index a954f0adf7..829290e71e 100644 --- a/spec/models/alchemy/content_spec.rb +++ b/spec/models/alchemy/content_spec.rb @@ -4,7 +4,7 @@ module Alchemy describe Content do - let(:element) { create(:alchemy_element, name: 'headline', create_contents_after_create: true) } + let(:element) { create(:alchemy_element, :with_contents, name: 'headline') } let(:content) { element.contents.find_by(essence_type: 'Alchemy::EssenceText') } it "should return the ingredient from its essence" do @@ -49,7 +49,7 @@ module Alchemy describe '#update_essence' do subject { content.update_essence(params) } - let(:element) { create(:alchemy_element, name: 'text', create_contents_after_create: true) } + let(:element) { create(:alchemy_element, :with_contents, name: 'text') } let(:content) { element.contents.first } let(:params) { {} } @@ -73,7 +73,7 @@ module Alchemy end context 'with validations and without params given' do - let(:element) { create(:alchemy_element, name: 'contactform', create_contents_after_create: true) } + let(:element) { create(:alchemy_element, :with_contents, name: 'contactform') } it "should add error messages if save fails and return false" do is_expected.to be_falsey @@ -94,9 +94,7 @@ module Alchemy describe '.copy' do let(:element) do - create :alchemy_element, - name: 'text', - create_contents_after_create: true + create(:alchemy_element, :with_contents, name: 'text') end let(:new_element) do @@ -108,8 +106,7 @@ module Alchemy end it "should create a new record with all attributes of source except given differences" do - copy = Content.copy(content, {name: 'foobar', element_id: new_element.id}) - expect(copy.name).to eq('foobar') + copy = Content.copy(content, {element_id: new_element.id}) expect(copy.element_id).to eq(new_element.id) end @@ -119,70 +116,61 @@ module Alchemy end it "should copy source essence attributes" do + content.essence.update_column(:body, 'Lorem ipsum') copy = Content.copy(content) copy.essence.body == content.essence.body end end - describe '.build' do - let(:element) { build_stubbed(:alchemy_element) } - - it "builds a new instance from elements.yml definition" do - expect(Content.build(element, {name: 'headline'})).to be_instance_of(Content) - end - end - - describe '.content_definition' do - let(:element) { build_stubbed(:alchemy_element) } + describe ".definitions" do + context "without any definitions in elements.yml file" do + before { expect(Element).to receive(:definitions).and_return([]) } - context "with blank name key" do - it "returns a essence hash build from essence type" do - expect(Content).to receive(:content_definition_from_essence_type).with(element, 'EssenceText') - Content.content_definition(element, essence_type: 'EssenceText') + it "should return an empty array" do + expect(Content.definitions).to eq([]) end end - context "with name key present" do - it "returns a essence hash from element" do - expect(element).to receive(:content_definition_for).with('headline') - Content.content_definition(element, name: 'headline') + context "with some element definitions having no contents defined" do + before do + expect(Element).to receive(:definitions) do + [ + { + 'name' => 'foo', + 'contents' => [{'name' => 'title'}] + }, + { + 'name' => 'bar' + } + ] + end end - end - end - describe '.content_definition_from_essence_type' do - let(:element) { build_stubbed(:alchemy_element) } - - it "returns the definition hash from element" do - expect(Content).to receive(:content_name_from_element_and_essence_type).with(element, 'EssenceText').and_return('Foo') - expect(Content.content_definition_from_essence_type(element, 'EssenceText')).to eq({ - 'type' => 'EssenceText', - 'name' => 'Foo' - }) + it "returns only content definitions" do + expect(Content.definitions).to match_array( + [{'name' => 'title'}] + ) + end end end - describe '.content_name_from_element_and_essence_type' do + describe '.new' do let(:element) { build_stubbed(:alchemy_element) } - it "returns a name from essence type and count of essences in element" do - expect(Content.content_name_from_element_and_essence_type(element, 'EssenceText')).to eq("essence_text_1") + it "builds a new instance from elements.yml definition" do + expect(Content.new({element: element, name: 'headline'})).to be_instance_of(Content) end end - describe '.create_from_scratch' do + describe '.create' do let(:element) { create(:alchemy_element, name: 'article') } it "builds the content" do - expect(Content.create_from_scratch(element, name: 'headline')).to be_instance_of(Alchemy::Content) + expect(Content.create(element: element, name: 'headline')).to be_instance_of(Alchemy::Content) end - it "creates the essence from name" do - expect(Content.create_from_scratch(element, name: 'headline').essence).to_not be_nil - end - - it "creates the essence from essence_type" do - expect(Content.create_from_scratch(element, essence_type: 'EssenceText').essence).to_not be_nil + it "creates the essence" do + expect(Content.create(element: element, name: 'headline').essence).to_not be_nil end context "with default value present" do @@ -190,7 +178,7 @@ module Alchemy allow_any_instance_of(Element).to receive(:content_definition_for) do {'name' => 'headline', 'type' => 'EssenceText', 'default' => 'Welcome'} end - content = Content.create_from_scratch(element, name: 'headline') + content = Content.create(element: element, name: 'headline') expect(content.ingredient).to eq("Welcome") end end @@ -200,31 +188,21 @@ module Alchemy let(:element) { create(:alchemy_element, name: 'headline') } it "should set the given value to the ingredient column of essence" do - c = Content.create_from_scratch(element, name: 'headline') + c = Content.create(element: element, name: 'headline') c.ingredient = "Welcome" expect(c.ingredient).to eq("Welcome") end context "no essence associated" do let(:element) { create(:alchemy_element, name: 'headline') } + let(:content) { Alchemy::Content.new(element: element, name: 'headline').tap(&:save) } it "should raise error" do - content = Content.create(element_id: element.id, name: 'headline') expect { content.ingredient = "Welcome" }.to raise_error(EssenceMissingError) end end end - describe "#definitions" do - context "without any definitions in elements.yml file" do - before { allow(Element).to receive(:definitions).and_return([]) } - - it "should return an empty array" do - expect(Content.definitions).to eq([]) - end - end - end - describe "#dom_id" do let(:content) { build_stubbed(:alchemy_content) } diff --git a/spec/models/alchemy/element_spec.rb b/spec/models/alchemy/element_spec.rb index 7b10b1bbe2..4641d76559 100644 --- a/spec/models/alchemy/element_spec.rb +++ b/spec/models/alchemy/element_spec.rb @@ -9,40 +9,122 @@ module Alchemy # ClassMethods - describe '.new_from_scratch' do + describe '.new' do it "should initialize an element by name from scratch" do - el = Element.new_from_scratch(name: 'article') + el = Element.new(name: 'article') expect(el).to be_an(Alchemy::Element) expect(el.name).to eq('article') end it "should raise an error if the given name is not defined in the elements.yml" do expect { - Element.new_from_scratch(name: 'foobar') + Element.new(name: 'foobar') }.to raise_error(ElementDefinitionError) end it "should take the first part of an given name containing a hash (#)" do - el = Element.new_from_scratch(name: 'article#header') + el = Element.new(name: 'article#header') expect(el.name).to eq("article") end it "should merge given attributes into defined ones" do - el = Element.new_from_scratch(name: 'article', page_id: 1) + el = Element.new(name: 'article', page_id: 1) expect(el.page_id).to eq(1) end it "should not have forbidden attributes from definition" do - el = Element.new_from_scratch(name: 'article') + el = Element.new(name: 'article') expect(el.contents).to eq([]) end end + describe '.create' do + let(:page) { build(:alchemy_page) } + + subject(:element) { described_class.create(page: page, name: 'article') } + + it 'creates contents' do + expect(element.contents).to match_array([ + an_instance_of(Alchemy::Content), + an_instance_of(Alchemy::Content), + an_instance_of(Alchemy::Content), + an_instance_of(Alchemy::Content) + ]) + end + + context 'if autogenerate_contents set to false' do + subject(:element) do + described_class.create( + page: page, + name: 'article', + autogenerate_contents: false + ) + end + + it 'creates contents' do + expect(element.contents).to be_empty + end + end + + context 'if autogenerate is given in definition' do + subject(:element) do + described_class.create(page: page, name: 'slider') + end + + it 'creates nested elements' do + expect(element.nested_elements).to match_array([ + an_instance_of(Alchemy::Element) + ]) + end + + context 'if element name is not a nestable element' do + subject(:element) do + described_class.create( + page: page, + name: 'slider' + ) + end + + before do + expect(Alchemy::Element).to receive(:definitions).at_least(:once) do + [ + {'name' => 'slider', 'nestable_elements' => ['foo'], 'autogenerate' => ['bar']} + ] + end + end + + it 'logs error warning' do + expect_any_instance_of(Alchemy::Logger).to \ + receive(:log_warning).with("Element 'bar' not a nestable element for 'slider'. Skipping!") + element + end + + it 'skips element' do + expect(element.nested_elements).to be_empty + end + end + + context 'if autogenerate_nested_elements set to false' do + subject(:element) do + described_class.create( + page: page, + name: 'slider', + autogenerate_nested_elements: false + ) + end + + it 'creates contents' do + expect(element.contents).to be_empty + end + end + end + end + describe '.copy' do subject { Element.copy(element) } let(:element) do - create(:alchemy_element, create_contents_after_create: true, tag_list: 'red, yellow') + create(:alchemy_element, :with_contents, tag_list: 'red, yellow') end it "should not create contents from scratch" do @@ -50,10 +132,11 @@ module Alchemy end context 'with differences' do - subject(:copy) { Element.copy(element, {name: 'foobar'}) } + let(:new_page) { create(:alchemy_page) } + subject(:copy) { Element.copy(element, {page_id: new_page.id}) } it "should create a new record with all attributes of source except given differences" do - expect(copy.name).to eq('foobar') + expect(copy.page_id).to eq(new_page.id) end end @@ -68,8 +151,7 @@ module Alchemy context 'with nested elements' do let(:element) do - create(:alchemy_element, :with_nestable_elements, { - create_contents_after_create: true, + create(:alchemy_element, :with_contents, :with_nestable_elements, { tag_list: 'red, yellow', page: create(:alchemy_page) }) @@ -309,7 +391,7 @@ module Alchemy # InstanceMethods describe '#all_contents_by_type' do - let(:element) { create(:alchemy_element, create_contents_after_create: true) } + let(:element) { create(:alchemy_element, :with_contents) } let(:expected_contents) { element.contents.essence_texts } context "with namespaced essence type" do @@ -574,7 +656,7 @@ module Alchemy end context 'retrieving contents, essences and ingredients' do - let(:element) { create(:alchemy_element, name: 'news', create_contents_after_create: true) } + let(:element) { create(:alchemy_element, :with_contents, name: 'news') } it "should return an ingredient by name" do expect(element.ingredient('news_headline')).to eq(EssenceText.first.ingredient) @@ -797,7 +879,7 @@ module Alchemy describe "#to_partial_path" do it "should return a String in the format of 'alchemy/elements/#{name}_view'" do - expect(Element.new(name: 'mock').to_partial_path).to eq('alchemy/elements/mock_view') + expect(Element.new(name: 'article').to_partial_path).to eq('alchemy/elements/article_view') end end @@ -866,10 +948,6 @@ module Alchemy context 'with nestable_elements defined' do let(:element) { create(:alchemy_element, :with_nestable_elements) } - before do - element.nested_elements << create(:alchemy_element, name: 'slide') - end - it 'returns an AR scope containing nested elements' do expect(subject.count).to eq(1) end diff --git a/spec/models/alchemy/page_spec.rb b/spec/models/alchemy/page_spec.rb index 793aa806b9..299a69a5cb 100644 --- a/spec/models/alchemy/page_spec.rb +++ b/spec/models/alchemy/page_spec.rb @@ -10,7 +10,7 @@ module Alchemy let(:language_root) { create(:alchemy_page, :language_root) } let(:page) { mock_model(Page, page_layout: 'foo') } let(:public_page) { create(:alchemy_page, :public) } - let(:news_page) { create(:alchemy_page, :public, page_layout: 'news', do_not_autogenerate: false) } + let(:news_page) { create(:alchemy_page, :public, page_layout: 'news', autogenerate_elements: true) } it { is_expected.to have_one(:site) } @@ -247,7 +247,7 @@ module Alchemy context "Saving a normal page" do let(:page) do - build(:alchemy_page, language_code: nil, language: klingon, do_not_autogenerate: false) + build(:alchemy_page, language_code: nil, language: klingon, autogenerate_elements: true) end it "sets the language code" do @@ -335,9 +335,9 @@ module Alchemy end end - context "with do_not_autogenerate set to true" do + context "with autogenerate_elements set to false" do before do - page.do_not_autogenerate = true + page.autogenerate_elements = false end it "should not autogenerate the elements" do @@ -1008,7 +1008,7 @@ module Alchemy end it 'returns an active record collection of all elements including nested elements on that page' do - expect(page.descendent_elements.count).to eq(3) + expect(page.descendent_elements.count).to eq(4) end end @@ -1040,7 +1040,7 @@ module Alchemy end it 'returns an active record collection of all content including nested elements on that page' do - expect(page.descendent_contents.count).to eq(4) + expect(page.descendent_contents.count).to eq(6) end end @@ -1182,7 +1182,7 @@ module Alchemy end describe '#elements_grouped_by_cells' do - let(:page) { create(:alchemy_page, :public, do_not_autogenerate: false) } + let(:page) { create(:alchemy_page, :public, autogenerate_elements: true) } before do allow(PageLayout).to receive(:get).and_return({ @@ -2416,19 +2416,17 @@ module Alchemy let!(:page) { create(:alchemy_page) } let!(:expanded_element) do - create :alchemy_element, + create :alchemy_element, :with_contents, name: 'article', page: page, - folded: false, - create_contents_after_create: true + folded: false end let!(:folded_element) do - create :alchemy_element, + create :alchemy_element, :with_contents, name: 'article', page: page, - folded: true, - create_contents_after_create: true + folded: true end subject(:richtext_contents_ids) { page.richtext_contents_ids } @@ -2442,21 +2440,19 @@ module Alchemy context 'with nested elements' do let!(:nested_expanded_element) do - create :alchemy_element, + create :alchemy_element, :with_contents, name: 'article', page: page, parent_element: expanded_element, - folded: false, - create_contents_after_create: true + folded: false end let!(:nested_folded_element) do - create :alchemy_element, + create :alchemy_element, :with_contents, name: 'article', page: page, parent_element: folded_element, - folded: true, - create_contents_after_create: true + folded: true end it 'returns content ids for all expanded nested elements that have tinymce enabled' do diff --git a/spec/requests/alchemy/admin/contents_controller_spec.rb b/spec/requests/alchemy/admin/contents_controller_spec.rb index dd4d8dce3d..b3cf6cf1e2 100644 --- a/spec/requests/alchemy/admin/contents_controller_spec.rb +++ b/spec/requests/alchemy/admin/contents_controller_spec.rb @@ -17,17 +17,6 @@ module Alchemy post admin_contents_path(content: {element_id: element.id, name: 'headline'}, format: :js) }.to change { Alchemy::Content.count }.by(1) end - - it "creates a content from essence_type" do - expect { - post admin_contents_path( - content: { - element_id: element.id, essence_type: 'EssencePicture' - }, - format: :js - ) - }.to change { Alchemy::Content.count }.by(1) - end end end end