rails g migration CreatePosts title:string tags:string
rails g migration CreateCategories title:string
rails g CreatePostsAndCategoriesJoin
class CreatePosts < ActiveRecord::Migration[6.1] def change create_table :posts do |t| t.string :title t.string :tags, array: true, default: [] t.timestamps end end end class CreateCategories < ActiveRecord::Migration[6.1] def change create_table :categories do |t| t.string :title t.timestamps end end end class CreatePostsAndCategoriesJoin < ActiveRecord::Migration[6.1] def change create_table :posts_categories, id: false do |t| t.belongs_to :post, index: true t.belongs_to :category, index: true end end end
Rails.application.routes.draw do # This line mounts Solidus's routes at the root of your application. # This means, any requests to URLs such as /products, will go to Spree::ProductsController. # If you would like to change where this engine is mounted, simply change the :at option to something different. # # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html # # We ask that you don't use the :as option here, as Solidus relies on it being the default of "spree" mount Spree::Core::Engine, at: '/' Spree::Core::Engine.routes.draw do scope :admin do resources :categories resources :posts end # You may or may not have other things here, # but this is the basic way to correctly scope within the Spree engine. end end
# app/controllers/store_base_controller.rb class StoreBaseController < Spree::BaseController include Spree::Core::ControllerHelpers::Auth include Spree::Core::ControllerHelpers::Store include Spree::Core::ControllerHelpers::Order layout 'spree/layouts/admin' end # app/controllers/spree/posts_controller.rb class Spree::PostsController < StoreBaseController before_action :set_post, only: %i[show edit update destroy] before_action :authenticate_spree_user!, only: %i[new create edit update destroy] before_action :check_status, only: %i[new create edit update destroy] # GET /posts or /posts.json def index @posts = Post.all @categories = Category.all end # GET /posts/1 or /posts/1.json def show; end # GET /posts/new def new @post = Post.new end # GET /posts/1/edit def edit; end # POST /posts or /posts.json def create @post = Post.new(post_params) respond_to do |format| if @post.save format.html { redirect_to post_url(@post), notice: t('notice.model_create', model: t_model) } format.json { render :show, status: :created, location: @post } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @post.errors, status: :unprocessable_entity } end end end # PATCH/PUT /posts/1 or /posts/1.json def update respond_to do |format| if @post.update(post_params) format.html { redirect_to post_url(@post), notice: t('notice.model_update', model: t_model) } format.json { render :show, status: :ok, location: @post } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @post.errors, status: :unprocessable_entity } end end end # DELETE /posts/1 or /posts/1.json def destroy @post.destroy respond_to do |format| format.html { redirect_to posts_url, notice: t('notice.model_destroy', model: t_model) } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_post @post = Post.find(params[:id]) end # Only allow a list of trusted parameters through. def post_params params.require(:post).permit(:title, :content, :tag_list, categories: [], category_ids: []) end end # app/controllers/spree/categories_controller.rb class Spree::CategoriesController < StoreBaseController before_action :set_category, only: %i[show edit update destroy] before_action :authenticate_spree_user!, only: %i[new create edit update destroy] before_action :check_status, only: %i[new create edit update destroy] # GET /categories or /categories.json def index @categories = Category.all end # GET /categories/1 or /categories/1.json def show; end # GET /categories/new def new @category = Category.new end # GET /categories/1/edit def edit; end # POST /categories or /categories.json def create @category = Category.new(category_params) respond_to do |format| if @category.save format.html { redirect_to category_url(@category), notice: t('notice.model_create', model: t_model) } format.json { render :show, status: :created, location: @category } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @category.errors, status: :unprocessable_entity } end end end # PATCH/PUT /categories/1 or /categories/1.json def update respond_to do |format| if @category.update(category_params) format.html { redirect_to category_url(@category), notice: t('notice.model_update', model: t_model) } format.json { render :show, status: :ok, location: @category } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @category.errors, status: :unprocessable_entity } end end end # DELETE /categories/1 or /categories/1.json def destroy @category.destroy respond_to do |format| format.html { redirect_to categories_url, notice: t('notice.model_destroy', model: t_model) } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_category @category = Category.find(params[:id]) end # Only allow a list of trusted parameters through. def category_params params.require(:category).permit(:title, posts: [], post_ids: []) end end
# app/views/spree/posts/index.html.erb <% admin_breadcrumb(Post.model_name.human) %> <% content_for :page_actions do %> <li> <%= link_to t('new'), new_post_path, class: "btn btn-primary" %> </li> <% end %> <div class="mt-5 container"> <% if notice.present? %> <p class="" id="notice"><%= notice %></p> <% end %> <div class="d-flex justify-content-between mb-4"> <div class="d-block"> <h1> <%= Post.model_name.human %> </h1> </div> <div class="d-block"> <%# %> </div> </div> <div id="posts" class="mb-4"> <% @posts.each do |post| %> <div id="<%= dom_id post %>" class="row"> <div class="col"> <strong class="d-block"><%= Post.human_attribute_name(:title) %></strong> <%= post.title %> </div> <div class="col"> <strong class="d-block"><%= Post.human_attribute_name(:content) %></strong> <%= post.content.to_plain_text.truncate(70) %> </div> <div class="col"> <strong class="d-block"><%= Post.human_attribute_name(:tags) %></strong> <%= post.tags.join(', ') %> </div> <div class="col text-right"> <% if action_name != "show" %> <%= link_to t('show'), post_path(post, locale: I18n.locale), class: "btn btn-sm btn-primary btn mx-1" %> <%= link_to t('edit'), edit_post_path(post, locale: I18n.locale), class: "btn btn-sm btn-primary btn mx-1" %> <%= link_to t('destroy'), post_path(post), method: :delete, class: "btn btn-sm btn-warning mx-1", data: { confirm: t('confirm') } %> <% end %> </div> </div> <% end %> </div> <hr> <div class="d-flex justify-content-between mb-4"> <div class="d-block"> <h1> <%= Category.model_name.human %> </h1> </div> <div class="d-block"> <%= link_to t('new'), new_category_path, class: "btn btn-primary" %> </div> </div> <div id="categories" class="mb-4"> <% @categories.each do |category| %> <div id="<%= dom_id category %>" class="row"> <div class="col"> <strong class="d-block"><%= Category.human_attribute_name(:title) %></strong> <%= category.title %> </div> <div class="col"> <strong class="d-block"><%= Category.human_attribute_name(:slug) %></strong> <%= category.slug %> </div> <div class="col"> <strong class="d-block"><%= Post.model_name.human %></strong> <%= category.posts.count %> </div> <div class="col text-right"> <%= link_to t('edit'), edit_category_path(category, locale: I18n.locale), class: "btn btn-sm btn-primary btn mx-1" %> <%= link_to t('destroy'), category_path(category), method: :destroy, class: "btn btn-sm btn-warning mx-1" %> </div> </div> <% end %> </div> </div>
Spree::Backend::Config.configure do |config| # Uncomment and change the following configuration if you want to add # Menu items use icons from fontawesome v4, so you can pick one from here: # https://fontawesome.com/v4/icons/ config.menu_items << config.class::MenuItem.new( [:posts], 'file-text-o', url: :posts_path, condition: -> { can?(:manage, Spree::Order) }
<div class="p-2"> <% if params[:keywords] %> <div data-hook="search_results"> <% if @products.empty? %> <h6 class="search-results-title"><%= t('spree.no_products_found') %></h6> <% else %> <%= render partial: 'spree/shared/products', locals: { products: @products, taxon: @taxon } %> <% end %> </div> <% else %> <div class="text-xl md:text-2xl xl:text-3xl prose py-4 md:py-8 underline decoration-slate-300 decoration-wavy decoration-1 tracking-wider"> <%= t('spree.new_posts') %> </div> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 py-4"> <% Post.last(2).each do |post| %> <div class="col"> <div class="text-base md:text-lg lg:text-xl pb-4"> <%= post.title %> </div> <div class="text-sm lg:text-base prose"> <%= post.content %> </div> <div class="font-light text-sm text-neutral-400"> <%= post.created_at.strftime("%B %d, %Y") %> </div> </div> <% end %> </div> <hr class="pt-2 xl:text-3xl mt-2"> <div data-hook="homepage_products"> <div class="text-xl lg:text-2xl xl:text-3xl prose py-4 md:py-8 underline decoration-slate-300 decoration-wavy decoration-1 tracking-wider"> <%= t('spree.popular_products') %> </div> <%= render partial: 'spree/shared/products', locals: { products: @products, taxon: @taxon } %> </div> <% end %> </div>