landable 1.7.0 → 1.7.1.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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