├── Gemfile ├── lib ├── spree_essential_blog │ ├── version.rb │ └── custom_hooks.rb ├── tasks │ ├── sample │ │ ├── sailing.jpg │ │ ├── sailing2.jpg │ │ └── sailing3.jpg │ └── sample.rake ├── generators │ ├── templates │ │ └── db │ │ │ └── migrate │ │ │ ├── create_post_products.rb │ │ │ ├── create_post_categories.rb │ │ │ ├── create_post_categories_posts.rb │ │ │ ├── create_posts.rb │ │ │ └── acts_as_taggable_on_posts.rb │ └── spree_essentials │ │ └── blog_generator.rb └── spree_essential_blog.rb ├── test ├── dummy_hooks │ ├── after_migrate.rb.sample │ └── before_migrate.rb ├── support │ ├── helper_methods.rb │ └── factories.rb ├── test_helper.rb ├── unit │ ├── post_category_test.rb │ └── post_test.rb └── integration │ ├── admin │ ├── disqus_integration_test.rb │ ├── post_integration_test.rb │ └── post_category_integration_test.rb │ ├── post_category_integration_test.rb │ └── post_integration_test.rb ├── .gitignore ├── Versionfile ├── app ├── controllers │ ├── pages_controller_decorator.rb │ └── blog │ │ ├── admin │ │ ├── post_categories_controller.rb │ │ ├── disqus_settings_controller.rb │ │ ├── post_products_controller.rb │ │ ├── post_images_controller.rb │ │ └── posts_controller.rb │ │ ├── post_categories_controller.rb │ │ └── posts_controller.rb ├── models │ ├── post_product.rb │ ├── blog_configuration.rb │ ├── post_category.rb │ ├── post_image.rb │ └── post.rb ├── views │ └── blog │ │ ├── admin │ │ ├── configurations │ │ │ └── _disqus_config.html.erb │ │ ├── post_products │ │ │ ├── _form.html.erb │ │ │ ├── _related_products_table.html.erb │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ └── index.html.erb │ │ ├── post_images │ │ │ ├── _form.html.erb │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ └── index.html.erb │ │ ├── post_categories │ │ │ ├── _form.html.erb │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ └── index.html.erb │ │ ├── posts │ │ │ ├── edit.html.erb │ │ │ ├── new.html.erb │ │ │ ├── show.html.erb │ │ │ ├── _form.html.erb │ │ │ └── index.html.erb │ │ ├── disqus_settings │ │ │ ├── show.html.erb │ │ │ └── edit.html.erb │ │ └── shared │ │ │ └── _post_tabs.html.erb │ │ ├── post_categories │ │ └── show.html.erb │ │ ├── posts │ │ ├── archive.html.erb │ │ ├── index.rss.builder │ │ ├── index.html.erb │ │ └── show.html.erb │ │ └── shared │ │ ├── _preview.html.erb │ │ ├── _sidebar.html.erb │ │ ├── _disqus_comments.html.erb │ │ └── _archive.html.erb ├── validators │ └── datetime_validator.rb └── helpers │ └── blog │ └── posts_helper.rb ├── Rakefile ├── config ├── routes.rb └── locales │ └── en.yml ├── public └── stylesheets │ └── posts.css ├── spree_essential_blog.gemspec ├── LICENSE └── README.md /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /lib/spree_essential_blog/version.rb: -------------------------------------------------------------------------------- 1 | module SpreeEssentialBlog 2 | VERSION = "0.1.0.rc1" 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy_hooks/after_migrate.rb.sample: -------------------------------------------------------------------------------- 1 | rake "db:migrate db:seed db:sample:blog", :env => "development" 2 | -------------------------------------------------------------------------------- /lib/tasks/sample/sailing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/spree_essential_blog/master/lib/tasks/sample/sailing.jpg -------------------------------------------------------------------------------- /lib/tasks/sample/sailing2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/spree_essential_blog/master/lib/tasks/sample/sailing2.jpg -------------------------------------------------------------------------------- /lib/tasks/sample/sailing3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/spree_essential_blog/master/lib/tasks/sample/sailing3.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | .DS_Store 4 | Gemfile.lock 5 | pkg/* 6 | test/dummy_hooks/after_migrate.rb 7 | test/dummy 8 | -------------------------------------------------------------------------------- /Versionfile: -------------------------------------------------------------------------------- 1 | "0.60.x" => { :version => "0.1.0" } 2 | "0.50.x" => { :version => "0.1.0" } 3 | "0.40.x" => {} 4 | "0.30.x" => {} 5 | "0.10.x" => {} 6 | -------------------------------------------------------------------------------- /test/dummy_hooks/before_migrate.rb: -------------------------------------------------------------------------------- 1 | rake "spree_core:install" 2 | run "rails g spree_essentials:install" 3 | run "rails g spree_essentials:blog" 4 | -------------------------------------------------------------------------------- /app/controllers/pages_controller_decorator.rb: -------------------------------------------------------------------------------- 1 | if defined?(PagesController) 2 | PagesController.instance_eval do 3 | helper 'blog/posts' 4 | end 5 | end -------------------------------------------------------------------------------- /app/models/post_product.rb: -------------------------------------------------------------------------------- 1 | class PostProduct < ActiveRecord::Base 2 | 3 | belongs_to :post 4 | belongs_to :product 5 | 6 | validates_associated :post 7 | validates_associated :product 8 | 9 | end -------------------------------------------------------------------------------- /app/views/blog/admin/configurations/_disqus_config.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= link_to t("blog.settings.disqus"), edit_admin_disqus_settings_path %> 3 | <%= t("blog.settings.explain_disqus") %> 4 | -------------------------------------------------------------------------------- /app/views/blog/admin/post_products/_form.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= form.label :attachment %>:
3 | <%= form.file_field :attachment %> 4 |

5 |

6 | <%= form.label :alt %>:
7 | <%= form.text_field :alt %> 8 |

9 | -------------------------------------------------------------------------------- /app/views/blog/admin/post_images/_form.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= form.label :attachment %>:
3 | <%= form.file_field :attachment %> 4 |

5 |

6 | <%= form.label :alt %>:
7 | <%= form.text_field :alt, :class => 'text' %> 8 |

9 | -------------------------------------------------------------------------------- /app/models/blog_configuration.rb: -------------------------------------------------------------------------------- 1 | class BlogConfiguration < Configuration 2 | 3 | def self.current 4 | self.create if self.count == 0 5 | self.first 6 | end 7 | 8 | preference :disqus_shortname, :string, :default => '' 9 | 10 | end 11 | -------------------------------------------------------------------------------- /app/views/blog/admin/post_categories/_form.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= form.label :name %>:
3 | <%= form.text_field :name, :class => 'text' %> 4 |

5 |

6 | <%= form.label :permalink %>:
7 | <%= form.text_field :permalink, :class => 'text' %> 8 |

9 | -------------------------------------------------------------------------------- /lib/spree_essential_blog/custom_hooks.rb: -------------------------------------------------------------------------------- 1 | module SpreeEssentialBlog 2 | 3 | class CustomHooks < Spree::ThemeSupport::HookListener 4 | 5 | insert_after :admin_configurations_menu, 'blog/admin/configurations/disqus_config' 6 | 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/support/helper_methods.rb: -------------------------------------------------------------------------------- 1 | module HelperMethods 2 | 3 | def random_email 4 | random_token.split("").insert(random_token.length / 2, "@").push(".com").join("") 5 | end 6 | 7 | def random_token 8 | Digest::SHA1.hexdigest((Time.now.to_i * rand).to_s) 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /app/validators/datetime_validator.rb: -------------------------------------------------------------------------------- 1 | class DatetimeValidator < ActiveModel::EachValidator 2 | def validate_each(record, attribute, value) 3 | date = DateTime.parse(value.to_s) rescue ArgumentError 4 | record.errors.add(attribute, I18n.t(:invalid_date_time, :scope => [:activerecord, :errors, :messages])) if date == ArgumentError 5 | end 6 | end -------------------------------------------------------------------------------- /app/views/blog/post_categories/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= render :partial => 'blog/shared/preview', :collection => @posts, :as => :post %> 3 | 4 |
5 | 6 | 7 | <%= will_paginate @posts %> 8 | 9 |
10 | 11 | <% content_for :sidebar do %> 12 | <%= render :partial => 'blog/shared/sidebar' %> 13 | <% end %> 14 | -------------------------------------------------------------------------------- /lib/generators/templates/db/migrate/create_post_products.rb: -------------------------------------------------------------------------------- 1 | class CreatePostProducts < ActiveRecord::Migration 2 | def self.up 3 | create_table :post_products do |t| 4 | t.references :post 5 | t.references :product 6 | t.integer :position 7 | end 8 | end 9 | 10 | def self.down 11 | drop_table :post_products 12 | end 13 | end -------------------------------------------------------------------------------- /lib/generators/templates/db/migrate/create_post_categories.rb: -------------------------------------------------------------------------------- 1 | class CreatePostCategories < ActiveRecord::Migration 2 | def self.up 3 | create_table :post_categories, :force => true do |t| 4 | t.string :name 5 | t.string :permalink 6 | t.timestamps 7 | end 8 | end 9 | 10 | def self.down 11 | drop_table :post_categories 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/generators/templates/db/migrate/create_post_categories_posts.rb: -------------------------------------------------------------------------------- 1 | class CreatePostCategoriesPosts < ActiveRecord::Migration 2 | def self.up 3 | create_table :post_categories_posts, :id => false, :force => true do |t| 4 | t.integer :post_id 5 | t.integer :post_category_id 6 | end 7 | end 8 | 9 | def self.down 10 | drop_table :post_categories_posts 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/views/blog/posts/archive.html.erb: -------------------------------------------------------------------------------- 1 |

Archive

2 | 3 |
4 | 5 | <%= render 'blog/shared/archive', :posts => @posts %> 6 | 7 |

<%= t('.where_to_next') %>

8 |

9 | <%= link_to t('.go_to_store'), products_path %> <%= t('or') %> 10 | <%= link_to t('.back_to_posts'), posts_path %> 11 |

12 | 13 |
14 | -------------------------------------------------------------------------------- /app/views/blog/admin/post_categories/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Categories"} unless request.xhr? %> 2 | 3 |

<%= t("new_category") %>

4 | 5 | <%= form_for(@post_category, :url => admin_post_categories_path(@post)) do |form| %> 6 | <%= render "form", :form => form %> 7 |

8 | <%= button t("create") %> 9 |

10 | <% end %> 11 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Envinronment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require 'rubygems' 5 | require 'spork' 6 | 7 | Spork.prefork do 8 | require File.expand_path("../dummy/config/environment.rb", __FILE__) 9 | require 'spree_essentials/test_helper' 10 | end 11 | 12 | Spork.each_run do 13 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 14 | include HelperMethods 15 | end 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'rubygems' 3 | begin 4 | require 'bundler/setup' 5 | rescue LoadError 6 | puts 'You must run `gem install bundler` and `bundle install` to run rake tasks' 7 | end 8 | 9 | require 'rake' 10 | require 'rake/testtask' 11 | 12 | Bundler::GemHelper.install_tasks 13 | 14 | Rake::TestTask.new(:test) do |t| 15 | t.libs << 'lib' 16 | t.libs << 'test' 17 | t.pattern = 'test/**/*_test.rb' 18 | t.verbose = false 19 | end 20 | 21 | task :default => :test -------------------------------------------------------------------------------- /app/views/blog/admin/posts/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Post Details"} %> 2 | 3 | <% if @post.try(:errors).present? %> 4 | <%= render 'shared/error_messages', :target => @post %> 5 | <% end %> 6 | 7 | <%= form_for([:admin, @post]) do |f| %> 8 | <%= render "form", :form => f %> 9 |

10 | <%= button t("update") %> <%= t('or') %> <%= link_to t('cancel'), collection_url %> 11 |

12 | <% end %> 13 | -------------------------------------------------------------------------------- /app/views/blog/admin/posts/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'admin/shared/contents_sub_menu' %> 2 | 3 |

<%= t('.new_post') %>

4 | 5 | <% if @post.try(:errors).present? %> 6 | <%= render 'shared/error_messages', :target => @post %> 7 | <% end %> 8 | 9 | <%= form_for([:admin, @post]) do |f| %> 10 | <%= render "form", :form => f %> 11 |

12 | <%= button t("create") %> <%= t('or') %> <%= link_to t('cancel'), collection_url %> 13 |

14 | <% end %> 15 | -------------------------------------------------------------------------------- /app/views/blog/admin/post_categories/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Categories"} %> 2 | 3 |

<%= t(".edit_category") %>

4 | 5 | <%= form_for([:admin, @post, @post_category], :url => admin_post_category_path(@post, @post_category.id)) do |form| %> 6 | <%= render "form", :form => form %> 7 |

8 | <%= hidden_field_tag :post_id, @post.path, :class => 'hidden' %> 9 | <%= button t("update") %> 10 |

11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/blog/admin/disqus_settings/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'admin/shared/configuration_menu' %> 2 | 3 |

<%= t("blog.settings.disqus") %>

4 | 5 | 13 | 14 |

<%= link_to_with_icon 'edit', t("edit"), edit_admin_disqus_settings_path, :id => 'admin_disqus_settings_link' %>

15 | -------------------------------------------------------------------------------- /app/views/blog/posts/index.rss.builder: -------------------------------------------------------------------------------- 1 | xml.instruct! :xml, :version => "1.0" 2 | xml.rss :version => "2.0" do 3 | xml.channel do 4 | xml.title "#{Spree::Config[:site_name]} - Blog" 5 | xml.description "#{Spree::Config[:site_url]} - Blog" 6 | xml.link posts_url 7 | 8 | for post in @posts 9 | xml.item do 10 | xml.title post.title 11 | xml.description post_rss(post) 12 | xml.pubDate post.posted_at.to_s(:rfc822) 13 | xml.link post_seo_path(post) 14 | end 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /app/controllers/blog/admin/post_categories_controller.rb: -------------------------------------------------------------------------------- 1 | class Blog::Admin::PostCategoriesController < Admin::ResourceController 2 | 3 | before_filter :load_data 4 | 5 | def location_after_save 6 | admin_post_categories_url(@post) 7 | end 8 | 9 | private 10 | 11 | def translated_object_name 12 | I18n.t('post_category.model_name') 13 | end 14 | 15 | def load_data 16 | @post = Post.find_by_path(params[:post_id]) 17 | @post_categories = PostCategory.all 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/generators/templates/db/migrate/create_posts.rb: -------------------------------------------------------------------------------- 1 | class CreatePosts < ActiveRecord::Migration 2 | def self.up 3 | create_table :posts do |t| 4 | t.string :title, :required => true 5 | t.string :path, :required => true 6 | t.string :teaser 7 | t.datetime :posted_at 8 | t.text :body 9 | t.string :author 10 | t.boolean :live, :default => true 11 | t.timestamps 12 | end 13 | end 14 | 15 | def self.down 16 | drop_table :posts 17 | end 18 | end -------------------------------------------------------------------------------- /app/views/blog/posts/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if @posts.blank? %> 3 |

<%= t('blog.no_posts') %>

4 | <% else %> 5 | <%= render :partial => 'blog/shared/preview', :collection => @posts, :as => :post %> 6 | <% end %> 7 |
8 | 9 | <%= link_to(image_tag('blog/rss.png', :alt => t('.rss')) + t('.rss'), posts_path(:format => 'rss'), :id => "posts-rss") %> 10 | <%= will_paginate @posts %> 11 | 12 |
13 | 14 | <% content_for :sidebar do %> 15 | <%= render :partial => 'blog/shared/sidebar' %> 16 | <% end %> 17 | -------------------------------------------------------------------------------- /app/views/blog/admin/posts/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Post Details"} %> 2 | 3 |
<%= h @post.posted_at.strftime('%A %B %d, %Y at %I:%M%p').gsub(/\s0/, ' ') %>
4 |
5 | 6 | <% for image in @post.images do %> 7 | <%= image_tag image.attachment.url(:small), :alt => @post.title %> 8 | <% end %> 9 | 10 | <%= RDiscount.new(@post.body).to_html.html_safe %> 11 | 12 |
13 | 14 |

15 | <%= link_to_edit @user %>   16 | <%= link_to t('back'), collection_url %> 17 |

18 | -------------------------------------------------------------------------------- /app/views/blog/admin/post_products/_related_products_table.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <% post.post_products.each do |post_product| %> 10 | 11 | 12 | 15 | 16 | <% end %> 17 | 18 |
<%= t('name') %>
<%= post_product.product.name %> 13 | <%= link_to_delete post_product, {:url => admin_post_product_url(post, post_product)} %> 14 |
-------------------------------------------------------------------------------- /app/views/blog/shared/_preview.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

<%= link_to h(post.title), post_seo_path(post) %>

5 |
<%= date_full(post.posted_at) %>
6 |
7 | 8 | <% if post.has_images? %> 9 |

<%= link_to image_tag(post.images.first.attachment.url, :alt => post.title), post_seo_path(post) %>

10 | <% end %> 11 | 12 | <%= post.rendered_preview %> 13 | 14 |
15 | 16 |

<%= link_to t('.read_more'), post_seo_path(post) %>

17 | 18 |
19 | -------------------------------------------------------------------------------- /app/views/blog/admin/disqus_settings/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'admin/shared/configuration_menu' %> 2 | 3 |

<%= t('blog.settings.disqus') %>

4 | 5 | <%= form_for(@config, :url => admin_disqus_settings_path) do |form| %> 6 | 7 |

8 | <%= form.label :preferred_disqus_shortname, t('blog.settings.disqus_shortname') %>:
9 | <%= form.text_field :preferred_disqus_shortname, :class => 'text' %> 10 |

11 | 12 |

13 | <%= button t('update') %> 14 | <%= t("or") %> <%= link_to t("cancel"), admin_disqus_settings_url %> 15 |

16 | <% end %> 17 | -------------------------------------------------------------------------------- /app/views/blog/admin/post_images/new.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t("new_image") %>

2 | 3 | <%= form_for(@post_image, :url => admin_post_images_path(@post), :html => { :multipart => true }) do |form| %> 4 | <%= render "form", :form => form %> 5 |

6 | <%= button t("create") %> 7 | or <%= link_to t("cancel"), "#", :id => "cancel_link" %> 8 |

9 | <% end %> 10 | 11 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/controllers/blog/admin/disqus_settings_controller.rb: -------------------------------------------------------------------------------- 1 | class Blog::Admin::DisqusSettingsController < Admin::BaseController 2 | 3 | def show 4 | @config = BlogConfiguration.current 5 | @preferences = ['disqus_shortname'] 6 | end 7 | 8 | def edit 9 | @config = BlogConfiguration.current 10 | @preferences = ['disqus_shortname'] 11 | end 12 | 13 | def update 14 | BlogConfiguration.current.update_attributes(params[:blog_configuration]) 15 | respond_to do |format| 16 | format.html { 17 | redirect_to admin_disqus_settings_path 18 | } 19 | end 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /app/helpers/blog/posts_helper.rb: -------------------------------------------------------------------------------- 1 | module Blog::PostsHelper 2 | 3 | def post_seo_path(post) 4 | full_post_path(post.year, post.month, post.day, post.to_param) 5 | end 6 | 7 | def post_seo_url(post) 8 | full_post_url(post.year, post.month, post.day, post.to_param) 9 | end 10 | 11 | def post_rss(post) 12 | output = [] 13 | post.images.each do |image| 14 | output << image_tag(image.attachment.url, :alt => image.alt) 15 | end 16 | output << post.rendered_body 17 | output.join("\n").html_safe 18 | end 19 | 20 | def date_full(date) 21 | date.strftime('%A %B %d, %Y').gsub(/\s0/, ' ') 22 | end 23 | 24 | end -------------------------------------------------------------------------------- /app/views/blog/admin/post_products/new.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t("new_product") %>

2 | 3 | <% form_for(@post_product, :url => admin_post_products_path(@post), :html => { :multipart => true }) do |form| %> 4 | 5 | <%= render "form", :form => form %> 6 |
7 |

8 | <%= button t("create") %> 9 | or <%= link_to t("cancel"), "#", :id => "cancel_link" %> 10 |

11 | <% end %> 12 | 13 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/controllers/blog/post_categories_controller.rb: -------------------------------------------------------------------------------- 1 | class Blog::PostCategoriesController < Spree::BaseController 2 | helper 'blog/posts' 3 | 4 | before_filter :get_sidebar, :only => [:index, :search, :show] 5 | 6 | 7 | def show 8 | @category = PostCategory.find_by_permalink(params[:id]) 9 | @posts = @category.posts.live 10 | @posts = @posts.paginate(:page => params[:page], :per_page => Post.per_page) 11 | end 12 | 13 | def get_sidebar 14 | @archive_posts = Post.live.limit(10) 15 | @post_categories = PostCategory.all 16 | get_tags 17 | end 18 | 19 | def get_tags 20 | @tags = Post.live.tag_counts.order('count DESC').limit(25) 21 | end 22 | 23 | end -------------------------------------------------------------------------------- /test/unit/post_category_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../test_helper' 2 | 3 | class PostCategoryTest < Test::Unit::TestCase 4 | 5 | def setup 6 | PostCategory.destroy_all 7 | end 8 | 9 | subject { PostCategory.new } 10 | 11 | should validate_presence_of(:name) 12 | 13 | should "automatically set path" do 14 | @category = Factory.create(:post_category, :name => "This should parameterize") 15 | assert_equal "this-should-parameterize", @category.permalink 16 | end 17 | 18 | should "not duplicate path" do 19 | @category1 = Factory.create(:post_category) 20 | @category2 = Factory.create(:post_category) 21 | assert @category1.permalink != @category2.permalink 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /app/views/blog/admin/post_images/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Images"} %> 2 | 3 | <% if @post_image.try(:errors).present? %> 4 | <%= render 'shared/error_messages', :target => @post_image %> 5 | <% end %> 6 | 7 | <%= form_for([:admin, @post.id, @post_image], :url => admin_post_image_url(@post, @post_image), :html => { :multipart => true }) do |f| %> 8 |

9 | <%= label_tag ("thumbnail") %>:
10 | <%= link_to(image_tag(@post_image.attachment.url(:mini)), @post_image.attachment.url(:product)) %> 11 |

12 | <%= render "form", :form => f %> 13 |

14 | <%= button t("update") %> 15 | or <%= link_to t("cancel"), admin_post_images_url(@post), :id => "cancel_link" %> 16 |

17 | <% end %> -------------------------------------------------------------------------------- /app/controllers/blog/admin/post_products_controller.rb: -------------------------------------------------------------------------------- 1 | class Blog::Admin::PostProductsController < Admin::BaseController 2 | 3 | before_filter :load_data 4 | 5 | def create 6 | position = @post.products.count 7 | @product = Variant.find(params[:variant_id]).product 8 | PostProduct.create(:post_id => @post.id, :product_id => @product.id, :position => position) 9 | render :partial => "admin/blog/post_products/related_products_table", :locals => { :post => @post }, :layout => false 10 | end 11 | 12 | def destroy 13 | @related = PostProduct.find(params[:id]) 14 | if @related.destroy 15 | render_js_for_destroy 16 | end 17 | end 18 | 19 | private 20 | 21 | def load_data 22 | @post = Post.find_by_path(params[:post_id]) 23 | end 24 | 25 | end -------------------------------------------------------------------------------- /app/views/blog/admin/post_products/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Images"} %> 2 | 3 | <% if @post_product.try(:errors).present? %> 4 | <%= render 'shared/error_messages', :target => @post_product %> 5 | <% end %> 6 | 7 | <% form_for([:admin, @post.id, @post_product], :url => admin_post_product_url(@post, @post_product), :html => { :multipart => true }) do |f| %> 8 |

9 | <%= label_tag ("thumbnail") %>:
10 | <%= link_to(product_tag(@post_product.attachment.url(:mini)), @post_product.attachment.url(:product)) %> 11 |

12 | <%= render "form", :form => f %> 13 | 14 |

15 | <%= button t("update") %> 16 | or <%= link_to t("cancel"), admin_post_products_url(@post), :id => "cancel_link" %> 17 |

18 | <% end %> -------------------------------------------------------------------------------- /test/integration/admin/disqus_integration_test.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require_relative '../../test_helper' 5 | 6 | class Admin::DisqusIntegrationTest < ActiveSupport::IntegrationCase 7 | 8 | should "have a link to disqus config" do 9 | visit admin_configurations_path 10 | within "#content table.index" do 11 | assert has_link?("Disqus Settings", :href => edit_admin_disqus_settings_path) 12 | end 13 | end 14 | 15 | should "edit disqus config" do 16 | visit edit_admin_disqus_settings_path 17 | within "form.edit_blog_configuration" do 18 | fill_in "Disqus Shortname", :with => "its-just-a-test" 19 | click_button "Update" 20 | end 21 | assert_equal current_path, admin_disqus_settings_path 22 | assert_seen "its-just-a-test", :within => "#content .member-list h3" 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /app/views/blog/shared/_sidebar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |

<%= t('.recent_posts') %>

4 | <%= render 'blog/shared/archive', :posts => @archive_posts %> 5 | 6 |

<%= link_to t('.view_archive'), archive_posts_path %>

7 | 8 |
9 |

<%= t('.categories') %>

10 | 15 |
16 | 17 |
18 |

<%= t('.tags') %>

19 | 24 |
25 | 26 |
-------------------------------------------------------------------------------- /app/controllers/blog/admin/post_images_controller.rb: -------------------------------------------------------------------------------- 1 | class Blog::Admin::PostImagesController < Admin::ResourceController 2 | 3 | before_filter :load_data 4 | 5 | create.before :set_viewable 6 | update.before :set_viewable 7 | destroy.before :destroy_before 8 | 9 | def update_positions 10 | params[:positions].each do |id, index| 11 | PostImage.update_all(['position=?', index], ['id=?', id]) 12 | end 13 | 14 | respond_to do |format| 15 | format.js { render :text => 'Ok' } 16 | end 17 | end 18 | 19 | private 20 | 21 | def location_after_save 22 | admin_post_images_url(@post) 23 | end 24 | 25 | def load_data 26 | @post = Post.find_by_path(params[:post_id]) 27 | end 28 | 29 | def set_viewable 30 | @post_image.viewable = @post 31 | end 32 | 33 | def destroy_before 34 | @viewable = @post_image.viewable 35 | end 36 | 37 | end -------------------------------------------------------------------------------- /lib/generators/templates/db/migrate/acts_as_taggable_on_posts.rb: -------------------------------------------------------------------------------- 1 | class ActsAsTaggableOnPosts < ActiveRecord::Migration 2 | def self.up 3 | return if table_exists? :tags 4 | 5 | create_table :tags do |t| 6 | t.string :name 7 | end 8 | 9 | create_table :taggings do |t| 10 | t.references :tag 11 | 12 | # You should make sure that the column created is 13 | # long enough to store the required class names. 14 | t.references :taggable, :polymorphic => true 15 | t.references :tagger, :polymorphic => true 16 | 17 | t.string :context 18 | 19 | t.datetime :created_at 20 | end 21 | 22 | add_index :taggings, :tag_id 23 | add_index :taggings, [:taggable_id, :taggable_type, :context] 24 | end 25 | 26 | def self.down 27 | return unless table_exists? :tags 28 | drop_table :tags 29 | drop_table :taggings 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /test/unit/post_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../test_helper' 2 | 3 | class PostTest < Test::Unit::TestCase 4 | 5 | def setup 6 | Post.destroy_all 7 | end 8 | 9 | subject { Post.new } 10 | 11 | should validate_presence_of(:title) 12 | should validate_presence_of(:body) 13 | 14 | should "automatically set path" do 15 | @post = Factory.create(:post, :title => "This should parameterize") 16 | assert_equal "this-should-parameterize", @post.path 17 | end 18 | 19 | should "validate date time" do 20 | @post = Factory.build(:post) 21 | @post.posted_at = "testing" 22 | assert !@post.valid? 23 | end 24 | 25 | should "parse date time" do 26 | date = DateTime.parse("2011/4/1 16:15") 27 | @post = Factory.build(:post) 28 | @post.posted_at = "april 1 2011 - 4:15 pm" 29 | assert @post.valid? 30 | assert_equal date, @post.posted_at 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lib/generators/spree_essentials/blog_generator.rb: -------------------------------------------------------------------------------- 1 | require 'generators/essentials_base' 2 | 3 | module SpreeEssentials 4 | module Generators 5 | class BlogGenerator < SpreeEssentials::Generators::EssentialsBase 6 | 7 | desc "Installs required migrations for spree_essentials_blog" 8 | source_root File.expand_path("../../templates/db/migrate", __FILE__) 9 | 10 | def copy_migrations 11 | migration_template "create_posts.rb", "db/migrate/create_posts.rb" 12 | migration_template "create_post_products.rb", "db/migrate/create_post_products.rb" 13 | migration_template "acts_as_taggable_on_posts.rb", "db/migrate/acts_as_taggable_on_posts.rb" 14 | migration_template "create_post_categories.rb", "db/migrate/create_post_categories.rb" 15 | migration_template "create_post_categories_posts.rb", "db/migrate/create_post_categories_posts.rb" 16 | end 17 | 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /app/controllers/blog/admin/posts_controller.rb: -------------------------------------------------------------------------------- 1 | class Blog::Admin::PostsController < Admin::ResourceController 2 | 3 | private 4 | 5 | update.before :set_category_ids 6 | 7 | def set_category_ids 8 | if params[:post] && params[:post][:post_category_ids].is_a?(Array) 9 | params[:post][:post_category_ids].reject!{|i| i.to_i == 0 } 10 | end 11 | end 12 | 13 | def translated_object_name 14 | I18n.t('post.model_name') 15 | end 16 | 17 | def location_after_save 18 | params[:redirect_to] || object_url 19 | end 20 | 21 | def find_resource 22 | @object ||= Post.find_by_path(params[:id]) 23 | end 24 | 25 | def collection 26 | params[:search] ||= {} 27 | params[:search][:meta_sort] ||= "posted_at.desc" 28 | @search = Post.metasearch(params[:search]) 29 | @collection = @search.paginate(:per_page => Spree::Config[:orders_per_page], :page => params[:page]) 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /app/models/post_category.rb: -------------------------------------------------------------------------------- 1 | class PostCategory < ActiveRecord::Base 2 | 3 | validates :name, :presence => true 4 | validates :permalink, :presence => true, :uniqueness => true, :if => proc{ |record| !record.name.blank? } 5 | 6 | has_and_belongs_to_many :posts 7 | 8 | before_validation :create_permalink 9 | 10 | def to_param 11 | permalink 12 | end 13 | 14 | private 15 | 16 | def create_permalink 17 | count = 2 18 | new_permalink = name.to_s.parameterize 19 | exists = permalink_exists?(new_permalink) 20 | while exists do 21 | dupe_permalink = "#{new_permalink}_#{count}" 22 | exists = permalink_exists?(dupe_permalink) 23 | count += 1 24 | end 25 | self.permalink = dupe_permalink || new_permalink 26 | end 27 | 28 | def permalink_exists?(new_permalink) 29 | post_category = PostCategory.find_by_permalink(new_permalink) 30 | post_category != nil && !(post_category == self) 31 | end 32 | 33 | end -------------------------------------------------------------------------------- /lib/spree_essential_blog.rb: -------------------------------------------------------------------------------- 1 | require 'spree_essentials' 2 | require 'acts-as-taggable-on' 3 | require 'spree_essential_blog/custom_hooks' 4 | 5 | module SpreeEssentialBlog 6 | 7 | def self.tab 8 | [:posts, :post_images, :post_products ] 9 | end 10 | 11 | def self.sub_tab 12 | [:posts, { :label => 'admin.subnav.posts', :match_path => '/posts' }] 13 | end 14 | 15 | class Engine < Rails::Engine 16 | 17 | config.autoload_paths += %W(#{config.root}/lib) 18 | 19 | initializer "static assets" do |app| 20 | app.middleware.insert_before ::Rack::Lock, ::ActionDispatch::Static, "#{config.root}/public" 21 | end 22 | 23 | def self.activate 24 | Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator.rb")) do |c| 25 | Rails.env.production? ? require(c) : load(c) 26 | end 27 | end 28 | 29 | config.to_prepare &method(:activate).to_proc 30 | 31 | end 32 | 33 | end 34 | 35 | SpreeEssentials.register :blog, SpreeEssentialBlog 36 | -------------------------------------------------------------------------------- /test/support/factories.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :post do 4 | title "Peanut Butter Jelly Time" 5 | posted_at { Time.now + rand(10000) } 6 | body "Vivamus rutrum nunc non neque consectetur quis placerat neque lobortis. Nam vestibulum, arcu sodales feugiat consectetur, nisl orci bibendum elit, eu euismod magna sapien ut nibh. Donec semper quam scelerisque tortor dictum gravida. In hac habitasse platea dictumst. Nam pulvinar, odio sed rhoncus suscipit, sem diam ultrices mauris, eu consequat purus metus eu velit. Proin metus odio, aliquam eget molestie nec, gravida ut sapien. Phasellus quis est sed turpis sollicitudin venenatis sed eu odio. Praesent eget neque eu eros interdum malesuada non vel leo. Sed fringilla porta ligula egestas tincidunt. Nullam risus magna, ornare vitae varius eget, scelerisque a libero." 7 | tag_list "peanut butter, jelly, sandwich, lunch" 8 | live true 9 | end 10 | 11 | factory :post_category do 12 | name "Jellies" 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | scope(:module => "Blog") do 4 | 5 | constraints( 6 | :year => /\d{4}/, 7 | :month => /\d{1,2}/, 8 | :day => /\d{1,2}/ 9 | ) do 10 | get '/blog/:year(/:month(/:day))' => 'posts#index', :as => :post_date 11 | get '/blog/:year/:month/:day/:id' => 'posts#show', :as => :full_post 12 | get '/blog/category/:id' => 'post_categories#show', :as => :post_category 13 | end 14 | 15 | get '/blog/search/:query', :to => 'posts#search', :as => :search_posts, :query => /.*/ 16 | 17 | resources :posts, :path => 'blog' do 18 | get :archive, :on => :collection 19 | end 20 | 21 | namespace :admin do 22 | 23 | resources :posts do 24 | resources :images, :controller => "post_images" do 25 | collection do 26 | post :update_positions 27 | end 28 | end 29 | resources :products, :controller => "post_products" 30 | resources :categories, :controller => "post_categories" 31 | end 32 | 33 | resource :disqus_settings 34 | 35 | end 36 | 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/views/blog/shared/_disqus_comments.html.erb: -------------------------------------------------------------------------------- 1 | <% disqus_short_name = @blog_config.preferred_disqus_shortname %> 2 | <% if !disqus_short_name.blank? %> 3 |
4 |

<%= t('blog.comments') %>

5 |
6 | 17 | 18 | blog comments powered by Disqus 19 |
20 | <% end %> 21 | -------------------------------------------------------------------------------- /app/views/blog/admin/shared/_post_tabs.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'admin/shared/contents_sub_menu' %> 2 | 3 |

<%== t(".editing_post") + " “#{@post.title}”".html_safe %>

4 | 5 | <% content_for :sidebar do %> 6 | 7 |

<%= @post.title %><%= (@post.posted_at || Time.now).strftime('%x') %>

8 |
9 | 10 | 26 |
27 | 28 | <% end %> 29 | -------------------------------------------------------------------------------- /app/views/blog/posts/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sidebar do %> 2 | <%= render :partial => 'blog/shared/sidebar' %> 3 | <% end %> 4 | 5 |
6 | 7 |
8 |
<%= h @post.posted_at.strftime('%A %B %d, %Y').gsub(/\s0/, ' ') %>
9 |

<%=h @post.title %>

10 |
11 | 12 | <% if @post.has_images? %> 13 |
14 | <% @post.images.each_with_index do |image, index| %> 15 |

<%= link_to image_tag(image.attachment.url(:medium), :alt => image.has_alt? ? image.alt : "#{@post.title} - Photo #{index + 1}"), image.attachment.url(:large), :id => "photo_#{index}" %>

16 | <% end %> 17 |
18 | <% end %> 19 | 20 | <%= @post.rendered_body %> 21 | 22 |
23 | 24 |
25 |
Tagged:
26 |

<%= @post.tags.collect{|tag| link_to(tag.name, search_posts_path(tag.name)) }.join(", ").html_safe %>

27 |
28 | 29 | <% unless @post.products.empty? %> 30 |
31 | <%= render 'shared/products', :products => @post.products %> 32 |
33 | <% end %> 34 | 35 | <%= render "blog/shared/disqus_comments" if @blog_config %> 36 | 37 |
38 | -------------------------------------------------------------------------------- /app/views/blog/shared/_archive.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% posts.group_by(&:year).each do |year, posts_by_year| %> 3 |

<%= link_to year, post_date_path(year) %>

4 | <% index = 0 %> 5 | <% posts_by_year.group_by(&:month).each do |month, posts_by_month| %> 6 |
7 |
<%= link_to "#{Date::MONTHNAMES[month]} #{year}", post_date_path(year, month) %>
8 | 23 |
24 | <% end %> 25 |
26 | <% end %> 27 |
28 | 29 | <% content_for :head do %> 30 | <%= stylesheet_link_tag 'posts' %> 31 | <% end %> -------------------------------------------------------------------------------- /lib/tasks/sample.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | namespace :sample do 3 | desc "creates sample blog posts" 4 | task :blog do 5 | 6 | require 'faker' 7 | require Rails.root.join('config/environment.rb') 8 | 9 | image_dir = File.expand_path("../sample", __FILE__) 10 | images = Dir[image_dir + "/*.jpg"] 11 | 12 | product_ids = Product.select('id').all.collect(&:id) rescue [] 13 | 14 | 25.times { |i| 15 | 16 | post = Post.create( 17 | :title => Faker::Lorem.sentence, 18 | :posted_at => Time.now - i * rand(10000000), 19 | :body => Faker::Lorem.paragraph, 20 | :tag_list => Faker::Lorem.words(rand(10)).join(", ") 21 | ) 22 | 23 | rand(5).times { |i| 24 | image = post.images.create(:attachment => File.open(images.sort_by{rand}.first), :alt => Faker::Lorem.sentence) 25 | } 26 | 27 | unless product_ids.empty? 28 | rand(5).times { |i| 29 | post.post_products.create(:product => Product.find(product_ids.sort_by{rand}.first), :position => i) 30 | } 31 | end 32 | 33 | print "*" 34 | 35 | } 36 | 37 | puts "\ndone." 38 | 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/integration/post_category_integration_test.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require_relative '../test_helper' 5 | 6 | class Blog::PostCategoryIntegrationTest < ActiveSupport::IntegrationCase 7 | 8 | include Blog::PostsHelper 9 | 10 | def setup 11 | Post.destroy_all 12 | PostCategory.destroy_all 13 | @categories = %w(Jellies Peanuts Butters).map{ |i| Factory.create(:post_category, :name => i) } 14 | 3.times{|i| 15 | post = Factory.create(:post, :title => "Capy post #{i}", :posted_at => Time.now - i.days) 16 | post.categories = [@categories.slice(i)] 17 | post.save 18 | } 19 | @posts = Post.order(:id).all 20 | end 21 | 22 | should "get the blog page" do 23 | visit posts_path 24 | within ".post-sidebar .post-categories" do 25 | assert_seen "Categories" 26 | @categories.each do |i| 27 | assert has_link?(i.name, :href => post_category_path(i)) 28 | end 29 | end 30 | end 31 | 32 | should "only have the first post in the first category" do 33 | @post = @posts.shift 34 | visit post_category_path(@categories.first) 35 | assert_seen @post.title, :within => ".post-title h2" 36 | within "#content .posts" do 37 | @posts.each do |i| 38 | assert !has_content?(i.title) 39 | end 40 | end 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /public/stylesheets/posts.css: -------------------------------------------------------------------------------- 1 | /* ========================================== 2 | Post Archive 3 | */ 4 | 5 | .posts { 6 | position: relative; 7 | } 8 | 9 | #posts-rss { 10 | display: block; 11 | position: absolute; 12 | left: 0; 13 | bottom: 4px; 14 | line-height: 16px; 15 | text-decoration: none; 16 | img { 17 | display: block; 18 | float: left; 19 | margin-right: 5px; 20 | } 21 | } 22 | 23 | .post-archive ul.archive { 24 | list-style-type: none; 25 | text-decoration: none; 26 | margin: 0; 27 | } 28 | .post-archive h4.year, 29 | .post-archive h5.month, 30 | .post-archive ul.archive li span.day { 31 | display: block; 32 | position: relative; 33 | border-bottom: 1px solid #ccc; 34 | font-size: 17px; 35 | font-weight: normal; 36 | margin: 0 0 0.5em 0; 37 | } 38 | .post-archive ul.archive li ul.posts { 39 | list-style-type: none; 40 | display: block; 41 | float: left; 42 | width: 90%; 43 | margin: 0 0 1em 0; 44 | padding: 0; 45 | } 46 | .post-archive ul.archive li ul.posts li { 47 | margin: 0 0 0.5em 0; 48 | } 49 | 50 | 51 | /* ========================================== 52 | Post Preview 53 | */ 54 | 55 | .post-preview { 56 | clear: both; 57 | padding-bottom: 1em; 58 | border-bottom: 1px solid #ccc; 59 | } 60 | .post-preview h2 { 61 | margin-bottom: 0; 62 | } 63 | .post-comments { 64 | clear: both; 65 | } -------------------------------------------------------------------------------- /app/views/blog/admin/post_images/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Images"} %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <% @post.images.each do |image| %> 10 | 11 | 12 | 17 | 18 | <% end %> 19 | 20 |
<%= t("thumbnail") %><%= t("action") %>
<%= link_to(image_tag(image.attachment.url(:mini)), image.attachment.url(:large)) %> 13 | <%= link_to_with_icon('edit', t("edit"), edit_admin_post_image_url(@post, image)) %> 14 |   15 | <%= link_to_delete image, {:url => admin_post_image_url(@post, image) }%> 16 |
21 | 22 |
23 |
24 |

25 | <%= link_to icon('add') + ' ' + t("new_image"), new_admin_post_image_url(@post), :id => "new_image_link" %> 26 |

27 | 28 | <% content_for :head do %> 29 | 40 | <% end %> -------------------------------------------------------------------------------- /app/models/post_image.rb: -------------------------------------------------------------------------------- 1 | class PostImage < Image 2 | 3 | validate :no_attachement_errors 4 | 5 | if defined?(SpreeHeroku) 6 | has_attached_file :attachment, 7 | :styles => Proc.new{ |clip| clip.instance.attachment_sizes }, 8 | :default_style => :medium, 9 | :path => "assets/posts/:id/:style/:basename.:extension", 10 | :storage => "s3", 11 | :s3_credentials => "#{Rails.root}/config/s3.yml" 12 | else 13 | has_attached_file :attachment, 14 | :styles => Proc.new{ |clip| clip.instance.attachment_sizes }, 15 | :default_style => :medium, 16 | :url => "/assets/posts/:id/:style/:basename.:extension", 17 | :path => ":rails_root/public/assets/posts/:id/:style/:basename.:extension" 18 | end 19 | 20 | def image_content? 21 | attachment_content_type.match(/\/(jpeg|png|gif|tiff|x-photoshop)/) 22 | end 23 | 24 | def attachment_sizes 25 | if image_content? 26 | { :mini => '48x48>', :small => '150x150>', :medium => '600x600>', :large => '950x700>' } 27 | else 28 | {} 29 | end 30 | end 31 | 32 | def no_attachement_errors 33 | unless attachment.errors.empty? 34 | # uncomment this to get rid of the less-than-useful interrim messages 35 | # errors.clear 36 | errors.add :attachment, "Paperclip returned errors for file '#{attachment_file_name}' - check ImageMagick installation or image source file." 37 | false 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /app/views/blog/admin/posts/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form.field_container :title do %> 2 | <%= form.label :title, t("post.title") %>
3 | <%= form.text_field :title, :class => 'title' %> 4 | <%= error_message_on :post, :title%> 5 | <% end %> 6 | 7 | <%= form.field_container :posted_at do %> 8 | <%= form.label :posted_at, t("post.posted_at") %>
9 | <%= form.text_field :posted_at, :class => 'text', :value => (@post.posted_at.to_s(:long) unless @post.new_record?) %>
10 | <%= error_message_on :post, :posted_at %> 11 | <% end %> 12 | 13 | <%= form.field_container :body do %> 14 | <%= form.label :body, t("post.body") %><%= markdown_helper %>
15 | <%= form.text_area :body %> 16 | <%= error_message_on :post, :body %> 17 | <% end %> 18 | 19 | <%= form.field_container :tag_list do %> 20 | <%= form.label :tag_list, t("post.tags") %>
21 | <%= form.text_field :tag_list, :class => 'text' %>
22 | <%= error_message_on :post, :tag_list %> 23 | <% end %> 24 | 25 | <%= form.field_container :live do %> 26 | <%= form.check_box :live %> <%= form.label :live, t("post.live") %>
27 | <% end %> 28 | 29 | <% content_for :head do %> 30 | <%= stylesheet_link_tag "markitup.css" %> 31 | <%= javascript_include_tag 'date.js', 'jquery.autodate.js', 'jquery.markitup.js', 'markdown.set.js' %> 32 | 40 | <% end %> -------------------------------------------------------------------------------- /spree_essential_blog.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "spree_essential_blog/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "spree_essential_blog" 7 | s.version = SpreeEssentialBlog::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Spencer Steffen"] 10 | s.email = ["spencer@citrusme.com"] 11 | s.homepage = "https://github.com/citrus/spree_essential_blog" 12 | s.summary = %q{Spree Essential Blog is a blog plugin for Spree sites equipped with spree_essentials.} 13 | s.description = %q{Spree Essential Blog is a blog plugin for Spree sites equipped with spree_essentials.} 14 | 15 | s.files = `git ls-files`.split("\n") 16 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 17 | 18 | s.require_paths = ["lib"] 19 | 20 | # Runtime 21 | s.add_dependency('spree_essentials', '>= 0.1.3') 22 | s.add_dependency('acts-as-taggable-on', '>= 2.0.6') 23 | 24 | # Development 25 | s.add_development_dependency('shoulda', '>= 2.11.3') 26 | s.add_development_dependency('dummier', '>= 0.2.0') 27 | s.add_development_dependency('factory_girl', '= 2.0.0.rc1') 28 | s.add_development_dependency('capybara', '>= 1.0.0') 29 | s.add_development_dependency('selenium-webdriver', '>= 0.2.2') 30 | s.add_development_dependency('sqlite3', '>= 1.3.3') 31 | s.add_development_dependency('spork', '>= 0.9.0.rc9') 32 | s.add_development_dependency('spork-testunit', '>= 0.0.5') 33 | 34 | end 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Spencer Steffen and Citrus Media Group. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Citrus Media Group nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /app/controllers/blog/posts_controller.rb: -------------------------------------------------------------------------------- 1 | class Blog::PostsController < Spree::BaseController 2 | 3 | helper :products 4 | 5 | before_filter :get_sidebar, :only => [:index, :search, :show] 6 | 7 | def index 8 | @posts_by_month = Post.live.limit(50).group_by { |post| post.posted_at.strftime("%B %Y") } 9 | scope = Post.live 10 | if params[:year].present? 11 | year = params[:year].to_i 12 | month = 1 13 | day = 1 14 | if has_month = params[:month].present? 15 | if has_day = params[:day].present? 16 | day = params[:day].to_i 17 | end 18 | month = params[:month].to_i 19 | end 20 | start = Date.new(year, month, day) 21 | stop = start + 1.year 22 | if has_month 23 | stop = start + 1.month 24 | if has_day 25 | stop = start + 1.day 26 | end 27 | end 28 | scope = scope.where("posted_at >= ? AND posted_at <= ?", start, stop) 29 | end 30 | @posts = scope.paginate(:page => params[:page], :per_page => Post.per_page) 31 | end 32 | 33 | def search 34 | query = params[:query].gsub(/%46/, '.') 35 | @posts = Post.live.tagged_with(query).paginate(:page => params[:page], :per_page => Post.per_page) 36 | get_tags 37 | render :template => 'blog/posts/index' 38 | end 39 | 40 | def show 41 | @blog_config = BlogConfiguration.current 42 | @post = Post.live.includes(:tags, :images, :products).find_by_path(params[:id]) rescue nil 43 | return redirect_to archive_posts_path unless @post 44 | end 45 | 46 | def archive 47 | @posts = Post.live.all 48 | end 49 | 50 | def get_sidebar 51 | @archive_posts = Post.live.limit(10) 52 | @post_categories = PostCategory.all 53 | get_tags 54 | end 55 | 56 | def get_tags 57 | @tags = Post.live.tag_counts.order('count DESC').limit(25) 58 | end 59 | 60 | end -------------------------------------------------------------------------------- /app/views/blog/admin/post_products/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Related Products"} %> 2 | 3 |
4 |
5 | <%= t('.add_related_product') %> 6 |
7 | <%= label_tag :add_product_name, t("name_or_sku") %> 8 | <%= text_field_tag :add_product_name, '', :class => 'fullwidth title' %> 9 | <%= hidden_field_tag :add_variant_id %> 10 |
11 |
12 | <%= link_to text_for_button_link(t("add"), :icon => 'add'), 13 | admin_post_products_path(@post), 14 | :id => "add_related_product", :class => 'button' %> 15 |
16 |
17 |
18 | 19 |
20 | <%= render :partial => "related_products_table", :locals => { :post => @post } %> 21 |
22 | 23 | <% content_for :head do %> 24 | 25 | <%= csrf_meta_tag %> 26 | <%= javascript_tag "var expand_variants = false;" %> 27 | 28 | 50 | <% end %> -------------------------------------------------------------------------------- /app/views/blog/admin/post_categories/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'blog/admin/shared/post_tabs', :locals => {:current => "Categories"} %> 2 | 3 |
4 | <%= form_for([:admin, @post]) do |f| %> 5 |
6 | <%= t('.manage_categories') %> 7 | 8 | <% @post_categories.each_with_index do |category, index| -%> 9 | 10 | 14 | 18 | 19 | <% end -%> 20 |
11 | <%= check_box_tag "post[post_category_ids][]", category.id, @post.post_categories.include?(category), :id => "post_category_id_#{index}" -%> 12 | <%= label_tag "post_category_id_#{index}", category.name -%> 13 | 15 | <%= link_to_with_icon('edit', t("edit"), edit_admin_post_category_url(@post, category.id)) %>   16 | <%= link_to_delete category, {:url => admin_post_category_url(@post, category.id)} %> 17 |
21 | <%= hidden_field_tag "post[post_category_ids][]", 0 %> 22 | <%= hidden_field_tag :redirect_to, request.fullpath %> 23 | <%= button t("update") %> 24 |
25 | <% end %> 26 |
27 | 28 |
29 | 30 |

<%= link_to icon('add') + ' ' + t("new_category"), new_admin_post_category_url(@post), :id => "btn_new_category" %>

31 | 32 | 33 | <% content_for :head do %> 34 | 45 | <% end %> -------------------------------------------------------------------------------- /app/views/blog/admin/posts/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => 'admin/shared/contents_sub_menu' %> 2 | 3 |
4 | 9 |
10 |
11 | 12 | 13 |

Listing Posts

14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <%- @collection.each do |post|%> 27 | 28 | 29 | 30 | 31 | 35 | 36 | <% end %> 37 | 38 |
<%= sort_link @search, :title, t("post.title") %><%= sort_link @search, :posted_at, t("post.posted_at") %><%= sort_link @search, :live, t("post.live") %><%= t("action") %>
<%=link_to post.title, object_url(post) %><%=link_to post.posted_at.strftime('%x %X'), object_url(post) %><%= t(post.live ? 'yes' : 'no')%> 32 | <%= link_to_edit post %>   33 | <%= link_to_delete post %> 34 |
39 | 40 | <%= will_paginate(:prev => "« #{t('previous')}", :next => "#{t('next')} »") %> 41 | 42 | <% content_for :sidebar do %> 43 |
44 |

<%= t(:search) %>

45 | 46 | <% @post = Post.metasearch %> 47 | <%= form_for [:admin, @post] do |f| %> 48 | <%- locals = {:f => f} %> 49 | <%= hook :admin_posts_index_search, locals do %> 50 |

51 |
52 | <%= f.text_field :title_like, :size => 25 %> 53 |

54 |
55 |
56 |
57 | <%= f.spree_date_picker :posted_at_greater_than %>
58 | 59 |
60 |
61 | <%= f.spree_date_picker :posted_at_less_than %>
62 | 63 |
64 |
65 |
66 | <% end %> 67 | <%= hook :admin_posts_index_search_buttons, locals do %> 68 |

<%= button t("search") %>

69 | <% end %> 70 | <% end %> 71 |
72 | <% end %> -------------------------------------------------------------------------------- /app/models/post.rb: -------------------------------------------------------------------------------- 1 | class Post < ActiveRecord::Base 2 | 3 | acts_as_taggable 4 | 5 | # for flash messages 6 | alias_attribute :name, :title 7 | 8 | has_and_belongs_to_many :post_categories 9 | alias_attribute :categories, :post_categories 10 | 11 | has_many :post_products, :dependent => :destroy 12 | has_many :products, :through => :post_products 13 | has_many :images, :as => :viewable, :class_name => 'PostImage', :order => :position, :dependent => :destroy 14 | 15 | validates :title, :presence => true 16 | validates :path, :presence => true, :uniqueness => true, :if => proc{ |record| !record.title.blank? } 17 | validates :body, :presence => true 18 | validates :posted_at, :datetime => true 19 | 20 | cattr_reader :per_page 21 | @@per_page = 10 22 | 23 | scope :live, where(:live => true ).order('posted_at DESC') 24 | 25 | before_validation :create_path, :if => proc{ |record| record.title_changed? } 26 | 27 | 28 | # Creates date-part accessors for the posted_at timestamp for grouping purposes. 29 | %w(day month year).each do |method| 30 | define_method method do 31 | self.posted_at.send(method) 32 | end 33 | end 34 | 35 | def rendered_preview 36 | preview = body.split("")[0] 37 | render(preview) 38 | end 39 | 40 | def rendered_body 41 | render(body.gsub("", "")) 42 | end 43 | 44 | def preview_image 45 | images.first if has_images? 46 | end 47 | 48 | def has_images? 49 | images && !images.empty? 50 | end 51 | 52 | 53 | def live? 54 | live && live == true 55 | end 56 | 57 | def to_param 58 | path 59 | end 60 | 61 | 62 | private 63 | 64 | def render(val) 65 | val = val.is_a?(Symbol) ? send(val) : val 66 | RDiscount.new(val).to_html.html_safe 67 | end 68 | 69 | def create_path 70 | #downcase.gsub(/\s/, '-').gsub(/[^a-z0-9\-\_]/, '').gsub(/[\-]+/, '-') 71 | count = 2 72 | new_path = title.to_s.parameterize 73 | exists = path_exists?(new_path) 74 | while exists do 75 | dupe_path = "#{new_path}_#{count}" 76 | exists = path_exists?(dupe_path) 77 | count += 1 78 | end 79 | self.path = dupe_path || new_path 80 | end 81 | 82 | def path_exists?(new_path) 83 | post = ::Post.find_by_path(new_path) 84 | post != nil && !(post == self) 85 | end 86 | 87 | end -------------------------------------------------------------------------------- /test/integration/admin/post_integration_test.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require_relative '../../test_helper' 5 | 6 | class Admin::PostIntegrationTest < ActiveSupport::IntegrationCase 7 | 8 | setup do 9 | Post.destroy_all 10 | @labels = %(Title, Posted At, Body, Tags).split(', ') 11 | @values = %(Just a post, #{Time.now}, #{Faker::Lorem.paragraphs(1 + rand(4)).join("\n\n")}, one tag).split(', ') 12 | end 13 | 14 | should "have a link to new post" do 15 | visit admin_posts_path 16 | btn = find(".actions a.button").native 17 | assert_match /#{new_admin_post_path}$/, btn.attribute('href') 18 | assert_equal "New Post", btn.text 19 | end 20 | 21 | should "get new post" do 22 | visit new_admin_post_path 23 | assert has_content?("New Post") 24 | within "#new_post" do 25 | @labels.each do |f| 26 | assert has_field?(f) 27 | end 28 | end 29 | end 30 | 31 | should "validate post" do 32 | visit new_admin_post_path 33 | click_button "Create" 34 | within "#errorExplanation" do 35 | assert_seen "3 errors prohibited this record from being saved:" 36 | assert_seen "Title can't be blank" 37 | assert_seen "Body can't be blank" 38 | assert_seen "Posted at is an invalid date." 39 | end 40 | end 41 | 42 | should "create a post" do 43 | visit new_admin_post_path 44 | within "#new_post" do 45 | @labels.each_with_index do |label, index| 46 | fill_in label, :with => @values[index] 47 | end 48 | end 49 | click_button "Create" 50 | assert_flash :notice, %(Post "Just a post" has been successfully created!) 51 | end 52 | 53 | context "an existing post" do 54 | setup do 55 | @post = Factory.create(:post) 56 | end 57 | 58 | should "edit and update" do 59 | visit edit_admin_post_path(@post) 60 | 61 | within "#edit_post_#{@post.id}" do 62 | @labels.each_with_index do |label, index| 63 | next if label == 'Posted At' 64 | fill_in label, :with => @values[index].reverse 65 | end 66 | end 67 | click_button "Update" 68 | assert_equal admin_post_path(@post.reload), current_path 69 | assert_flash :notice, %(Post "tsop a tsuJ" has been successfully updated!) 70 | end 71 | 72 | should "get destroyed" do 73 | visit admin_posts_path 74 | find("a[href='#']").click 75 | assert find_by_id("popup_ok").click 76 | end 77 | 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spree Essential Blog 2 | ==================== 3 | 4 | A complete blogging solution for [Spree Commerce](http://spreecommerce.com) with archives, categories, tags, disqus comments and related products. 5 | 6 | This extension relies on [spree_essentials](https://github.com/citrus/spree_essentials) for it's editor, uploads admin and a tab to live under in the admin. 7 | 8 | [todo] write more... 9 | 10 | 11 | Installation 12 | ------------ 13 | 14 | If you don't already have an existing Spree site, [click here](https://gist.github.com/946719) then come back later... You can also read the Spree docs [here](http://spreecommerce.com/documentation/getting_started.html)... 15 | 16 | Otherwise, follow these steps to get up and running with spree_essential_blog: 17 | 18 | First, add spree_essential_blog to your Gemfile... it hasn't been released to Rubygems yet so we'll grab them it git. 19 | 20 | gem 'spree_essential_blog', :git => 'git://github.com/citrus/spree_essential_blog.git' 21 | 22 | Run the generators to create the migration files. 23 | 24 | rails g spree_essentials:install 25 | rails g spree_essentials:blog 26 | 27 | Now migrate your database... 28 | 29 | rake db:migrate 30 | 31 | Boot your server and checkout the admin! 32 | 33 | rails s 34 | 35 | 36 | ### Sample Posts 37 | 38 | If you'd like some sample posts, just use the rake command from your project: 39 | 40 | rake db:sample:blog 41 | 42 | 43 | 44 | Testing 45 | ------- 46 | 47 | Clone this repo to where you develop, bundle up, then run `dummier` to get the show started: 48 | 49 | git clone git://github.com/citrus/spree_essential_blog.git 50 | cd spree_essential_blog 51 | bundle install 52 | bundle exec dummier 53 | 54 | This will generate a fresh rails app in test/dummy, install spree & spree_essential_blog, then migrate the test database. Sweet. 55 | 56 | 57 | ### Spork 58 | 59 | If you want to spork, run: 60 | 61 | bundle exec spork 62 | 63 | In another window, run all tests: 64 | 65 | testdrb test/**/*_test.rb 66 | 67 | Or just a specific test: 68 | 69 | testdrb test/unit/page_test.rb 70 | 71 | 72 | ### No Spork 73 | 74 | If you don't want to spork, just use rake: 75 | 76 | rake 77 | 78 | 79 | 80 | Demo 81 | ---- 82 | 83 | You can easily use the `test/dummy` app as a demo of spree_essential_blog. Just `cd` to where you develop and run: 84 | 85 | git clone git://github.com/citrus/spree_essential_blog.git 86 | cd spree_essential_blog 87 | mv lib/dummy_hooks/after_migrate.rb.sample lib/dummy_hooks/after_migrate.rb 88 | bundle install 89 | bundle exec dummier 90 | cd test/dummy 91 | rails s 92 | 93 | 94 | 95 | 96 | Change Log 97 | ---------- 98 | 99 | **2011/6/7** 100 | 101 | * Added Disqus for comments 102 | * Re-namespaced from Admin::Blog to Blog::Admin 103 | * Added more tests for categories 104 | 105 | 106 | **2011/6/6** 107 | 108 | * pulled GH1 (Thanks [detierno](https://github.com/detierno)!) 109 | * Switched to [dummier](https://github.com/citrus/dummier) for demo & testing 110 | * Improved testing 111 | * Improved documentation 112 | 113 | 114 | **2011/4/13 - 2011/5/27** 115 | 116 | * Initial development 117 | 118 | 119 | Contributors 120 | ------------ 121 | 122 | * Spencer Steffen ([citrus](https://github.com/citrus)) 123 | * Denis Tierno ([detierno](https://github.com/detierno)) 124 | 125 | If you'd like to help out feel free to fork and send me pull requests! 126 | 127 | 128 | License 129 | ------- 130 | 131 | Copyright (c) 2011 Spencer Steffen, released under the New BSD License All rights reserved. 132 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | post: 3 | model_name: Post 4 | title: Title 5 | posted_at: Posted At 6 | body: Body 7 | live: Published 8 | tags: Tags 9 | post_category: 10 | model_name: Category 11 | name: Name 12 | permalink: Permalink 13 | 14 | admin: 15 | subnav: 16 | posts: Posts 17 | 18 | blog: 19 | admin: 20 | posts: 21 | new: 22 | new_post: New Post 23 | post_products: 24 | index: 25 | add_related_product: Add Related Products 26 | post_categories: 27 | index: 28 | manage_categories: Manage Categories 29 | new: 30 | new_category: New Category 31 | edit: 32 | edit_category: Edit Category 33 | 34 | shared: 35 | post_tabs: 36 | post_details: Post Details 37 | editing_post: Editing Post 38 | related_products: Related Products 39 | post_categories: Categories 40 | comments: Comments 41 | home: 42 | title: Recent Posts 43 | archive: Blog Archive 44 | no_posts: No posts found! 45 | posts: 46 | index: 47 | blog: Blog 48 | rss: Subscribe via RSS 49 | archive: 50 | where_to_next: Where to next? 51 | go_to_store: "Shop the Store" 52 | back_to_posts: "Back to the blog" 53 | settings: 54 | disqus: Disqus Settings 55 | explain_disqus: Configure Disqus Comments for Blog 56 | disqus_shortname: Disqus Shortname 57 | shared: 58 | sidebar: 59 | recent_posts: Recent Posts 60 | categories: Categories 61 | archives: Archive 62 | view_archive: View Full Archive 63 | tags: Tags 64 | preview: 65 | read_more: Read More 66 | post_tabs: 67 | editing_post: Editing Post 68 | post_details: Post Details 69 | related_products: Related Products 70 | post_categories: Categories 71 | subnav: 72 | posts: Blog Posts 73 | 74 | 75 | faker: 76 | lorem: 77 | words: [alias, consequatur, perferendis, voluptatem, accusantium, doloremque, aperiam, eaque, ipsa, quae, illo, inventore, veritatis, quasi, architecto, beatae, vitae, dicta, sunt, explicabo, aspernatur, aut, odit, aut, fugit, sed, quia, consequuntur, magni, dolores, eos, qui, ratione, voluptatem, sequi, nesciunt, neque, dolorem, ipsum, quia, dolor, sit, amet, consectetur, adipisci, velit, sed, quia, non, numquam, eius, modi, tempora, incidunt, ut, labore, et, dolore, magnam, aliquam, quaerat, voluptatem, ut, enim, ad, minima, veniam, quis, nostrum, exercitationem, ullam, corporis, nemo, enim, ipsam, voluptatem, quia, voluptas, sit, suscipit, laboriosam, nisi, ut, aliquid, ex, ea, commodi, consequatur, quis, autem, vel, eum, iure, reprehenderit, qui, in, ea, voluptate, velit, esse, quam, nihil, molestiae, et, iusto, odio, dignissimos, ducimus, qui, blanditiis, praesentium, laudantium, totam, rem, voluptatum, deleniti, atque, corrupti, quos, dolores, et, quas, molestias, excepturi, sint, occaecati, cupiditate, non, provident, sed, ut, perspiciatis, unde, omnis, iste, natus, error, similique, sunt, in, culpa, qui, officia, deserunt, mollitia, animi, id, est, laborum, et, dolorum, fuga, et, harum, quidem, rerum, facilis, est, et, expedita, distinctio, nam, libero, tempore, cum, soluta, nobis, est, eligendi, optio, cumque, nihil, impedit, quo, porro, quisquam, est, qui, minus, id, quod, maxime, placeat, facere, possimus, omnis, voluptas, assumenda, est, omnis, dolor, repellendus, temporibus, autem, quibusdam, et, aut, consequatur, vel, illum, qui, dolorem, eum, fugiat, quo, voluptas, nulla, pariatur, accusamus, officiis, debitis, aut, rerum, necessitatibus, saepe, eveniet, ut, et, voluptates, repudiandae, sint, molestiae, non, recusandae, itaque, earum, rerum, tenetur, sapiente, delectus, reiciendis, voluptatibus, maiores, doloribus, asperiores, repellat] 78 | -------------------------------------------------------------------------------- /test/integration/admin/post_category_integration_test.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require_relative '../../test_helper' 5 | 6 | class Admin::PostCategoryIntegrationTest < ActiveSupport::IntegrationCase 7 | 8 | #include Blog::PostsHelper 9 | 10 | def setup 11 | Post.destroy_all 12 | PostCategory.destroy_all 13 | @post = Factory.create(:post) 14 | @category = Factory.create(:post_category) 15 | end 16 | 17 | should "have a link to post categories" do 18 | visit admin_post_path(@post) 19 | within ".sidebar.post-menu" do 20 | assert has_link?("Categories") 21 | end 22 | end 23 | 24 | should "get the post categories index" do 25 | visit admin_post_categories_path(@post) 26 | assert_seen "Categories", :within => ".sidebar.post-menu li.active" 27 | assert_seen "Manage Categories", :within => ".edit_post legend" 28 | assert_seen @category.name, :within => "tr#post_category_#{@category.id} td label" 29 | assert has_selector?("button[type=submit]") 30 | assert has_selector?("a#btn_new_category") 31 | end 32 | 33 | should "add a new post category" do 34 | visit admin_post_categories_path(@post) 35 | click_link "btn_new_category" 36 | fill_in "Name", :with => "Just a Category" 37 | fill_in "Permalink", :with => "just-a-category" 38 | click_button "Create" 39 | @category = PostCategory.last 40 | assert_equal admin_post_categories_path(@post), current_path 41 | assert_flash :notice, %(Post category "#{@category.name}" has been successfully created!) 42 | assert_seen @category.name, :within => "tr#post_category_#{@category.id} td label" 43 | end 44 | 45 | should "edit existing post category" do 46 | visit edit_admin_post_category_path(@post, @category.id) 47 | assert_equal @category.name, find_field("Name").value 48 | assert_equal @category.permalink, find_field("Permalink").value 49 | fill_in "Name", :with => "Not just a Category" 50 | fill_in "Permalink", :with => "not-just-a-category" 51 | click_button "Update" 52 | assert_equal admin_post_categories_path(@post), current_path 53 | assert_flash :notice, %(Post category "Not just a Category" has been successfully updated!) 54 | assert_seen "Not just a Category", :within => "tr#post_category_#{@category.id} td label" 55 | end 56 | 57 | should "destroy the post category" do 58 | visit admin_post_categories_path(@post) 59 | find("tr#post_category_#{@category.id} td.options a[href='#']").click 60 | assert find_by_id("popup_ok").click 61 | end 62 | 63 | should "link a post category" do 64 | visit admin_post_categories_path(@post) 65 | assert !field_labeled(@category.name).checked? 66 | check @category.name 67 | click_button "Update" 68 | assert field_labeled(@category.name).checked? 69 | assert_equal admin_post_categories_path(@post), current_path 70 | assert_flash :notice, %(Post "#{@post.title}" has been successfully updated!) 71 | end 72 | 73 | should "unlink a post category" do 74 | @post.categories << @category 75 | @post.save 76 | visit admin_post_categories_path(@post.reload) 77 | assert field_labeled(@category.name).checked? 78 | uncheck @category.name 79 | click_button "Update" 80 | assert_equal admin_post_categories_path(@post), current_path 81 | assert !field_labeled(@category.name).checked? 82 | assert_flash :notice, %(Post "#{@post.title}" has been successfully updated!) 83 | end 84 | 85 | context "with multiple categories" do 86 | 87 | setup do 88 | PostCategory.destroy_all 89 | @categories = %w(one two three four five).map{|i| Factory.create(:post_category, :name => i) } 90 | end 91 | 92 | should "link a multiple post categories" do 93 | visit admin_post_categories_path(@post) 94 | @categories.each do |category| 95 | assert !field_labeled(category.name).checked? 96 | end 97 | @categories.take(3).each do |category| 98 | check category.name 99 | end 100 | click_button "Update" 101 | @categories.take(3).each do |category| 102 | assert field_labeled(category.name).checked? 103 | end 104 | @categories.slice(3, 2).each do |category| 105 | assert !field_labeled(category.name).checked? 106 | end 107 | assert_equal admin_post_categories_path(@post), current_path 108 | assert_flash :notice, %(Post "#{@post.title}" has been successfully updated!) 109 | end 110 | 111 | should "unlink multiple post category" do 112 | @post.categories = @categories.take(3) 113 | @post.save 114 | visit admin_post_categories_path(@post.reload) 115 | @categories.take(3).each do |category| 116 | assert field_labeled(category.name).checked? 117 | end 118 | @categories.take(3).each do |category| 119 | uncheck category.name 120 | end 121 | click_button "Update" 122 | @categories.each do |category| 123 | assert !field_labeled(category.name).checked? 124 | end 125 | assert_equal admin_post_categories_path(@post), current_path 126 | assert_flash :notice, %(Post "#{@post.title}" has been successfully updated!) 127 | end 128 | 129 | end 130 | 131 | end 132 | -------------------------------------------------------------------------------- /test/integration/post_integration_test.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require_relative '../test_helper' 5 | 6 | class Blog::PostIntegrationTest < ActiveSupport::IntegrationCase 7 | 8 | 9 | include Blog::PostsHelper 10 | 11 | def setup 12 | Post.destroy_all 13 | end 14 | 15 | 16 | context "with some posts" do 17 | 18 | setup do 19 | 11.times{ |i| Factory.create(:post, :title => "Capy post #{i}", :posted_at => Time.now - i.days) } 20 | assert_equal 11, Post.count 21 | end 22 | 23 | should "get the blog page" do 24 | visit posts_path 25 | # first post 26 | assert has_link?("Capy post 1") 27 | # last post 28 | assert has_link?("Capy post 9") 29 | # archive link 30 | assert has_link?("View Full Archive") 31 | # tag link 32 | assert has_link?("peanut butter") 33 | # page two 34 | assert has_link?("2") 35 | assert has_link?("Next →") 36 | 37 | 38 | #assert_equal nil, page.html.match(%( DateTime.parse("2011/2/17"), :tag_list => "gruyere, emmentaler, fondue") 63 | assert_equal 1, Post.count 64 | end 65 | 66 | should "find by seo path" do 67 | visit post_seo_path(@post) 68 | assert_seen "Peanut Butter Jelly Time", :within => ".post-title h1" 69 | assert_seen "Thursday February 17, 2011", :within => ".post-title h5" 70 | within ".post-tags" do 71 | assert has_link?("gruyere") 72 | assert has_link?("emmentaler") 73 | assert has_link?("fondue") 74 | end 75 | end 76 | 77 | should "not find by tags" do 78 | visit search_posts_path(:query => "some crazy random query") 79 | assert_seen "No posts found!", :within => ".posts h1" 80 | end 81 | 82 | should "find by tags" do 83 | visit search_posts_path(:query => "emmentaler") 84 | assert has_link?("Peanut Butter Jelly Time", :href => post_seo_path(@post)) 85 | assert has_link?("Read More", :href => post_seo_path(@post)) 86 | end 87 | 88 | end 89 | 90 | 91 | context "unpublished posts" do 92 | 93 | def assert_no_post(post) 94 | within "#sidebar .post-archive" do 95 | assert !has_link?(post.title) 96 | end 97 | within "#content .posts" do 98 | assert !has_link?(post.title) 99 | end 100 | within ".tag-cloud ul.tags" do 101 | post.tags.each do |tag| 102 | assert !has_link?(tag.name) 103 | end 104 | end 105 | end 106 | 107 | setup do 108 | @tags = %(totally, not published).split(", ") 109 | @post = Factory.create(:post, :title => "Unpublished Post", :tag_list => @tags.join(", "), :live => false) 110 | assert_equal 1, Post.count 111 | end 112 | 113 | should "not include post in index" do 114 | visit posts_path 115 | assert_no_post(@post) 116 | end 117 | 118 | should "not include post in day specific index" do 119 | visit post_date_path(:year => @post.year, :month => @post.month, :day => @post.day) 120 | assert_no_post(@post) 121 | end 122 | 123 | should "not include post in month specific index" do 124 | visit post_date_path(:year => @post.year, :month => @post.month) 125 | assert_no_post(@post) 126 | end 127 | 128 | should "not include post in year specific index" do 129 | visit post_date_path(:year => @post.year) 130 | assert_no_post(@post) 131 | end 132 | 133 | should "not include post in search results" do 134 | @tags.each do |tag| 135 | visit search_posts_path(tag) 136 | assert_no_post(@post) 137 | end 138 | end 139 | 140 | end 141 | 142 | 143 | context "published posts" do 144 | 145 | def assert_has_post(post) 146 | within "#sidebar .post-archive" do 147 | assert has_link?(post.title) 148 | end 149 | within "#content .posts" do 150 | assert has_link?(post.title) 151 | end 152 | within ".tag-cloud ul.tags" do 153 | post.tags.each do |tag| 154 | assert has_link?(tag.name) 155 | end 156 | end 157 | end 158 | 159 | setup do 160 | @tags = %(totally, published).split(", ") 161 | @post = Factory.create(:post, :title => "Published Post", :tag_list => @tags.join(", "), :live => true) 162 | assert_equal 1, Post.count 163 | end 164 | 165 | should "not include post in index" do 166 | visit posts_path 167 | assert_has_post(@post) 168 | end 169 | 170 | should "not include post in day specific index" do 171 | visit post_date_path(:year => @post.year, :month => @post.month, :day => @post.day) 172 | assert_has_post(@post) 173 | end 174 | 175 | should "not include post in month specific index" do 176 | visit post_date_path(:year => @post.year, :month => @post.month) 177 | assert_has_post(@post) 178 | end 179 | 180 | should "not include post in year specific index" do 181 | visit post_date_path(:year => @post.year) 182 | assert_has_post(@post) 183 | end 184 | 185 | should "not include post in search results" do 186 | @tags.each do |tag| 187 | visit search_posts_path(tag) 188 | assert_has_post(@post) 189 | end 190 | end 191 | 192 | end 193 | 194 | end 195 | 196 | 197 | 198 | 199 | # [todo] make these capy tests 200 | # 201 | #context "published, dated posts" do 202 | # 203 | # setup do 204 | # @date = DateTime.parse("2011/3/20 16:00") 205 | # @post = Factory.create(:post, :posted_at => @date) 206 | # 10.times {|i| Factory.create(:post, :title => "Today's Sample Post #{i}", :posted_at => @date) } 207 | # 10.times {|i| Factory.create(:post, :title => "Last Weeks's Sample Post #{i}", :posted_at => @date - 1.week) } 208 | # 10.times {|i| Factory.create(:post, :title => "Last Month's Sample Post #{i}", :posted_at => @date - 1.month) } 209 | # 10.times {|i| Factory.create(:post, :title => "Last Years's Sample Post #{i}", :posted_at => @date - 1.year) } 210 | # end 211 | # 212 | # should "assert proper post count" do 213 | # assert_equal 41, Post.count 214 | # end 215 | # 216 | # should "paginate posts by day" do 217 | # get :index, :year => @post.year, :month => @post.month, :day => @post.day 218 | # assert_equal 10, assigns(:posts).length 219 | # assert_equal 11, assigns(:posts).total_entries 220 | # assert_equal 2, assigns(:posts).total_pages 221 | # assert_response :success 222 | # end 223 | # 224 | # should "get posts by month" do 225 | # get :index, :year => @post.year, :month => @post.month 226 | # assert_equal 10, assigns(:posts).length 227 | # assert_equal 21, assigns(:posts).total_entries 228 | # assert_equal 3, assigns(:posts).total_pages 229 | # assert_response :success 230 | # end 231 | # 232 | # should "get posts by year" do 233 | # get :index, :year => @post.year 234 | # assert_not_nil assigns(:posts) 235 | # assert_equal 10, assigns(:posts).length 236 | # assert_equal 31, assigns(:posts).total_entries 237 | # assert_equal 4, assigns(:posts).total_pages 238 | # assert_response :success 239 | # end 240 | # 241 | #end 242 | --------------------------------------------------------------------------------