ninetails 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -4
- data/Rakefile +3 -3
- data/app/components/ninetails/element.rb +5 -1
- data/app/components/ninetails/element_definition.rb +2 -2
- data/app/components/ninetails/property_type.rb +0 -2
- data/app/components/ninetails/{section_template.rb → section.rb} +6 -8
- data/app/controllers/ninetails/application_controller.rb +1 -0
- data/app/controllers/ninetails/page_revisions_controller.rb +2 -3
- data/app/controllers/ninetails/pages_controller.rb +20 -1
- data/app/controllers/ninetails/sections_controller.rb +35 -0
- data/app/models/ninetails/page.rb +3 -16
- data/app/models/ninetails/page_revision.rb +2 -8
- data/app/models/ninetails/page_revision_section.rb +1 -1
- data/app/models/ninetails/page_section.rb +40 -0
- data/app/views/ninetails/page_revisions/index.json.jbuilder +1 -0
- data/app/views/ninetails/pages/_page.json.jbuilder +13 -0
- data/app/views/ninetails/pages/index.json.jbuilder +1 -0
- data/app/views/ninetails/pages/show.json.jbuilder +1 -0
- data/app/views/ninetails/sections/_section.json.jbuilder +1 -0
- data/app/views/ninetails/sections/validate.json.jbuilder +3 -0
- data/config/routes.rb +9 -5
- data/config/spring.rb +1 -0
- data/db/migrate/20151120120538_rename_sections_to_page_sections.rb +5 -0
- data/lib/ninetails/config.rb +8 -0
- data/lib/ninetails/engine.rb +5 -0
- data/lib/ninetails/key_conversion.rb +28 -0
- data/lib/ninetails/version.rb +1 -1
- data/lib/tasks/ninetails_tasks.rake +2 -2
- data/spec/components/element_spec.rb +3 -3
- data/spec/components/property_spec.rb +2 -2
- data/spec/components/property_type_spec.rb +5 -5
- data/spec/components/section_spec.rb +2 -2
- data/spec/dummy/app/components/{section_template → section}/billboard.rb +2 -2
- data/spec/dummy/app/components/{section_template → section}/document_head.rb +2 -2
- data/spec/dummy/app/components/section/minimal_billboard.rb +9 -0
- data/spec/dummy/bin/rails +5 -0
- data/spec/dummy/bin/rake +5 -0
- data/spec/dummy/bin/rspec +8 -0
- data/spec/dummy/bin/spring +15 -0
- data/spec/dummy/config/application.rb +0 -10
- data/spec/dummy/config/initializers/ninetails.rb +1 -0
- data/spec/dummy/config/spring.rb +1 -0
- data/spec/dummy/db/schema.rb +10 -10
- data/spec/dummy/log/development.log +2499 -0
- data/spec/dummy/log/test.log +55690 -0
- data/spec/factories/page_sections.rb +15 -0
- data/spec/models/{section_spec.rb → page_section_spec.rb} +17 -36
- data/spec/models/page_spec.rb +9 -14
- data/spec/requests/middleware_spec.rb +80 -0
- data/spec/requests/page_revisions_spec.rb +41 -42
- data/spec/requests/pages_spec.rb +54 -1
- data/spec/requests/sections_spec.rb +32 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/section_helpers.rb +27 -0
- metadata +74 -35
- data/app/controllers/ninetails/section_templates_controller.rb +0 -17
- data/app/models/ninetails/section.rb +0 -30
- data/db/schema.rb +0 -53
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/inflections.rb +0 -16
- data/spec/dummy/config/initializers/mime_types.rb +0 -4
- data/spec/dummy/config/locales/en.yml +0 -23
- data/spec/dummy/public/404.html +0 -67
- data/spec/dummy/public/422.html +0 -67
- data/spec/dummy/public/500.html +0 -66
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/sections.rb +0 -15
- data/spec/requests/section_templates_spec.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf825500b2284d3a0d28a013d01cd244f777a340
|
4
|
+
data.tar.gz: e2dc5d6215e849d76dbc5288b26212b8e16453ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6475bd8ed8242c51e4acae2778f5db16a1e1116e76b6004634eb6df4080daa8671de7af17e55c8efd96a80da0f57c0faf81055a20d1e5a4763d4d20131f16c97
|
7
|
+
data.tar.gz: 833c7d437b231c5568a0f0d67cd076f0808fa6c1c2ef5a64de9ffcf7342be5229296024d45e4e7e9a9a9b564fb9aa23962279b769182e5da861b1ada21003953
|
data/README.md
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
![Ninetails](http://i.imgur.com/jv28Kg3.png)
|
2
2
|
|
3
3
|
[![Build Status](https://travis-ci.org/iZettle/ninetails.svg)](https://travis-ci.org/iZettle/ninetails)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/iZettle/ninetails/badges/gpa.svg)](https://codeclimate.com/github/iZettle/ninetails)
|
5
|
+
[![Test Coverage](https://codeclimate.com/github/iZettle/ninetails/badges/coverage.svg)](https://codeclimate.com/github/iZettle/ninetails/coverage)
|
4
6
|
|
5
|
-
Ninetails is a simple
|
7
|
+
Ninetails is a simple Rails engine to create an API for a content management system to run on. It is designed on the idea of a CMS which is limited in scope in that not all aspects of a page should be editable. Instead, you can define Sections, Elements, and Properties which are editable and can then be loaded in and styled in a reliable manner. It gives you the control to decide exactly what should and what should not be editable in your project.
|
6
8
|
|
7
|
-
|
9
|
+
**WARNING: Ninetails is very much a work in progress and is not ready to be fully used yet!**
|
10
|
+
|
11
|
+
# Getting started
|
8
12
|
|
9
13
|
Ninetails is a mountable Rails engine, so it needs to be loaded into your app. You app is also where you will define your components which Ninetails should expose. Start by adding it to your Gemfile:
|
10
14
|
|
@@ -12,6 +16,25 @@ Ninetails is a mountable Rails engine, so it needs to be loaded into your app. Y
|
|
12
16
|
gem "ninetails"
|
13
17
|
```
|
14
18
|
|
15
|
-
|
19
|
+
Then you need to mount the engine in `config/routes.rb`
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Rails.application.routes.draw do
|
23
|
+
mount Ninetails::Engine, at: "/api"
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage guide
|
16
28
|
|
17
|
-
|
29
|
+
* [Thinking in sections](https://github.com/iZettle/ninetails/wiki/Thinking-in-sections)
|
30
|
+
* [Creating pages](https://github.com/iZettle/ninetails/wiki/Creating-pages)
|
31
|
+
* [Publishing pages](https://github.com/iZettle/ninetails/wiki/Publishing-pages)
|
32
|
+
* [Validating properties](https://github.com/iZettle/ninetails/wiki/Validating-properties)
|
33
|
+
|
34
|
+
## Configuration
|
35
|
+
|
36
|
+
By default, Ninetails will force all keys in JSON responses to be camelcase with the first letter lowercase, to conform with JSON standards. If you would prefer the output to be as underscored, you can set this in an initializer:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
Ninetails::Config.key_style = :underscore
|
40
|
+
```
|
data/Rakefile
CHANGED
@@ -9,11 +9,11 @@ load 'rails/tasks/engine.rake'
|
|
9
9
|
|
10
10
|
Bundler::GemHelper.install_tasks
|
11
11
|
|
12
|
-
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
12
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
|
13
13
|
|
14
14
|
require 'rspec/core'
|
15
15
|
require 'rspec/core/rake_task'
|
16
16
|
|
17
17
|
desc "Run all specs in spec directory (excluding plugin specs)"
|
18
|
-
RSpec::Core::RakeTask.new(:
|
19
|
-
task :
|
18
|
+
RSpec::Core::RakeTask.new(spec: 'app:db:test:prepare')
|
19
|
+
task default: :spec
|
@@ -6,6 +6,10 @@ module Ninetails
|
|
6
6
|
@reference ||= SecureRandom.uuid
|
7
7
|
end
|
8
8
|
|
9
|
+
def name
|
10
|
+
@name ||= self.class.name.demodulize
|
11
|
+
end
|
12
|
+
|
9
13
|
def deserialize(input)
|
10
14
|
properties_instances.collect do |property|
|
11
15
|
property.serialized_values = input[property.name.to_s]
|
@@ -41,7 +45,7 @@ module Ninetails
|
|
41
45
|
|
42
46
|
def generate_structure
|
43
47
|
properties_instances.each_with_object({}) do |property_type, hash|
|
44
|
-
hash[:type] =
|
48
|
+
hash[:type] = name
|
45
49
|
hash[:reference] = reference
|
46
50
|
hash[property_type.name] = property_type.serialize
|
47
51
|
|
@@ -36,7 +36,7 @@ module Ninetails
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def all_elements_valid?
|
39
|
-
elements.all?
|
39
|
+
elements.all?(&:valid?)
|
40
40
|
end
|
41
41
|
|
42
42
|
private
|
@@ -51,7 +51,7 @@ module Ninetails
|
|
51
51
|
|
52
52
|
def multiple_element_structure
|
53
53
|
if elements.present?
|
54
|
-
elements.collect
|
54
|
+
elements.collect(&:properties_structure)
|
55
55
|
else
|
56
56
|
[properties_structure]
|
57
57
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Ninetails
|
2
|
-
class
|
2
|
+
class Section
|
3
3
|
ALLOWED_POSITIONS = [:head, :body]
|
4
4
|
|
5
5
|
attr_accessor :elements_instances
|
@@ -16,6 +16,11 @@ module Ninetails
|
|
16
16
|
@position
|
17
17
|
end
|
18
18
|
|
19
|
+
def self.define_element(name, type, count)
|
20
|
+
@elements ||= []
|
21
|
+
@elements << Ninetails::ElementDefinition.new(name, type, count)
|
22
|
+
end
|
23
|
+
|
19
24
|
def self.has_element(name, type)
|
20
25
|
define_element name, type, :single
|
21
26
|
end
|
@@ -55,12 +60,5 @@ module Ninetails
|
|
55
60
|
end
|
56
61
|
end
|
57
62
|
|
58
|
-
private
|
59
|
-
|
60
|
-
def self.define_element(name, type, count)
|
61
|
-
@elements ||= []
|
62
|
-
@elements << Ninetails::ElementDefinition.new(name, type, count)
|
63
|
-
end
|
64
|
-
|
65
63
|
end
|
66
64
|
end
|
@@ -4,16 +4,15 @@ module Ninetails
|
|
4
4
|
before_action :find_page
|
5
5
|
|
6
6
|
def index
|
7
|
-
render json: { revisions: @page.revisions }
|
8
7
|
end
|
9
8
|
|
10
9
|
def create
|
11
10
|
@page.build_revision_from_params revision_params
|
12
11
|
|
13
12
|
if @page.revision.save
|
14
|
-
render
|
13
|
+
render "/ninetails/pages/show", status: :created
|
15
14
|
else
|
16
|
-
render
|
15
|
+
render "/ninetails/pages/show", status: :bad_request
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
@@ -1,14 +1,33 @@
|
|
1
1
|
module Ninetails
|
2
2
|
class PagesController < ApplicationController
|
3
3
|
|
4
|
+
def index
|
5
|
+
@pages = Page.all
|
6
|
+
end
|
7
|
+
|
4
8
|
def show
|
5
9
|
@page = Page.find_by! url: params[:id]
|
6
10
|
@page.revision = @page.revisions.find params[:revision_id] if params[:revision_id]
|
7
11
|
|
8
|
-
render json: @page.to_builder.target!
|
9
12
|
rescue ActiveRecord::RecordNotFound
|
10
13
|
render json: {}, status: :not_found
|
11
14
|
end
|
12
15
|
|
16
|
+
def create
|
17
|
+
@page = Page.new page_params
|
18
|
+
|
19
|
+
if @page.save
|
20
|
+
render :show, status: :created
|
21
|
+
else
|
22
|
+
render :show, status: :bad_request
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def page_params
|
29
|
+
params.require(:page).permit(:url, :name)
|
30
|
+
end
|
31
|
+
|
13
32
|
end
|
14
33
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Ninetails
|
2
|
+
class SectionsController < ApplicationController
|
3
|
+
|
4
|
+
def index
|
5
|
+
sections = Dir.glob(Rails.root.join("app", "components", "section", "*.rb")).collect do |entry|
|
6
|
+
empty_section_from_name File.basename(entry, ".rb")
|
7
|
+
end
|
8
|
+
|
9
|
+
render json: { sections: sections }
|
10
|
+
end
|
11
|
+
|
12
|
+
def show
|
13
|
+
render json: empty_section_from_name(params[:id])
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate
|
17
|
+
@section = Ninetails::PageSection.new section_params
|
18
|
+
|
19
|
+
unless @section.valid?
|
20
|
+
render status: :bad_request
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def empty_section_from_name(id)
|
27
|
+
"Section::#{id.classify}".safe_constantize.new.serialize
|
28
|
+
end
|
29
|
+
|
30
|
+
def section_params
|
31
|
+
params.require(:section).permit!
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -3,29 +3,16 @@ module Ninetails
|
|
3
3
|
has_many :revisions, class_name: "PageRevision"
|
4
4
|
has_one :current_revision, class_name: "PageRevision"
|
5
5
|
|
6
|
+
validates :url, presence: true, uniqueness: true
|
7
|
+
|
6
8
|
attr_writer :revision
|
7
9
|
|
8
10
|
def revision
|
9
11
|
@revision || current_revision
|
10
12
|
end
|
11
13
|
|
12
|
-
def to_builder
|
13
|
-
Jbuilder.new do |json|
|
14
|
-
json.page do
|
15
|
-
json.call(self, :id, :name, :url)
|
16
|
-
json.revision_id revision.id
|
17
|
-
json.sections sections_to_builder
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def sections_to_builder
|
23
|
-
revision.sections.collect do |section|
|
24
|
-
section.to_builder.attributes!
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
14
|
def build_revision_from_params(page_revision)
|
15
|
+
page_revision = page_revision.convert_keys(:underscore).with_indifferent_access
|
29
16
|
self.revision = revisions.build message: page_revision[:message]
|
30
17
|
|
31
18
|
page_revision[:sections].each do |section_json|
|
@@ -12,15 +12,9 @@ module Ninetails
|
|
12
12
|
# contain error messages
|
13
13
|
def sections_are_all_valid
|
14
14
|
sections.each do |section|
|
15
|
-
|
16
|
-
|
17
|
-
deserialized_section.elements_instances.each do |element_definition|
|
18
|
-
unless element_definition.all_elements_valid?
|
19
|
-
errors.add :base, "Element #{element_definition.name} is not valid"
|
20
|
-
end
|
15
|
+
unless section.valid?
|
16
|
+
errors.add :base, section.errors.messages[:base]
|
21
17
|
end
|
22
|
-
|
23
|
-
section.elements = deserialized_section.serialize[:elements]
|
24
18
|
end
|
25
19
|
end
|
26
20
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ninetails
|
2
|
+
class PageSection < ActiveRecord::Base
|
3
|
+
self.inheritance_column = nil
|
4
|
+
has_many :page_revision_sections
|
5
|
+
has_many :page_revisions, through: :page_revision_sections
|
6
|
+
|
7
|
+
validate :validate_elements
|
8
|
+
|
9
|
+
def section
|
10
|
+
@section ||= "Section::#{type}".safe_constantize.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def deserialize
|
14
|
+
section.elements_instances = []
|
15
|
+
|
16
|
+
elements.each do |name, element_json|
|
17
|
+
element = section.class.find_element name
|
18
|
+
element.deserialize element_json
|
19
|
+
section.elements_instances << element
|
20
|
+
end
|
21
|
+
|
22
|
+
section
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def validate_elements
|
28
|
+
deserialized_section = deserialize
|
29
|
+
|
30
|
+
deserialized_section.elements_instances.each do |element_definition|
|
31
|
+
unless element_definition.all_elements_valid?
|
32
|
+
errors.add :base, "Element #{element_definition.name} is not valid"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
self.elements = deserialized_section.serialize[:elements]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
json.revisions @page.revisions, :id, :page_id, :created_at, :updated_at, :message
|
@@ -0,0 +1,13 @@
|
|
1
|
+
json.page do
|
2
|
+
json.call page, :id, :name, :url
|
3
|
+
|
4
|
+
if page.revision.present?
|
5
|
+
json.revision_id page.revision.id
|
6
|
+
|
7
|
+
json.sections page.revision.sections, partial: "/ninetails/sections/section", as: :section
|
8
|
+
end
|
9
|
+
|
10
|
+
if page.errors.present?
|
11
|
+
json.errors page.errors
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
json.pages @pages, :id, :url, :name
|
@@ -0,0 +1 @@
|
|
1
|
+
json.partial! "/ninetails/pages/page", page: @page
|
@@ -0,0 +1 @@
|
|
1
|
+
json.call section, :id, :name, :type, :tags, :elements
|
data/config/routes.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
Ninetails::Engine.routes.draw do
|
2
|
-
|
2
|
+
with_options defaults: { format: :json } do
|
3
|
+
root 'pages#index'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
resources :pages, only: [:show, :create, :index] do
|
6
|
+
resources :page_revisions, only: [:create, :show, :index], path: "revisions"
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
+
resources :sections, only: [:show, :index] do
|
10
|
+
post :validate, on: :collection
|
11
|
+
end
|
12
|
+
end
|
9
13
|
end
|
data/config/spring.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Spring.application_root = './spec/dummy'
|
data/lib/ninetails/engine.rb
CHANGED
@@ -9,6 +9,9 @@ require "jbuilder"
|
|
9
9
|
require "hash-pipe"
|
10
10
|
require "virtus"
|
11
11
|
|
12
|
+
require "ninetails/config"
|
13
|
+
require "ninetails/key_conversion"
|
14
|
+
|
12
15
|
begin
|
13
16
|
require "pry"
|
14
17
|
rescue LoadError
|
@@ -18,6 +21,8 @@ module Ninetails
|
|
18
21
|
class Engine < ::Rails::Engine
|
19
22
|
isolate_namespace Ninetails
|
20
23
|
|
24
|
+
middleware.use "Ninetails::KeyConversion"
|
25
|
+
|
21
26
|
config.generators do |g|
|
22
27
|
g.test_framework :rspec, fixture: false
|
23
28
|
g.fixture_replacement :factory_girl, dir: 'spec/factories'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ninetails
|
2
|
+
class KeyConversion
|
3
|
+
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
@status, @headers, @response = @app.call(env)
|
10
|
+
|
11
|
+
if @response.respond_to? :body
|
12
|
+
[@status, @headers, [modify_keys(@response.body)]]
|
13
|
+
else
|
14
|
+
[@status, @headers, @response]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def modify_keys(body)
|
19
|
+
if @headers["Content-Type"].include?("application/json") && Ninetails::Config.key_style == :camelcase
|
20
|
+
body = JSON.parse(body).convert_keys -> (key) { key.camelcase :lower }
|
21
|
+
body.to_json
|
22
|
+
else
|
23
|
+
body
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|