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.
- 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
|
+
[](http://rubygems.org/gems/landable)
|
|
4
|
+
[](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
|