thesis 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Thesis
1
+ # Thesis ![Build Status](https://travis-ci.org/clearsightstudio/thesis.png)
2
2
 
3
- ### Thesis is a CMS gem that integrates as seamlessly as possible into your current Rails website.
3
+ ### Thesis is a Rails CMS gem that integrates as seamlessly as possible into your current Rails website.
4
4
 
5
5
  Most Rails content management systems make you conform to their system from the start,
6
6
  making it difficult to just "drop in" the gem and make it work with your CMS. Thesis
@@ -10,9 +10,12 @@ of the way.
10
10
  ### Requirements
11
11
 
12
12
  * Rails 3.2.x (or higher)
13
+ * ActiveRecord on MySQL, PostgresQL, or SQLite3
13
14
  * Ruby 1.9.3 (or higher)
15
+ * jQuery
16
+ * jQuery UI
14
17
 
15
- Thesis might work with earlier versions but that's not our focus.
18
+ Thesis *might* work with other versions but no guarantees.
16
19
 
17
20
  ## Getting Started
18
21
 
@@ -116,13 +119,11 @@ Referencing a content area in a page template will create one if it doesn't exis
116
119
 
117
120
  ### Routing
118
121
 
119
- Thesis will also add a route handler to your `routes.rb` file. This will
120
- automatically handle routes for pages you create with Thesis.
121
-
122
- thesis_routes
122
+ Thesis will automatically handle routes for pages you create with Thesis. Your
123
+ routes will take precedence over Thesis-created pages, so if you create a page
124
+ with Thesis called "About" and you already have a route for
125
+ `get "about" => "something#else"` Thesis won't show the page.
123
126
 
124
- **To improve performance, put it near the bottom of your `routes.rb` file.**
125
-
126
127
  ## Using the CMS
127
128
 
128
129
  ### Adding a Page
@@ -143,11 +144,12 @@ TODO
143
144
 
144
145
  ## What Thesis Isn't
145
146
 
146
- You can't have it all or it becomes the same as the other bloated content management systems
147
- out there. This is a list of what it's not and what it's not ever likely to be.
147
+ You can't have it all. Thesis isn't the same as other -bloated- full-functioned
148
+ content management systems out there. This is a list of what it's not now and
149
+ not likely to be in the future.
148
150
 
149
- We reserve the right to change our mind, however, especially with well planned and written
150
- pull requests to help prod us in the right direction. :-)
151
+ *We reserve the right to change our mind, however, especially with well planned and written
152
+ pull requests to help prod us in the right direction. :-)*
151
153
 
152
154
  1. A WordPress Replacement
153
155
  2. A full featured CMS
@@ -162,6 +164,13 @@ pull requests to help prod us in the right direction. :-)
162
164
  2. Create your feature branch (`git checkout -b my-new-feature`)
163
165
  3. Commit your changes (`git commit -am 'Add some feature'`)
164
166
  4. Write tests for your new feature
165
- 5. Run `bundle exec rspec` in the root directory to ensure that all tests pass.
167
+ 5. Run `rake spec` in the root directory to ensure that all tests pass.
166
168
  6. Push to the branch (`git push origin my-new-feature`)
167
169
  7. Create new Pull Request
170
+
171
+ ### Key Contributors
172
+
173
+ * Jamon Holmgren [@jamonholmgren](https://twitter.com/jamonholmgren)
174
+ * Daniel Berkompas [@dberkom](https://twitter.com/dberkom)
175
+ * The ClearSight Studio team
176
+
@@ -19,18 +19,14 @@ module Thesis
19
19
  end
20
20
 
21
21
  def copy_migrations
22
- migration_template "migrations/create_page.rb", "db/migrate/create_page.rb"
23
- migration_template "migrations/create_page_content.rb", "db/migrate/create_page_content.rb"
22
+ migration_template "migrations/thesis_create_page.rb", "db/migrate/thesis_create_page.rb"
23
+ migration_template "migrations/thesis_create_page_content.rb", "db/migrate/thesis_create_page_content.rb", { skip: true }
24
24
  end
25
25
 
26
26
  def create_folders
27
- copy_file "page_templates/default.html.slim", "app/views/page_templates/default.html.slim" if Slim
28
- copy_file "page_templates/default.html.haml", "app/views/page_templates/default.html.haml" if Haml
29
- copy_file "page_templates/default.html.erb", "app/views/page_templates/default.html.erb" unless Haml || Slim
30
- end
31
-
32
- def set_up_routes
33
- route "thesis_routes # This needs to be the last route!"
27
+ copy_file "page_templates/default.html.slim", "app/views/page_templates/default.html.slim" if defined? Slim
28
+ copy_file "page_templates/default.html.haml", "app/views/page_templates/default.html.haml" if defined? Haml
29
+ copy_file "page_templates/default.html.erb", "app/views/page_templates/default.html.erb" unless defined? Haml || defined? Slim
34
30
  end
35
31
 
36
32
  def install_js
@@ -48,15 +44,20 @@ module Thesis
48
44
  end
49
45
 
50
46
  def install_css
51
- filename = "app/assets/stylesheets/application.css.scss"
52
- existing = File.binread("#{filename}").include?("require thesis")
53
-
54
- if existing && generating?
55
- say_status("skipped", "insert into #{filename}", :yellow)
56
- else
57
- insert_into_file "#{filename}", after: %r{ *= require_self} do
58
- "\n *= require thesis"
47
+ filename = "app/assets/stylesheets/application.css"
48
+ filename = filename << ".scss" unless File.exists? filename
49
+ if File.exists? filename
50
+ existing = File.binread("#{filename}").include?("require thesis")
51
+
52
+ if existing && generating?
53
+ say_status("skipped", "insert into #{filename}", :yellow)
54
+ else
55
+ insert_into_file "#{filename}", after: %r{ *= require_self} do
56
+ "\n *= require thesis"
57
+ end
59
58
  end
59
+ else
60
+ say_status("skipped", "Couldn't insert into #{filename} -- doesn't exist")
60
61
  end
61
62
  end
62
63
 
@@ -1,5 +1,8 @@
1
- class CreatePage < ActiveRecord::Migration
1
+ class ThesisCreatePage < ActiveRecord::Migration
2
2
  def self.up
3
+ # Please note: if you change this (in the thesis gem) please
4
+ # update the /spec/spec_helper.rb to match.
5
+
3
6
  create_table :pages do |t|
4
7
  t.integer :parent_id
5
8
  t.string :name
@@ -1,5 +1,8 @@
1
- class CreatePageContent < ActiveRecord::Migration
1
+ class ThesisCreatePageContent < ActiveRecord::Migration
2
2
  def self.up
3
+ # Please note: if you change this (in the thesis gem) please
4
+ # update the /spec/spec_helper.rb to match.
5
+
3
6
  create_table :page_contents do |t|
4
7
  t.integer :page_id, null: false
5
8
  t.string :name, null: false
@@ -1,18 +1,5 @@
1
1
  module Thesis
2
2
  module ControllerHelpers
3
- module ClassMethods
4
- def class_method_here
5
- # Sample
6
- end
7
- end
8
-
9
- def self.included(base)
10
- raise ActiveRecordRequired.new("Currently, Thesis only works with ActiveRecord.") unless defined? ActiveRecord
11
-
12
- # base.extend ClassMethods
13
- # base.helper_method :class_method_here
14
- end
15
-
16
3
  def current_page
17
4
  @current_page ||= Page.where(slug: request.fullpath).first
18
5
  end
@@ -20,20 +7,9 @@ module Thesis
20
7
  def root_pages
21
8
  @root_pages ||= Page.where(parent_id: nil).order("sort_order ASC").all
22
9
  end
23
-
24
- def page_is_editable?(page)
25
- raise RequiredMethodNotImplemented.new("Add a `page_is_editable?(page)` method to your controller that returns true or false.")
26
- end
27
-
10
+
28
11
  def thesis_editor
29
12
  "<div id='thesis-editor'></div>".html_safe if page_is_editable?(current_page)
30
13
  end
31
14
  end
32
15
  end
33
-
34
- if defined? ActionController::Base
35
- ActionController::Base.class_eval do
36
- include Thesis::ControllerHelpers
37
- helper_method :current_page, :root_pages, :page_is_editable?, :thesis_editor
38
- end
39
- end
@@ -1,24 +1,39 @@
1
1
  module Thesis
2
- class ThesisController < ActionController::Base
3
- include ControllerHelpers
4
-
2
+ class ThesisController < ::ApplicationController
3
+ include Thesis::ControllerHelpers
4
+
5
5
  def show
6
6
  raise ActionController::RoutingError.new('Not Found') unless current_page
7
7
 
8
8
  if current_page.template && template_exists?("page_templates/#{current_page.template}")
9
- render "page_templates/#{current_page.template}"
9
+ render "page_templates/#{current_page.template}", layout: false
10
10
  else
11
11
  raise PageRequiresTemplate.new("Page requires a template but none was specified.")
12
12
  end
13
13
  end
14
14
 
15
- def new_page
15
+ def create_page
16
16
  page = Page.new
17
17
  return head :forbidden unless page_is_editable?(page)
18
18
 
19
19
  update_page_attributes page
20
20
 
21
- head page.save ? :ok : :not_acceptable
21
+ resp = {}
22
+
23
+ if page.save
24
+ resp[:page] = page
25
+ else
26
+ resp[:message] = page.errors.messages.first
27
+ end
28
+
29
+ render json: resp, status: page.valid? ? :ok : :not_acceptable
30
+ end
31
+
32
+ def delete_page
33
+ page = Page.where(slug: params[:slug].to_s).first
34
+ return head :forbidden unless page && page_is_editable?(page)
35
+
36
+ head page.destroy ? :ok : :not_acceptable
22
37
  end
23
38
 
24
39
  def update_page
@@ -38,16 +53,37 @@ module Thesis
38
53
  page_attributes.each { |a| page.send("#{a}=", params[a]) if params[a] }
39
54
  page
40
55
  end
41
-
42
- protected
43
-
44
- def page_is_editable?(page)
45
- appcon = ApplicationController.new
46
- if appcon.respond_to?("page_is_editable?")
47
- appcon.page_is_editable?(page)
56
+
57
+ def update_page_content
58
+ errors = false
59
+ error_message = "Unknown error."
60
+
61
+ page_contents = PageContent.where(id: params.keys).includes(:page).all
62
+ if page_contents.length.zero?
63
+ error_message = "That page doesn't exist anymore."
64
+ errors = true
48
65
  else
49
- raise RequiredMethodNotImplemented.new("Add a `page_is_editable?(page)` method to your controller that returns true or false.")
66
+ page_contents.each do |pc|
67
+ if page_is_editable? pc.page
68
+ pc.content = params[pc.id.to_s]
69
+ pc.save
70
+ else
71
+ errors = true
72
+ error_message = "You don't have permission to update this page."
73
+ end
74
+ end
50
75
  end
76
+
77
+ resp = {}
78
+ resp[:message] = error_message if errors
79
+
80
+ render json: resp, status: errors ? :not_acceptable : :ok
81
+ end
82
+
83
+ # The ApplicationController should implement this.
84
+ def page_is_editable?(page)
85
+ raise RequiredMethodNotImplemented.new("Add a `page_is_editable?(page)` method to your controller that returns true or false.") unless defined?(super)
86
+ super
51
87
  end
52
88
  end
53
89
  end
data/lib/thesis/engine.rb CHANGED
@@ -1,5 +1,14 @@
1
1
  module Thesis
2
2
  class Engine < ::Rails::Engine
3
- isolate_namespace Thesis
3
+ # isolate_namespace Thesis # We're accessing the application controller, so we can't do this.
4
+ initializer 'thesis.action_controller' do |app|
5
+ require "thesis/controllers/controller_helpers"
6
+ require "thesis/controllers/thesis_controller"
7
+
8
+ ActiveSupport.on_load(:action_controller) do
9
+ include ::Thesis::ControllerHelpers
10
+ helper_method :current_page, :root_pages, :page_is_editable?, :thesis_editor
11
+ end
12
+ end
4
13
  end
5
14
  end
@@ -4,12 +4,16 @@ module Thesis
4
4
 
5
5
  belongs_to :parent, class_name: "Page"
6
6
  has_many :subpages, class_name: "Page", foreign_key: "parent_id", order: "sort_order ASC"
7
- has_many :page_contents
7
+ has_many :page_contents, dependent: :destroy
8
8
 
9
9
  before_validation :update_slug
10
10
  after_save :update_subpage_slugs
11
11
 
12
- validates :slug, uniqueness: { message: "There's already a page like that. Change your page name." }, presence: true
12
+ validates :slug,
13
+ uniqueness: { message: "There's already a page like that. Change your page name." },
14
+ presence: true,
15
+ length: { minimum: 1 },
16
+ allow_blank: false
13
17
 
14
18
  def update_slug
15
19
  self.slug = "/" << self.name.parameterize
@@ -20,8 +24,8 @@ module Thesis
20
24
  subpages.each(&:save) if slug_changed?
21
25
  end
22
26
 
23
- def content(name, content_type = :html)
24
- pc = find_or_create_page_content(name, content_type)
27
+ def content(name, content_type = :html, opts = {})
28
+ pc = find_or_create_page_content(name, content_type, opts)
25
29
  pc.render
26
30
  end
27
31
 
@@ -31,9 +35,13 @@ module Thesis
31
35
 
32
36
  protected
33
37
 
34
- def find_or_create_page_content(name, content_type)
38
+ def find_or_create_page_content(name, content_type, opts = {})
35
39
  page_content = self.page_contents.where(name: name).first_or_create do |pc|
36
- pc.content = "Edit This Area"
40
+ pc.content = "<p>Edit This HTML Area</p>" if content_type == :html
41
+ pc.content = "Edit This Text Area" if content_type == :text
42
+ width = opts[:width] || 350
43
+ height = opts[:height] || 150
44
+ pc.content = "http://placehold.it/#{width}x#{height}" if content_type == :image
37
45
  end
38
46
  page_content.content_type = content_type
39
47
  page_content.save if page_content.changed?
@@ -9,7 +9,9 @@ module Thesis
9
9
  when :html
10
10
  render_html
11
11
  when :text
12
- render_text
12
+ render_plain_text
13
+ when :image
14
+ render_image
13
15
  else
14
16
  render_html
15
17
  end
@@ -19,22 +21,22 @@ module Thesis
19
21
 
20
22
  def render_html
21
23
  h = "<div class='thesis-content thesis-content-html' data-thesis-content-id='#{self.id}'>" +
22
- "#{self.content}" +
23
- "</div>"
24
+ "#{self.content}" +
25
+ "</div>"
24
26
  h.html_safe
25
27
  end
26
28
 
27
- def render_text
29
+ def render_plain_text
28
30
  h = "<span class='thesis-content thesis-content-text' data-thesis-content-id='#{self.id}'>" +
29
- "#{self.content}" +
30
- "</span>"
31
+ "#{self.content}" +
32
+ "</span>"
31
33
  h.html_safe
32
34
  end
33
35
 
34
36
  def render_image
35
37
  h = "<div class='thesis-content thesis-content-image'>" +
36
- "<img src='#{self.content}' />" +
37
- "</div>"
38
+ "<img src='#{self.content}' />" +
39
+ "</div>"
38
40
  h.html_safe
39
41
  end
40
42
  end
@@ -1,3 +1,3 @@
1
1
  module Thesis
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/thesis.rb CHANGED
@@ -1,10 +1,8 @@
1
- require "thesis/engine"
2
1
  require "thesis/version"
3
2
  require "thesis/exceptions"
4
- require "thesis/controllers/controller_helpers" # Included into ActionController::Base
5
- require "thesis/controllers/thesis_controller"
3
+ require "thesis/controllers/controller_helpers"
6
4
  require "thesis/routing/route_constraint"
7
- require "thesis/routing/routes"
5
+ require "thesis/engine"
8
6
 
9
7
  # Models
10
8
  require "thesis/models/page"
@@ -1,68 +1,96 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Thesis::ThesisController do
4
- let(:page) { create :page, template: "default" }
5
-
6
- before do
7
- described_class.any_instance.stub(:render).and_return(page.title)
8
- described_class.any_instance.stub(:template_exists?).and_return(true)
9
- described_class.any_instance.stub(:page_is_editable?).and_return(true)
10
- end
4
+ let(:page) { create :page, template: "default" }
5
+ let(:page_content) { create :page_content, name: "Main", page: page }
6
+
7
+ before do
8
+ described_class.any_instance.stub(:render).and_return(page.title)
9
+ described_class.any_instance.stub(:template_exists?).and_return(true)
10
+ described_class.any_instance.stub(:page_is_editable?).and_return(true)
11
+ end
11
12
 
12
- describe "#show" do
13
- it "displays a page when it can be found" do
14
- make_request :show, path: page.slug
15
- @response.should_not be_nil
16
- end
13
+ describe "#show" do
14
+ it "displays a page when it can be found" do
15
+ make_request :show, path: page.slug
16
+ @response.should_not be_nil
17
+ end
17
18
 
18
- it "raises RoutingError when the page slug can't be found" do
19
- expect { make_request :show, path: "/nonexistent" }.to raise_error ActionController::RoutingError
20
- end
19
+ it "raises RoutingError when the page slug can't be found" do
20
+ expect { make_request :show, path: "/nonexistent" }.to raise_error ActionController::RoutingError
21
+ end
21
22
 
22
- it "raises PageRequiresTemplate when template can't be found" do
23
- described_class.any_instance.stub(:template_exists?).and_return(false)
24
- expect { make_request :show, path: page.slug }.to raise_error Thesis::PageRequiresTemplate
25
- end
26
- end
23
+ it "raises PageRequiresTemplate when template can't be found" do
24
+ described_class.any_instance.stub(:template_exists?).and_return(false)
25
+ expect { make_request :show, path: page.slug }.to raise_error Thesis::PageRequiresTemplate
26
+ end
27
+ end
27
28
 
28
- describe "#new_page" do
29
- context "when the page can be edited" do
30
- it "creates a page" do
31
- make_request :new_page, name: "New Page"
32
- page = Thesis::Page.last
33
- expect(page.name).to eq "New Page"
34
- end
35
- end
29
+ describe "#create_page" do
30
+ context "when the page can be edited" do
31
+ it "creates a page" do
32
+ make_request :create_page, name: "New Page"
33
+ page = Thesis::Page.last
34
+ expect(page.name).to eq "New Page"
35
+ end
36
+ end
36
37
 
37
- context "when the page can't be edited" do
38
- before { described_class.any_instance.stub(:page_is_editable?).and_return(false) }
38
+ context "when the page can't be edited" do
39
+ before { described_class.any_instance.stub(:page_is_editable?).and_return(false) }
39
40
 
40
- it "returns a status of 403 'Forbidden'" do
41
- make_request :new_page, name: "New Page"
42
- expect(@response.status).to eq 403
43
- end
44
- end
45
- end
41
+ it "returns a status of 403 'Forbidden'" do
42
+ make_request :create_page, name: "New Page"
43
+ expect(@response.status).to eq 403
44
+ end
45
+ end
46
+ end
46
47
 
47
- describe "#update_page" do
48
- context "when the page can be edited" do
49
- it "updates the attributes of the page" do
50
- make_request :update_page, path: page.slug, name: "New Name"
51
- page.reload
52
- expect(page.name).to eq "New Name"
53
- expect(page.slug).to eq "/new-name"
54
- end
55
- end
56
- end
48
+ describe "#update_page" do
49
+ context "when the page can be edited" do
50
+ it "updates the attributes of the page" do
51
+ make_request :update_page, path: page.slug, name: "New Name"
52
+ page.reload
53
+ expect(page.name).to eq "New Name"
54
+ expect(page.slug).to eq "/new-name"
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "#update_page_content" do
60
+ context "when the page can be edited" do
61
+ it "updates the content on the page" do
62
+ page_content.content = "I'm the content"
63
+ page_content.save
64
+
65
+ make_request :update_page_content, { method: :put, page_content.id => "New content" }
66
+
67
+ expect(page_content.reload.content).to eq "New content"
68
+ end
69
+ end
70
+
71
+ context "when the page can't be edited" do
72
+ before { described_class.any_instance.stub(:page_is_editable?).and_return(false) }
73
+
74
+ it "doesn't update the page_content" do
75
+ page_content.content = "I'm the content"
76
+ page_content.save
77
+
78
+ make_request :update_page_content, { method: :post, page_content.id => "New content" }
79
+
80
+ expect(page_content.reload.content).to eq "I'm the content"
81
+ end
82
+ end
83
+ end
57
84
  end
58
85
 
59
86
  # Mock a request to the controller without
60
87
  # Using Rails routing. Makes the response available
61
88
  # in the @response instance variable.
62
89
  def make_request(action, params = {})
63
- path = params[:path] || "/"
64
- env = Rack::MockRequest.env_for(path, :params => params.except(:path), method: :post)
65
- status, headers, body = described_class.action(action).call(env)
66
- @response = ActionDispatch::TestResponse.new(status, headers, body)
67
- @controller = body.request.env['action_controller.instance']
90
+ path = params[:path] || "/"
91
+ method = params[:method] || "post"
92
+ env = Rack::MockRequest.env_for(path, :params => params.except(:path).except(:method), method: method)
93
+ status, headers, body = described_class.action(action).call(env)
94
+ @response = ActionDispatch::TestResponse.new(status, headers, body)
95
+ @controller = body.request.env['action_controller.instance']
68
96
  end
@@ -1,7 +1,7 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Thesis::PageContent do
4
- let(:page) { create(:page) }
4
+ let(:page) { create(:page) }
5
5
  let(:page_content) { create(:page_content, attributes.merge(page_id: page.id)) }
6
6
 
7
7
  describe "#render" do
@@ -14,20 +14,19 @@ describe Thesis::PageContent do
14
14
  it { should match page_content.content }
15
15
  end
16
16
 
17
- context "when the content type is 'text'" do
18
- let(:attributes) {{ content_type: "text" }}
17
+ context "when the content type is 'text'" do
18
+ let(:attributes) {{ content_type: "text" }}
19
19
 
20
- it { should match /thesis-content-text/ }
21
- it { should match page_content.content }
22
- end
20
+ it { should match /thesis-content-text/ }
21
+ it { should match page_content.content }
22
+ end
23
23
 
24
- context "when the content type is 'image'" do
25
- let(:attributes) {{ content_type: "image" }}
26
-
27
- pending "currently renders as 'html'. Consider using #render_image in #render."
28
- # it { should match /thesis-content-image/ }
29
- # it { should match /img/ }
30
- # it { should match page_content.content }
31
- end
32
- end
24
+ context "when the content type is 'image'" do
25
+ let(:attributes) {{ content_type: "image" }}
26
+
27
+ it { should match /thesis-content-image/ }
28
+ it { should match /img/ }
29
+ it { should match page_content.content }
30
+ end
31
+ end
33
32
  end
data/spec/spec_helper.rb CHANGED
@@ -1,12 +1,24 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
- require 'rails/all'
4
3
  require 'factory_girl'
5
4
  require 'rspec/autorun'
6
5
  require 'database_cleaner'
6
+ require 'rails/all'
7
+
8
+ # Add a fake ApplicationController for testing.
9
+ class ApplicationController < ActionController::Base
10
+ def page_is_editable?(page)
11
+ true
12
+ end
13
+ end
14
+
15
+ # Require thesis
16
+ require "thesis"
7
17
 
8
- # Require all the Thesis files
9
- Dir[File.join('.', '/lib/thesis/**/*.rb')].each {|file| require file }
18
+ # Manually run the Rails initializer. Thanks to @JoshReedSchramm for the code.
19
+ initializer = Thesis::Engine.initializers.select { |i| i.name == "thesis.action_controller" }.first
20
+ initializer.run
21
+ # Dir[File.join('.', '/lib/thesis/**/*.rb')].each {|file| require file }
10
22
 
11
23
  # Load Factories
12
24
  FactoryGirl.find_definitions