diff --git a/backend/app/assets/javascripts/comable/admin/dispatcher.coffee b/backend/app/assets/javascripts/comable/admin/dispatcher.coffee index a375d78a..bf9072fb 100644 --- a/backend/app/assets/javascripts/comable/admin/dispatcher.coffee +++ b/backend/app/assets/javascripts/comable/admin/dispatcher.coffee @@ -15,7 +15,9 @@ class Dispatcher action_name = path[1] switch page - when 'orders:edit' + when 'orders:edit', 'pages:update' new DynamicOrder - when 'pages:new', 'pages:show', 'pages:edit' + when 'pages:new', 'pages:show', 'pages:edit', 'pages:update', 'pages:create' new Page + when 'navigations:new', 'navigations:show', 'navigations:edit', 'navigations:update', 'navigations:create' + new Navigation diff --git a/backend/app/assets/javascripts/comable/admin/navigations.coffee b/backend/app/assets/javascripts/comable/admin/navigations.coffee new file mode 100644 index 00000000..1ad3043e --- /dev/null +++ b/backend/app/assets/javascripts/comable/admin/navigations.coffee @@ -0,0 +1,34 @@ +class @Navigation + constructor: -> + $(document).ready(@ready) + @add_event() + + ready: => + @navigation_items = $('#navigation-items') + @add_fields = $('.add_fields') + + # linkable_idの検索 + search_linkable_ids: -> + $linkable_type = $('#linkable_type') + $position = $('#position') + $linkable_type.val($(this).val()) + $position.val($('.linkable_type').index(this)) + $linkable_type.closest('form').submit() + + # アイテムの追加 + adding_navigation_item_field: -> + regexp = new RegExp($(this).data('index'), 'g') + field_tags = $(this).data('fields').replace(regexp, $('.navigation-item').length) # 置換予定文字を添字に置換する + $('#navigation-items').append(field_tags) # タグを追加 + + # アイテムの削除 + remove_navigation_item_field: -> + $(this).parent().prev('.destroy').val(true) + $(this).closest('fieldset').hide() + + # イベント設定 + add_event: -> + @navigation_items.on('change', '.linkable_type', @search_linkable_ids) + @navigation_items.on('click', '.remove_fields', @remove_navigation_item_field) + @add_fields.click(@adding_navigation_item_field) + diff --git a/backend/app/assets/javascripts/comable/admin/pages.coffee b/backend/app/assets/javascripts/comable/admin/pages.coffee index ce66a2cc..ef0e910b 100644 --- a/backend/app/assets/javascripts/comable/admin/pages.coffee +++ b/backend/app/assets/javascripts/comable/admin/pages.coffee @@ -1,6 +1,9 @@ class @Page constructor: -> $(document).ready(@ready) + @add_event_to_set_visibility() + @add_event_to_set_page_title() + @add_event_to_set_meta_description() ready: => @radio_published = $('#page_published_at_published') @@ -8,9 +11,6 @@ class @Page @published_at = $('#page_published_at') @initialize_visibility() - @add_event_to_set_visibility() - @add_event_to_set_page_title() - @add_event_to_set_meta_description() # 公開/非公開の制御 initialize_visibility: -> diff --git a/backend/app/assets/stylesheets/comable/admin/_navigations.scss b/backend/app/assets/stylesheets/comable/admin/_navigations.scss new file mode 100644 index 00000000..75b5c7e9 --- /dev/null +++ b/backend/app/assets/stylesheets/comable/admin/_navigations.scss @@ -0,0 +1,11 @@ +.add_fields { + margin-right: 30px; +} + +.remove_fields { + margin-top: 25px; +} + +.linkable_id select { + margin-top: 25px; +} diff --git a/backend/app/assets/stylesheets/comable/admin/application.scss b/backend/app/assets/stylesheets/comable/admin/application.scss index 1806e034..220f025a 100644 --- a/backend/app/assets/stylesheets/comable/admin/application.scss +++ b/backend/app/assets/stylesheets/comable/admin/application.scss @@ -22,5 +22,6 @@ @import 'comable/admin/orders'; @import 'comable/admin/products'; @import 'comable/admin/pages'; +@import 'comable/admin/navigations'; @import 'comable/admin/user_sessions'; @import 'comable/admin/themes'; diff --git a/backend/app/controllers/comable/admin/navigations_controller.rb b/backend/app/controllers/comable/admin/navigations_controller.rb new file mode 100644 index 00000000..ec75637d --- /dev/null +++ b/backend/app/controllers/comable/admin/navigations_controller.rb @@ -0,0 +1,84 @@ +require_dependency 'comable/admin/application_controller' + +module Comable + module Admin + class NavigationsController < Comable::Admin::ApplicationController + load_and_authorize_resource class: Comable::Navigation.name, except: :index + + def index + @q = Comable::Navigation.ransack(params[:q]) + @navigations = @q.result.accessible_by(current_ability) + end + + def show + edit + render :edit + end + + def new + @navigation.navigation_items.build + end + + def edit + end + + def create + @navigation = Comable::Navigation.new(navigation_params) + if @navigation.save + redirect_to comable.admin_navigation_path(@navigation), notice: Comable.t('successful') + else + render :new + end + end + + def update + @navigation.attributes = navigation_params + if @navigation.save + redirect_to comable.admin_navigation_path(@navigation), notice: Comable.t('successful') + else + render :edit + end + end + + def destroy + @navigation.destroy + redirect_to comable.admin_navigations_path, notice: Comable.t('successful') + end + + def search_linkable_ids + @linkable_id_options = linkable_id_options + render layout: false + end + + private + + def linkable_type + return if params[:linkable_type].blank? + params[:linkable_type] if Comable.const_defined?(params[:linkable_type].demodulize) + end + + def linkable_id_options + linkable_type ? linkable_type.constantize.linkable_id_options : [[]] + end + + def navigation_params + params.require(:navigation).permit( + :name, + navigation_items_attributes: navigation_items_attributes_keys + ) + end + + def navigation_items_attributes_keys + [ + :id, + :position, + :linkable_id, + :linkable_type, + :name, + :url, + :_destroy + ] + end + end + end +end diff --git a/backend/app/helpers/comable/admin/navigations_helper.rb b/backend/app/helpers/comable/admin/navigations_helper.rb new file mode 100644 index 00000000..9a352402 --- /dev/null +++ b/backend/app/helpers/comable/admin/navigations_helper.rb @@ -0,0 +1,19 @@ +module Comable + module Admin + module NavigationsHelper + def linkable_type_options + Comable::NavigationItem.linkable_params_lists.map { |attr| attr.slice(:name, :type).values } + end + + # アイテム追加ボタン設置 + def add_fields_button_tag(name, f, association) + new_object = f.object.send(association).klass.new + index = new_object.object_id # 後で置換するために必要な文字を入れる + fields = f.fields_for(association, new_object, child_index: index) do |builder| + render(association.to_s.singularize + '_fields', f: builder) + end + button_tag(name, type: :button, class: 'add_fields btn btn-default pull-right', data: { index: index, fields: fields.gsub("\n", '') }) + end + end + end +end diff --git a/backend/app/navigations/comable/admin/application.rb b/backend/app/navigations/comable/admin/application.rb index 2951d41f..506c6097 100644 --- a/backend/app/navigations/comable/admin/application.rb +++ b/backend/app/navigations/comable/admin/application.rb @@ -72,6 +72,10 @@ link comable.admin_pages_path end + item Comable.t('admin.nav.navigation') do + link comable.admin_navigations_path + end + divider item Comable.t('admin.nav.shipment_method') do diff --git a/backend/app/views/comable/admin/navigations/_form.slim b/backend/app/views/comable/admin/navigations/_form.slim new file mode 100644 index 00000000..00006fb0 --- /dev/null +++ b/backend/app/views/comable/admin/navigations/_form.slim @@ -0,0 +1,28 @@ += error_messages_for @navigation + +- url = @navigation.new_record? ? comable.admin_navigations_path : comable.admin_navigation_path(@navigation) + += form_for @navigation, url: url do |f| + .hidden + = f.submit + + fieldset + .col-sm-12 + .form-group + = f.label :name + = f.text_field :name + hr + + #navigation-items= f.fields_for :navigation_items do |navigation_items_form| + = render 'navigation_item_fields', f: navigation_items_form + + hr + + .row + = add_fields_button_tag Comable.t('admin.nav.navigation_items.add_link'), f, :navigation_items + +.hidden + = form_tag(comable.search_linkable_ids_admin_navigations_path, remote: true) do + = text_field_tag :linkable_type + = text_field_tag :position + = submit_tag diff --git a/backend/app/views/comable/admin/navigations/_navigation_item_fields.slim b/backend/app/views/comable/admin/navigations/_navigation_item_fields.slim new file mode 100644 index 00000000..dcad05c4 --- /dev/null +++ b/backend/app/views/comable/admin/navigations/_navigation_item_fields.slim @@ -0,0 +1,24 @@ +fieldset.navigation-item class="#{'hidden' if f.object._destroy }" + .col-sm-12 + .form-group + .row + .col-sm-3 + = f.label :name + = f.text_field :name + + .col-sm-3 + = f.label :linkable_type + = f.select :linkable_type, linkable_type_options, {}, class: :linkable_type + + .col-sm-3.url style="#{ 'display: none;' if f.object.linkable_type? && f.object.linkable_type.constantize.linkable_exists?}" + = f.label :url + = f.text_field :url + + .col-sm-3.linkable_id style="#{ 'display: none;' unless f.object.linkable_type? && f.object.linkable_type.constantize.linkable_exists?}" + = f.select :linkable_id, f.object.linkable_type? ? f.object.linkable_type.constantize.linkable_id_options : [[]] + + = f.hidden_field :_destroy, class: :destroy + + .col-sm-3 + button.btn.btn-default.pull-right.remove_fields type="button" + = Comable.t('actions.destroy') diff --git a/backend/app/views/comable/admin/navigations/edit.slim b/backend/app/views/comable/admin/navigations/edit.slim new file mode 100644 index 00000000..a78b85eb --- /dev/null +++ b/backend/app/views/comable/admin/navigations/edit.slim @@ -0,0 +1,27 @@ +.comable-page + .comable-main-fixed-top + .comable-page-heading + ul.pull-right.list-inline + li + = link_to_save + + h1.page-header + ol.breadcrumb + li> + = link_to Comable.t('admin.nav.navigation'), comable.admin_navigations_path + li.active + = @navigation.name + + .comable-page-body + = render 'form' + hr + .panel.panel-danger + .panel-heading type="button" data-toggle="collapse" data-target="#comable-danger" + strong + span.fa.fa-angle-down> + = Comable.t('admin.actions.destroy') + #comable-danger.collapse + .panel-body + p + = Comable.t('admin.confirmation_to_remove_navigation') + = link_to Comable.t('admin.actions.destroy'), comable.admin_navigation_path(@navigation), method: :delete, class: 'btn btn-danger' diff --git a/backend/app/views/comable/admin/navigations/index.slim b/backend/app/views/comable/admin/navigations/index.slim new file mode 100644 index 00000000..74d6a9e3 --- /dev/null +++ b/backend/app/views/comable/admin/navigations/index.slim @@ -0,0 +1,24 @@ +.comable-page + .comable-page-heading + ul.pull-right.list-inline + li + = link_to Comable.t('admin.actions.new'), comable.new_admin_navigation_path, class: 'btn btn-default' + h1.page-header + = Comable.t('admin.nav.navigation') + small< + | #{@navigations.size} #{Comable.t('admin.results')} + + section + - if @navigations.empty? + = Comable.t('admin.not_found') + - else + table.table.table-striped + thead + th + = sort_link [:comable, @q], :name + + tbody + - @navigations.each do |navigation| + tr + td + = link_to navigation.name, comable.admin_navigation_path(navigation) diff --git a/backend/app/views/comable/admin/navigations/new.slim b/backend/app/views/comable/admin/navigations/new.slim new file mode 100644 index 00000000..2f863680 --- /dev/null +++ b/backend/app/views/comable/admin/navigations/new.slim @@ -0,0 +1,16 @@ +.comable-page + .comable-main-fixed-top + .comable-page-heading + ul.pull-right.list-inline + li + = link_to_save + + h1.page-header + ol.breadcrumb + li> + = link_to Comable.t('admin.nav.navigation'), comable.admin_navigations_path + li.active + = Comable.t('admin.actions.new') + + .comable-page-body + = render 'form' diff --git a/backend/app/views/comable/admin/navigations/search_linkable_ids.coffee b/backend/app/views/comable/admin/navigations/search_linkable_ids.coffee new file mode 100644 index 00000000..3dd61445 --- /dev/null +++ b/backend/app/views/comable/admin/navigations/search_linkable_ids.coffee @@ -0,0 +1,15 @@ +$url = $('#navigation_navigation_items_attributes_<%= params[:position] %>_url') +$linkable_id = $('#navigation_navigation_items_attributes_<%= params[:position] %>_linkable_id') + +$linkable_id.html('<%= options_for_select(@linkable_id_options) %>') + +if <%= params[:linkable_type].blank? %> + # アドレスを入力する場合 + $linkable_id.closest('.linkable_id').hide() + $linkable_id.val('') + $url.closest('.url').show() + +else + $url.closest('.url').hide() + $url.val('') + $linkable_id.closest('.linkable_id').show() diff --git a/backend/config/routes.rb b/backend/config/routes.rb index d0f00799..a51a023f 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -38,6 +38,11 @@ resources :categories resources :pages + + resources :navigations do + post :search_linkable_ids, on: :collection + end + resources :users resources :shipment_methods resources :payment_methods diff --git a/backend/spec/controllers/comable/admin/navigations_controller_spec.rb b/backend/spec/controllers/comable/admin/navigations_controller_spec.rb new file mode 100644 index 00000000..5df76fc3 --- /dev/null +++ b/backend/spec/controllers/comable/admin/navigations_controller_spec.rb @@ -0,0 +1,153 @@ +describe Comable::Admin::NavigationsController do + sign_in_admin + + let(:comable) { controller.comable } + + let(:valid_attributes) do + attributes_for(:navigation).merge( + navigation_items_attributes: build(:navigation_item).attributes + ) + end + let(:invalid_attributes) { valid_attributes.merge(name: 'x' * 1024) } + + describe 'GET index' do + it 'assigns all navigations as @navigations' do + navigation = create(:navigation, navigation_items: [create(:navigation_item)]) + get :index + expect(assigns(:navigations)).to eq([navigation]) + end + end + + describe 'GET show' do + it 'assigns the requested navigation as @navigation' do + navigation = create(:navigation, navigation_items: [create(:navigation_item)]) + get :show, id: navigation.to_param + expect(assigns(:navigation)).to eq(navigation) + end + end + + describe 'GET new' do + it 'assigns a new navigation as @navigation' do + get :new + expect(assigns(:navigation)).to be_a_new(Comable::Navigation) + end + end + + describe 'GET edit' do + it 'assigns the requested navigation as @navigation' do + navigation = create(:navigation, navigation_items: [create(:navigation_item)]) + get :edit, id: navigation.to_param + expect(assigns(:navigation)).to eq(navigation) + end + end + + describe 'POST create' do + describe 'with valid params' do + it 'creates a new Comable::Navigation' do + expect { post :create, navigation: valid_attributes }.to change(Comable::Navigation, :count).by(1) + end + + it 'assigns a newly created navigation as @navigation' do + post :create, navigation: valid_attributes + expect(assigns(:navigation)).to be_a(Comable::Navigation) + expect(assigns(:navigation)).to be_persisted + end + + it 'redirects to the created navigation' do + post :create, navigation: valid_attributes + expect(response).to redirect_to([comable, :admin, Comable::Navigation.last]) + end + end + + describe 'with invalid params' do + it 'assigns a newly created but unsaved navigation as @navigation' do + post :create, navigation: invalid_attributes + expect(assigns(:navigation)).to be_a_new(Comable::Navigation) + end + + it "re-renders the 'new' template" do + post :create, navigation: invalid_attributes + expect(response).to render_template(:new) + end + end + end + + describe 'PUT update' do + let!(:navigation) { create(:navigation, navigation_items: [create(:navigation_item)]) } + + describe 'with valid params' do + let(:new_attributes) { { name: "NEW: #{navigation.name}" } } + + it 'updates the requested navigation' do + put :update, id: navigation.to_param, navigation: new_attributes + navigation.reload + expect(navigation).to have_attributes(new_attributes) + end + + it 'assigns the requested navigation as @navigation' do + put :update, id: navigation.to_param, navigation: valid_attributes + expect(assigns(:navigation)).to eq(navigation) + end + + it 'redirects to the navigation' do + put :update, id: navigation.to_param, navigation: valid_attributes + expect(response).to redirect_to([comable, :admin, navigation.reload]) + end + end + + describe 'with invalid params' do + it 'assigns the navigation as @navigation' do + put :update, id: navigation.to_param, navigation: invalid_attributes + expect(assigns(:navigation)).to eq(navigation) + end + + it "re-renders the 'edit' template" do + put :update, id: navigation.to_param, navigation: invalid_attributes + expect(response).to render_template(:edit) + end + end + end + + describe 'DELETE destroy' do + it 'destroys the requested navigation' do + navigation = create(:navigation, navigation_items: [create(:navigation_item)]) + expect { delete :destroy, id: navigation.to_param }.to change(Comable::Navigation, :count).by(-1) + end + + it 'redirects to the navigations list' do + navigation = create(:navigation, navigation_items: [create(:navigation_item)]) + delete :destroy, id: navigation.to_param + expect(response).to redirect_to([comable, :admin, :navigations]) + end + end + + describe 'POST search_linkable_ids' do + context 'with valid params' do + before do + post :search_linkable_ids, linkable_type: Comable::Product.to_s, position: 0 + end + + it 'assigns the linkable_id_options' do + expect(assigns(:linkable_id_options)).to eq Comable::Product.linkable_id_options + end + + it 'layout false' do + expect(response).to render_template(layout: false) + end + end + + context 'with invalid params' do + before do + post :search_linkable_ids, position: 0 + end + + it 'assigns the linkable_id_options' do + expect(assigns(:linkable_id_options).all?(&:blank?)).to eq true + end + + it 'layout false' do + expect(response).to render_template(layout: false) + end + end + end +end diff --git a/backend/spec/helpers/comable/admin/navigations_helper_spec.rb b/backend/spec/helpers/comable/admin/navigations_helper_spec.rb new file mode 100644 index 00000000..c96f38a5 --- /dev/null +++ b/backend/spec/helpers/comable/admin/navigations_helper_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Comable::Admin::PagesHelper. For example: +# +# describe Comable::Admin::PagesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +describe Comable::Admin::NavigationsHelper, type: :helper do + subject { helper } + + describe '#linkable_type_options' do + it 'returns' do + options = Comable::NavigationItem.linkable_params_lists.map { |attr| attr.slice(:name, :type).values } + expect(subject.linkable_type_options).to eq(options) + end + end +end diff --git a/backend/spec/views/comable/admin/navigations/edit.slim_spec.rb b/backend/spec/views/comable/admin/navigations/edit.slim_spec.rb new file mode 100644 index 00000000..c8e70516 --- /dev/null +++ b/backend/spec/views/comable/admin/navigations/edit.slim_spec.rb @@ -0,0 +1,11 @@ +describe 'comable/admin/navigations/edit' do + let(:navigation) { create(:navigation, navigation_items: [create(:navigation_item)]) } + + before { assign(:navigation, navigation) } + + it 'renders the edit navigation form' do + render + assert_select 'form[action=?]', comable.admin_navigation_path(navigation) + assert_select 'input[name=_method][value=?]', (Rails::VERSION::MAJOR == 3) ? 'put' : 'patch' + end +end diff --git a/backend/spec/views/comable/admin/navigations/index.slim_spec.rb b/backend/spec/views/comable/admin/navigations/index.slim_spec.rb new file mode 100644 index 00000000..2ec1fd5d --- /dev/null +++ b/backend/spec/views/comable/admin/navigations/index.slim_spec.rb @@ -0,0 +1,15 @@ +describe 'comable/admin/navigations/index' do + before do + create_list(:navigation, 2, navigation_items: [create(:navigation_item)]) + end + let!(:navigations) { Comable::Page.all } + let(:q) { Comable::Page.ransack } + + before { assign(:q, q) } + before { assign(:navigations, q.result.page(1)) } + + it 'renders a list of navigations' do + render + expect(rendered).to include(*navigations.map(&:name)) + end +end diff --git a/backend/spec/views/comable/admin/navigations/new.slim_spec.rb b/backend/spec/views/comable/admin/navigations/new.slim_spec.rb new file mode 100644 index 00000000..3be8f9ba --- /dev/null +++ b/backend/spec/views/comable/admin/navigations/new.slim_spec.rb @@ -0,0 +1,10 @@ +describe 'comable/admin/navigations/new' do + let(:navigation) { build(:navigation, navigation_items: [create(:navigation_item)]) } + + before { assign(:navigation, navigation) } + + it 'renders new navigation form' do + render + assert_select 'form[action=?][method=?]', comable.admin_navigations_path, 'post' + end +end diff --git a/core/app/helpers/comable/application_helper.rb b/core/app/helpers/comable/application_helper.rb index 1faa90a9..ca32bb4f 100644 --- a/core/app/helpers/comable/application_helper.rb +++ b/core/app/helpers/comable/application_helper.rb @@ -17,6 +17,10 @@ def current_trackers @curent_trackers ||= (controller_name == 'orders' && action_name == 'create') ? Comable::Tracker.activated : Comable::Tracker.activated.with_place(:everywhere) end + def current_navigations + @current_navigations ||= Comable::Navigation.all + end + def next_order_path comable.next_order_path(state: current_order.state) end diff --git a/core/app/models/comable/navigation.rb b/core/app/models/comable/navigation.rb new file mode 100644 index 00000000..33fa942b --- /dev/null +++ b/core/app/models/comable/navigation.rb @@ -0,0 +1,12 @@ +module Comable + class Navigation < ActiveRecord::Base + include Comable::Ransackable + + has_many :navigation_items + + accepts_nested_attributes_for :navigation_items, allow_destroy: true + + validates :name, length: { maximum: 255 }, presence: true + validates :navigation_items, presence: true + end +end diff --git a/core/app/models/comable/navigation_item.rb b/core/app/models/comable/navigation_item.rb new file mode 100644 index 00000000..0b4add3f --- /dev/null +++ b/core/app/models/comable/navigation_item.rb @@ -0,0 +1,48 @@ +module Comable + class NavigationItem < ActiveRecord::Base + belongs_to :navigation + belongs_to :linkable, polymorphic: true + + acts_as_list scope: :navigation_id + + validates :navigation, presence: true, if: :navigation_id? + validates :linkable, presence: true, if: :linkable_id? + validates :url, presence: true, unless: :linkable_type? + validates :url, length: { maximum: 255 } + validates :name, length: { maximum: 255 }, presence: true + validates :position, uniqueness: { scope: :navigation_id } + + class << self + def linkable_params_lists + [ + web_address_linkable_params, # Web Address + product_linkable_params, # Product + page_linkable_params # Page + ].compact + end + + def web_address_linkable_params + { + type: nil, + name: Comable.t('admin.nav.navigation_items.web_address') + } + end + + def product_linkable_params + return unless Comable::Product.linkable_exists? + { + type: Comable::Product.to_s, + name: Comable.t('products') + } + end + + def page_linkable_params + return unless Comable::Page.linkable_exists? + { + type: Comable::Page.to_s, + name: Comable.t('pages') + } + end + end + end +end diff --git a/core/app/models/comable/page.rb b/core/app/models/comable/page.rb index 07b8c2cc..97dcf3d2 100644 --- a/core/app/models/comable/page.rb +++ b/core/app/models/comable/page.rb @@ -1,8 +1,10 @@ module Comable class Page < ActiveRecord::Base include Comable::Ransackable - + include Comable::Linkable extend FriendlyId + + linkable_columns_keys name: :title friendly_id :title, use: :slugged validates :title, length: { maximum: 255 }, presence: true diff --git a/core/app/models/comable/product.rb b/core/app/models/comable/product.rb index a572cd98..67319e1c 100644 --- a/core/app/models/comable/product.rb +++ b/core/app/models/comable/product.rb @@ -5,6 +5,7 @@ class Product < ActiveRecord::Base include Comable::Liquidable include Comable::Product::Search include Comable::Product::Csvable + include Comable::Linkable has_many :stocks, class_name: Comable::Stock.name, dependent: :destroy has_many :images, class_name: Comable::Image.name, dependent: :destroy @@ -22,6 +23,8 @@ class Product < ActiveRecord::Base ransack_options attribute_select: { associations: :stocks } + linkable_columns_keys use_index: true + # Add conditions for the images association. # Override method of the images association to support Rails 3.x. def images diff --git a/core/app/models/concerns/comable/linkable.rb b/core/app/models/concerns/comable/linkable.rb new file mode 100644 index 00000000..cc7a466f --- /dev/null +++ b/core/app/models/concerns/comable/linkable.rb @@ -0,0 +1,51 @@ +module Comable + module Linkable + extend ActiveSupport::Concern + + module ClassMethods + attr_reader :_linkable_columns, :use_index, :index_only + + def linkable_columns_keys(options = {}) + if options.present? + @_linkable_columns = default_columns_key.merge(options.slice(:name, :id)) + @use_index = options[:use_index] + @index_only = options[:index_only] + else + @_linkable_columns + end + end + + def linkable_columns(*key) + (@_linkable_columns || default_columns_key).slice(*key).values + end + + def linkable_id_options + return [index_option] if @index_only + + # HACK: Rails3系のpluckでは複数フィールドを指定できないためselectとmapでカラムを取得する + # options = pluck(*linkable_columns(:name, :id)) + columns = linkable_columns(:name, :id) + records = select(columns) + options = records.map(&columns.first).zip(records.map(&columns.last)) + @use_index ? options.unshift(index_option) : options + end + + def linkable_exists? + @use_index || exists? + end + + private + + def default_columns_key + { + id: :id, + name: :name + } + end + + def index_option + [Comable.t('admin.actions.index'), nil] + end + end + end +end diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml index 3ee651d7..936a9264 100644 --- a/core/config/locales/en.yml +++ b/core/config/locales/en.yml @@ -33,6 +33,7 @@ en: failure: 'Failure' products: 'Products' pages: 'Pages' + navigations: 'Navigations' home: 'Home' support: 'Support' sample_header: 'Sample text' @@ -110,6 +111,7 @@ en: confirmation_to_remove_product: 'This operation cannot be undone. Would you like to proceed?' confirmation_to_remove_stock: 'This operation cannot be undone. Would you like to proceed?' confirmation_to_remove_page: 'This operation cannot be undone. Would you like to proceed?' + confirmation_to_remove_navigation: 'This operation cannot be undone. Would you like to proceed?' you_can_drag_and_drop: 'Edit the category tree by drag-and-drop.' you_can_right_click: 'Open the context menu by right click.' link_to_add_new_node: 'Add a new category from the following link: ' @@ -170,6 +172,7 @@ en: stock: 'Stocks' category: 'Categories' page: 'Pages' + navigation: 'Navigations' user: 'Users' general_settings: 'General settings' store: 'Store' @@ -196,6 +199,9 @@ en: visibility: 'publishing/closed' published: 'Published' unpublished: 'Unpublished' + navigation_items: + web_address: 'Web Address' + add_link: 'Add Link' stores: edit: 'Editing store' shipment_methods: @@ -397,6 +403,19 @@ en: published_at: 'Published at' created_at: 'Created at' updated_at: 'Updated at' + comable/navigation: + id: 'ID' + name: 'Name' + created_at: 'Created at' + updated_at: 'Updated at' + comable/navigation_item: + id: 'ID' + name: 'Name' + linkable_type: 'Link to' + linkable_id: '' + url: 'Name' + created_at: 'Created at' + updated_at: 'Updated at' enumerize: comable/user: role: diff --git a/core/config/locales/ja.yml b/core/config/locales/ja.yml index ed222fac..60c3cfd1 100644 --- a/core/config/locales/ja.yml +++ b/core/config/locales/ja.yml @@ -33,6 +33,7 @@ ja: failure: 失敗しました products: '商品' pages: 'ページ' + navigations: 'ナビゲーション' home: 'ホーム' support: 'サポート' sample_header: 'サンプルテキスト' @@ -110,6 +111,7 @@ ja: confirmation_to_remove_product: この操作は元に戻せません。商品を削除してもよろしいですか? confirmation_to_remove_stock: この操作は元に戻せません。在庫を削除してもよろしいですか? confirmation_to_remove_page: この操作は元に戻せません。ページを削除してもよろしいですか? + confirmation_to_remove_page: この操作は元に戻せません。ナビゲーションを削除してもよろしいですか? you_can_drag_and_drop: ドラッグ&ドロップの操作でツリーを編集できます。 you_can_right_click: 右クリックでメニューを表示できます。 link_to_add_new_node: '次のリンクから新しいカテゴリを追加できます: ' @@ -170,6 +172,7 @@ ja: stock: 在庫管理 category: カテゴリ page: ページ + navigation: ナビゲーション user: ユーザー general_settings: 一般設定 store: ストア設定 @@ -196,6 +199,9 @@ ja: visibility: 公開/非公開 published: 公開 unpublished: 非公開 + navigation_items: + web_address: アドレス入力 + add_link: リンク追加 stores: edit: 編集 shipment_methods: @@ -397,6 +403,19 @@ ja: published_at: 公開日 created_at: 作成日 updated_at: 更新日 + comable/navigation: + id: ID + name: 名前 + created_at: 作成日 + updated_at: 更新日 + comable/navigation_item: + id: ID + name: 名前 + linkable_type: リンク先 + linkable_id: '' + url: URL + created_at: 作成日 + updated_at: 更新日 enumerize: comable/user: diff --git a/core/db/migrate/20150701094210_create_comable_navigation.rb b/core/db/migrate/20150701094210_create_comable_navigation.rb new file mode 100644 index 00000000..1c2c2dd2 --- /dev/null +++ b/core/db/migrate/20150701094210_create_comable_navigation.rb @@ -0,0 +1,8 @@ +class CreateComableNavigation < ActiveRecord::Migration + def change + create_table :comable_navigations do |t| + t.string :name, null: false + t.timestamps null: false + end + end +end diff --git a/core/db/migrate/20150706085056_create_comable_navigation_item.rb b/core/db/migrate/20150706085056_create_comable_navigation_item.rb new file mode 100644 index 00000000..8bb94673 --- /dev/null +++ b/core/db/migrate/20150706085056_create_comable_navigation_item.rb @@ -0,0 +1,15 @@ +class CreateComableNavigationItem < ActiveRecord::Migration + def change + create_table :comable_navigation_items do |t| + t.references :navigation, null: false + t.integer :linkable_id + t.string :linkable_type + t.integer :position, null: false + t.string :name, null: false + t.string :url + t.timestamps null: false + end + + add_index :comable_navigation_items, [:position, :navigation_id] + end +end diff --git a/core/spec/helpers/comable/application_helper_spec.rb b/core/spec/helpers/comable/application_helper_spec.rb index 77cedc63..bbe213a5 100644 --- a/core/spec/helpers/comable/application_helper_spec.rb +++ b/core/spec/helpers/comable/application_helper_spec.rb @@ -46,4 +46,12 @@ expect(subject.liquid_assigns.keys).to include(*%w( current_store current_comable_user current_order current_trackers form_authenticity_token )) end end + + describe '#current_navigations' do + before { create(:navigation, navigation_items: [create(:navigation_item)]) } + + it 'returns all Navigations' do + expect(subject.current_navigations).to eq(Comable::Navigation.all) + end + end end diff --git a/core/spec/models/comable/navigation_item_spec.rb b/core/spec/models/comable/navigation_item_spec.rb new file mode 100644 index 00000000..d43c2496 --- /dev/null +++ b/core/spec/models/comable/navigation_item_spec.rb @@ -0,0 +1,29 @@ +describe Comable::NavigationItem do + subject { create(:navigation_item) } + + describe 'associations' do + it { is_expected.to belong_to(:navigation).class_name(Comable::Navigation.name) } + end + + describe 'validations' do + it { is_expected.to validate_uniqueness_of(:position).scoped_to(:navigation_id) } + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_length_of(:name).is_at_most(255) } + it { is_expected.to validate_length_of(:url).is_at_most(255) } + + context 'if navigation_id?' do + before { allow(subject).to receive(:navigation_id?).and_return(true) } + it { is_expected.to validate_presence_of(:navigation) } + end + + context 'if linkable_id?' do + before { allow(subject).to receive(:linkable_id?).and_return(true) } + it { is_expected.to validate_presence_of(:linkable) } + end + + context 'unless linkable_type?' do + before { allow(subject).to receive(:linkable_type?).and_return(false) } + it { is_expected.to validate_presence_of(:url) } + end + end +end diff --git a/core/spec/models/comable/navigation_spec.rb b/core/spec/models/comable/navigation_spec.rb new file mode 100644 index 00000000..503608b7 --- /dev/null +++ b/core/spec/models/comable/navigation_spec.rb @@ -0,0 +1,12 @@ +describe Comable::Navigation do + subject { create(:navigation, navigation_items: [create(:navigation_item)]) } + + describe 'associations' do + it { is_expected.to have_many(:navigation_items).class_name(Comable::NavigationItem.name) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_length_of(:name).is_at_most(255) } + end +end diff --git a/frontend/app/views/comable/shared/_footer.slim b/frontend/app/views/comable/shared/_footer.slim index cba3b0d2..3e3d658a 100644 --- a/frontend/app/views/comable/shared/_footer.slim +++ b/frontend/app/views/comable/shared/_footer.slim @@ -35,5 +35,8 @@ footer = link_to Comable.t('change_email_or_password'), comable.edit_user_path li = link_to Comable.t('edit_your_address_book'), comable.addresses_user_path + + = render 'comable/shared/navigation' + .credit | Powered by #{link_to Comable.app_name, Comable.homepage} diff --git a/frontend/app/views/comable/shared/_navigation.slim b/frontend/app/views/comable/shared/_navigation.slim new file mode 100644 index 00000000..d571fa0d --- /dev/null +++ b/frontend/app/views/comable/shared/_navigation.slim @@ -0,0 +1,16 @@ +- if current_navigations + - current_navigations.each do |navigation| + .col-md-4 + h3 + = navigation.name + ul.list-unstyled + - navigation.navigation_items.each do |navigation_item| + li + - if navigation_item.url? + = link_to navigation_item.name, navigation_item.url + + - elsif navigation_item.linkable.present? + = link_to navigation_item.name, polymorphic_url(navigation_item.linkable) + + - else + = link_to navigation_item.name, polymorphic_url(navigation_item.linkable_type.constantize) diff --git a/spec/factories/comable/navigation_items.rb b/spec/factories/comable/navigation_items.rb new file mode 100644 index 00000000..e81acf1f --- /dev/null +++ b/spec/factories/comable/navigation_items.rb @@ -0,0 +1,11 @@ +FactoryGirl.define do + factory :navigation_item, class: Comable::NavigationItem do + navigation { build_stubbed(:navigation) } + name 'navigation_item' + url 'http://comable.example.com' + + trait :page do + association :linkable, factory: :page + end + end +end diff --git a/spec/factories/comable/navigations.rb b/spec/factories/comable/navigations.rb new file mode 100644 index 00000000..04534b49 --- /dev/null +++ b/spec/factories/comable/navigations.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :navigation, class: Comable::Navigation do + name 'footer' + end +end