landable 1.7.0 → 1.7.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +12 -0
  4. data/CHANGELOG.md +4 -1
  5. data/Gemfile +4 -1
  6. data/README.md +4 -1
  7. data/app/controllers/landable/api/template_revisions_controller.rb +27 -0
  8. data/app/controllers/landable/api/templates_controller.rb +7 -1
  9. data/app/models/landable/template.rb +25 -0
  10. data/app/models/landable/template_revision.rb +40 -0
  11. data/app/serializers/landable/template_revision_serializer.rb +12 -0
  12. data/app/serializers/landable/template_serializer.rb +6 -1
  13. data/config/routes.rb +7 -1
  14. data/db/migrate/20140509190128_create_template_revisions.rb +77 -0
  15. data/doc/schema/template.json +62 -0
  16. data/doc/schema/template_revision.json +48 -0
  17. data/features/api/access_tokens.feature +2 -2
  18. data/features/api/templates.feature +7 -1
  19. data/features/liquid/tags.feature +22 -1
  20. data/features/step_definitions/factory_steps.rb +7 -2
  21. data/features/step_definitions/liquid_steps.rb +15 -4
  22. data/features/step_definitions/revision_steps.rb +6 -0
  23. data/landable-1.7.0.gem +0 -0
  24. data/landable.gemspec +1 -2
  25. data/lib/landable/liquid/tags.rb +17 -10
  26. data/lib/landable/partial.rb +5 -0
  27. data/lib/landable/version.rb +2 -2
  28. data/lib/tasks/landable/template.rake +10 -0
  29. data/{bin → script}/rails +0 -0
  30. data/{bin → script}/redb +0 -0
  31. data/spec/controllers/landable/api/template_revisions_controller_spec.rb +29 -0
  32. data/spec/dummy/db/structure.sql +103 -2
  33. data/spec/factories/template_revision.rb +6 -0
  34. data/spec/lib/landable/partial_spec.rb +5 -0
  35. data/spec/models/landable/template_revision_spec.rb +35 -0
  36. data/spec/models/landable/template_spec.rb +67 -0
  37. metadata +65 -65
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2bab73bf093b17b370337984023eac8234b4d072
4
- data.tar.gz: e4587f1dcf343bff05b4bc6280913d5d04e9dfa0
3
+ metadata.gz: 68d0d159a866cc88742e6b5603bcd0d5edb09b65
4
+ data.tar.gz: 2f1aa05942baaedab945f6163cebd4e65c5e4599
5
5
  SHA512:
6
- metadata.gz: 8569f8fd1867cffe6883e116200aa4060723abd163ff3e29711738c089ba88267e97d5f5ba0ce5305299fc922b5cc0a72db3a9b06ffa46b0c972f352db8ca921
7
- data.tar.gz: 4aad2b150a7b185ef673b2ea0f37daa115e57ab944b97cc307fbe92b650fc9c796f40722ba545146e2063c0eb150b563d3058af0d2448903954ad8ab0fb50633
6
+ metadata.gz: d9faf6bc62457e8a574dc47ca2eb6a2b95593bb2bcc72db13bc1d3f54ba327bb241fff269d658a27ac8d72e8412b4e36804b01fc1485d5ed1b3afce450d8e09b
7
+ data.tar.gz: ba14a46c7b46ae299eeb77d6b65568747ec989c3553816a0e947d70d9de1f7eb4f9d381eb60edad56edeca5cc45ce579a7b9cfb3f18ec8b4d1cc35815b705606
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3-p448
4
+ - 2.0.0-p247
5
+ - 2.1.2
6
+ before_script:
7
+ - psql -c "CREATE USER dummy WITH superuser" -U postgres
8
+ - psql -c "CREATE DATABASE dummy_test;" -U postgres
9
+ script:
10
+ - script/redb
11
+ - bundle exec rspec spec
12
+ - bundle exec cucumber features
data/CHANGELOG.md CHANGED
@@ -2,4 +2,7 @@
2
2
 
3
3
  See README.md before updating this file.
4
4
 
5
- ## Unreleased [#]
5
+ ## Unreleased [#](https://github.com/enova/landable/compare/v1.7.1.rc1...master)
6
+
7
+ ## 1.7.1.rc1 [#](https://github.com/enova/landable/compare/v1.7.0...v1.7.1.rc1)
8
+ * Allow for revisions for Templates [#3]
data/Gemfile CHANGED
@@ -1,6 +1,9 @@
1
- source "http://gems.enova.com"
2
1
  source "https://rubygems.org"
3
2
 
3
+ group :test do
4
+ gem "cucumber-rails", require: false
5
+ end
6
+
4
7
  # Declare your gem's dependencies in landable.gemspec.
5
8
  # Bundler will treat runtime dependencies like base dependencies, and
6
9
  # development dependencies will be added by default to the :development group.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Landable
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/landable.svg)](http://rubygems.org/gems/landable)
4
+ [![Build Status](https://travis-ci.org/enova/landable.svg?branch=master)](https://travis-ci.org/enova/landable)
5
+
3
6
  Landable is an API for building your CMS. It's implemented as a [Rails Engine](http://guides.rubyonrails.org/engines.html). We like to use Landable with [Publicist](https://github.com/enova/publicist), a great UI for managing content.
4
7
 
5
8
 
@@ -40,7 +43,7 @@ end
40
43
 
41
44
  ## Development
42
45
 
43
- Run `./bin/redb` to refresh the dummy app's database.
46
+ Run `./script/redb` to refresh the dummy app's database.
44
47
 
45
48
  Run the test suite with `rake landable`.
46
49
 
@@ -0,0 +1,27 @@
1
+ require_dependency "landable/api_controller"
2
+
3
+ module Landable
4
+ module Api
5
+ class TemplateRevisionsController < ApiController
6
+
7
+ def index
8
+ template = Template.find(params[:template_id])
9
+ respond_with template.revisions.order(:ordinal).reverse
10
+ end
11
+
12
+ def show
13
+ revision = TemplateRevision.find(params[:id])
14
+
15
+ respond_to do |format|
16
+ format.json { respond_with revision }
17
+ end
18
+ end
19
+
20
+ def revert_to
21
+ revision = TemplateRevision.find(params[:id])
22
+ revision.template.revert_to! revision
23
+ respond_with revision
24
+ end
25
+ end
26
+ end
27
+ end
@@ -23,10 +23,16 @@ module Landable
23
23
  respond_with template
24
24
  end
25
25
 
26
+ def publish
27
+ template = Template.find params[:id]
28
+ template.publish! author_id: current_author.id, notes: params[:notes], is_minor: !!params[:is_minor]
29
+ respond_with template
30
+ end
31
+
26
32
  private
27
33
 
28
34
  def template_params
29
- params.require(:template).permit(:id, :name, :body, :description, :thumbnail_url, :slug, :is_layout)
35
+ params.require(:template).permit(:id, :name, :body, :description, :thumbnail_url, :slug, :is_layout, :is_publishable)
30
36
  end
31
37
  end
32
38
  end
@@ -6,6 +6,14 @@ module Landable
6
6
  validates_uniqueness_of :name, case_sensitive: false
7
7
  validates_uniqueness_of :slug, case_sensitive: false
8
8
 
9
+
10
+ belongs_to :published_revision, class_name: 'Landable::TemplateRevision'
11
+ has_many :revisions, class_name: 'Landable::TemplateRevision'
12
+
13
+ before_save -> template {
14
+ template.is_publishable = true unless template.published_revision_id_changed?
15
+ }
16
+
9
17
  def name= val
10
18
  self[:name] = val
11
19
  self[:slug] ||= (val && val.underscore.gsub(/[^\w_]/, '_').gsub(/_{2,}/, '_'))
@@ -15,6 +23,23 @@ module Landable
15
23
  file.present?
16
24
  end
17
25
 
26
+ def publish!(options)
27
+ transaction do
28
+ published_revision.unpublish! if published_revision
29
+ revision = revisions.create! options
30
+ update_attributes!(published_revision: revision, is_publishable: false)
31
+ end
32
+ end
33
+
34
+ def revert_to!(revision)
35
+ self.name = revision.name
36
+ self.body = revision.body
37
+ self.description = revision.description
38
+ self.slug = revision.slug
39
+
40
+ save!
41
+ end
42
+
18
43
  class << self
19
44
  def create_from_partials!
20
45
  Partial.all.map(&:to_template)
@@ -0,0 +1,40 @@
1
+ module Landable
2
+ class TemplateRevision < ActiveRecord::Base
3
+ include Landable::TableName
4
+
5
+ @@ignored_template_attributes = [
6
+ 'editable',
7
+ 'created_at',
8
+ 'updated_at',
9
+ 'published_revision_id',
10
+ 'file',
11
+ 'thumbnail_url',
12
+ 'is_layout',
13
+ 'is_publishable',
14
+ ]
15
+
16
+ cattr_accessor :ignored_template_attributes
17
+
18
+ belongs_to :template, inverse_of: :revisions
19
+ belongs_to :author
20
+
21
+ def template_id=(id)
22
+ # set the value
23
+ self[:template_id] = id
24
+
25
+ # copy attributes from the template
26
+ self.name = template.name
27
+ self.body = template.body
28
+ self.description = template.description
29
+ self.slug = template.slug
30
+ end
31
+
32
+ def publish!
33
+ update_attribute :is_published, true
34
+ end
35
+
36
+ def unpublish!
37
+ update_attribute :is_published, false
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,12 @@
1
+ require_dependency 'landable/author_serializer'
2
+
3
+ module Landable
4
+ class TemplateRevisionSerializer < ActiveModel::Serializer
5
+ attributes :id, :ordinal, :notes, :is_minor, :is_published
6
+ attributes :created_at, :updated_at
7
+
8
+ embed :ids
9
+ has_one :template
10
+ has_one :author, include: true, serializer: AuthorSerializer
11
+ end
12
+ end
@@ -1,5 +1,10 @@
1
1
  module Landable
2
2
  class TemplateSerializer < ActiveModel::Serializer
3
- attributes :id, :name, :body, :description, :thumbnail_url, :slug, :is_layout, :editable, :file
3
+ attributes :id, :name, :body, :description
4
+ attributes :thumbnail_url, :slug, :is_layout, :editable
5
+ attributes :file, :is_publishable
6
+
7
+ embed :ids
8
+ has_one :published_revision
4
9
  end
5
10
  end
data/config/routes.rb CHANGED
@@ -22,7 +22,13 @@ Landable::Engine.routes.draw do
22
22
  post 'preview', on: :collection
23
23
  end
24
24
 
25
- resources :templates, only: [:index, :show, :create, :update]
25
+ resources :templates, only: [:index, :show, :create, :update] do
26
+ post 'publish', on: :member
27
+ end
28
+
29
+ resources :template_revisions, only: [:index, :show] do
30
+ post 'revert_to', on: :member
31
+ end
26
32
 
27
33
  resources :pages, concerns: [:has_assets, :has_screenshots] do
28
34
  post 'preview', on: :collection
@@ -0,0 +1,77 @@
1
+ class CreateTemplateRevisions < ActiveRecord::Migration
2
+ def change
3
+ create_table "#{Landable.configuration.database_schema_prefix}landable.template_revisions", id: :uuid, primary_key: :template_revision_id do |t|
4
+ t.integer :ordinal
5
+ t.text :notes
6
+ t.boolean :is_minor, default: false
7
+ t.boolean :is_published, default: true
8
+
9
+ t.uuid :template_id, null: false
10
+ t.uuid :author_id, null: false
11
+
12
+ t.text :name
13
+ t.text :slug
14
+ t.text :body
15
+ t.text :description
16
+
17
+ t.timestamps
18
+ end
19
+
20
+ add_column "#{Landable.configuration.database_schema_prefix}landable.templates", :published_revision_id, :uuid
21
+ add_column "#{Landable.configuration.database_schema_prefix}landable.templates", :is_publishable, :boolean, null: :false, default: true
22
+
23
+ # Create foreign keys
24
+ execute <<-SQL
25
+ ALTER TABLE #{Landable::Template.table_name}
26
+ ADD CONSTRAINT template_revision_id_fk
27
+ FOREIGN KEY (published_revision_id)
28
+ REFERENCES #{Landable::TemplateRevision.table_name} (template_revision_id);
29
+
30
+ ALTER TABLE #{Landable::TemplateRevision.table_name}
31
+ ADD CONSTRAINT template_id_fk
32
+ FOREIGN KEY (template_id)
33
+ REFERENCES #{Landable::Template.table_name} (template_id);
34
+
35
+ ALTER TABLE #{Landable::TemplateRevision.table_name}
36
+ ADD CONSTRAINT author_id_fk
37
+ FOREIGN KEY (author_id)
38
+ REFERENCES #{Landable::Author.table_name} (author_id);
39
+ SQL
40
+
41
+ # Revision-tracking trigger to automatically update ordinal
42
+ execute "CREATE FUNCTION #{Landable.configuration.database_schema_prefix}landable.template_revision_ordinal()
43
+ RETURNS TRIGGER
44
+ AS
45
+ $TRIGGER$
46
+ BEGIN
47
+
48
+ IF NEW.ordinal IS NOT NULL THEN
49
+ RAISE EXCEPTION $$Must not supply ordinal value manually.$$;
50
+ END IF;
51
+
52
+ NEW.ordinal = (SELECT COALESCE(MAX(ordinal)+1,1)
53
+ FROM #{Landable.configuration.database_schema_prefix}landable.template_revisions
54
+ WHERE template_id = NEW.template_id);
55
+
56
+ RETURN NEW;
57
+
58
+ END
59
+ $TRIGGER$
60
+ LANGUAGE plpgsql;"
61
+
62
+ execute "CREATE TRIGGER #{Landable.configuration.database_schema_prefix}landable_template_revisions__bfr_insert
63
+ BEFORE INSERT ON #{Landable.configuration.database_schema_prefix}landable.template_revisions
64
+ FOR EACH ROW EXECUTE PROCEDURE #{Landable.configuration.database_schema_prefix}landable.template_revision_ordinal();"
65
+
66
+ # Trigger disallowing deletes on template_revisions
67
+ execute "CREATE TRIGGER #{Landable.configuration.database_schema_prefix}landable_template_revisions__no_delete
68
+ BEFORE DELETE ON #{Landable.configuration.database_schema_prefix}landable.template_revisions
69
+ FOR EACH STATEMENT EXECUTE PROCEDURE #{Landable.configuration.database_schema_prefix}landable.tg_disallow();"
70
+
71
+ execute "CREATE TRIGGER #{Landable.configuration.database_schema_prefix}landable_template_revisions__no_update
72
+ BEFORE UPDATE OF notes, is_minor, template_id, author_id, created_at, ordinal ON #{Landable.configuration.database_schema_prefix}landable.template_revisions
73
+ FOR EACH STATEMENT EXECUTE PROCEDURE #{Landable.configuration.database_schema_prefix}landable.tg_disallow();"
74
+
75
+
76
+ end
77
+ end
@@ -0,0 +1,62 @@
1
+ {
2
+ "title": "Template",
3
+ "description": "A template served by landable",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["name", "slug", "description", "is_layout", "body"],
7
+
8
+ "properties": {
9
+ "id": {
10
+ "$ref": "uuid.json"
11
+ },
12
+
13
+ "name": {
14
+ "type": ["string"]
15
+ },
16
+
17
+ "slug": {
18
+ "type": ["string"]
19
+ },
20
+
21
+ "file": {
22
+ "type": ["string", "null"]
23
+ },
24
+
25
+ "title": {
26
+ "type": ["string"]
27
+ },
28
+
29
+ "body": {
30
+ "type": ["string"]
31
+ },
32
+
33
+ "description": {
34
+ "type": ["string"]
35
+ },
36
+
37
+ "is_layout": {
38
+ "type": "boolean"
39
+ },
40
+
41
+ "editable": {
42
+ "type": "boolean"
43
+ },
44
+
45
+ "thumbnail_url": {
46
+ "type": ["string", "null"]
47
+ },
48
+
49
+ "is_publishable": {
50
+ "type": "boolean"
51
+ },
52
+
53
+ "published_revision_id": {
54
+ "type": ["null", {"$ref": "uuid.json"}]
55
+ },
56
+
57
+ "updated_by_author_id": {
58
+ "type": ["null", {"$ref": "uuid.json"}]
59
+ }
60
+
61
+ }
62
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "title": "Template Revision",
3
+ "description": "A particular snapshot of a template which has, at some point, been published.",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["id", "template_id", "author_id", "notes", "is_published", "is_minor", "ordinal", "created_at", "updated_at"],
7
+
8
+ "properties": {
9
+ "id": {
10
+ "$ref": "uuid.json"
11
+ },
12
+
13
+ "template_id": {
14
+ "$ref": "uuid.json"
15
+ },
16
+
17
+ "author_id": {
18
+ "type": "string"
19
+ },
20
+
21
+ "notes": {
22
+ "type": ["string", "null"]
23
+ },
24
+
25
+ "is_published": {
26
+ "type": "boolean"
27
+ },
28
+
29
+ "is_minor": {
30
+ "type": "boolean"
31
+ },
32
+
33
+ "ordinal": {
34
+ "type": "integer",
35
+ "minimum": 1
36
+ },
37
+
38
+ "created_at": {
39
+ "type": "string",
40
+ "format": "date-time"
41
+ },
42
+
43
+ "updated_at": {
44
+ "type": "string",
45
+ "format": "date-time"
46
+ }
47
+ }
48
+ }
@@ -20,7 +20,7 @@ Feature: Access Tokens API
20
20
  And the response body should be empty
21
21
 
22
22
  Scenario: Creating an author if none yet exists
23
- Given there are no authors in the database
23
+ Given an author "someone" does not exist
24
24
  When I POST to "/api/access_tokens" with:
25
25
  """
26
26
  { "access_token": { "username": "someone", "password": "anything" } }
@@ -34,7 +34,7 @@ Feature: Access Tokens API
34
34
  """
35
35
  { "access_token": { "username": "someone", "password": "anything" } }
36
36
  """
37
- Then there should be 1 author in the database
37
+ Then there should be 2 author in the database
38
38
  And the author "someone" should have 1 access token
39
39
 
40
40
  Scenario: Retrieving my own fresh token