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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +12 -0
- data/CHANGELOG.md +4 -1
- data/Gemfile +4 -1
- data/README.md +4 -1
- data/app/controllers/landable/api/template_revisions_controller.rb +27 -0
- data/app/controllers/landable/api/templates_controller.rb +7 -1
- data/app/models/landable/template.rb +25 -0
- data/app/models/landable/template_revision.rb +40 -0
- data/app/serializers/landable/template_revision_serializer.rb +12 -0
- data/app/serializers/landable/template_serializer.rb +6 -1
- data/config/routes.rb +7 -1
- data/db/migrate/20140509190128_create_template_revisions.rb +77 -0
- data/doc/schema/template.json +62 -0
- data/doc/schema/template_revision.json +48 -0
- data/features/api/access_tokens.feature +2 -2
- data/features/api/templates.feature +7 -1
- data/features/liquid/tags.feature +22 -1
- data/features/step_definitions/factory_steps.rb +7 -2
- data/features/step_definitions/liquid_steps.rb +15 -4
- data/features/step_definitions/revision_steps.rb +6 -0
- data/landable-1.7.0.gem +0 -0
- data/landable.gemspec +1 -2
- data/lib/landable/liquid/tags.rb +17 -10
- data/lib/landable/partial.rb +5 -0
- data/lib/landable/version.rb +2 -2
- data/lib/tasks/landable/template.rake +10 -0
- data/{bin → script}/rails +0 -0
- data/{bin → script}/redb +0 -0
- data/spec/controllers/landable/api/template_revisions_controller_spec.rb +29 -0
- data/spec/dummy/db/structure.sql +103 -2
- data/spec/factories/template_revision.rb +6 -0
- data/spec/lib/landable/partial_spec.rb +5 -0
- data/spec/models/landable/template_revision_spec.rb +35 -0
- data/spec/models/landable/template_spec.rb +67 -0
- metadata +65 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 68d0d159a866cc88742e6b5603bcd0d5edb09b65
|
4
|
+
data.tar.gz: 2f1aa05942baaedab945f6163cebd4e65c5e4599
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 `./
|
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
|
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
|
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
|
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
|