ninetails 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -4
  3. data/Rakefile +3 -3
  4. data/app/components/ninetails/element.rb +5 -1
  5. data/app/components/ninetails/element_definition.rb +2 -2
  6. data/app/components/ninetails/property_type.rb +0 -2
  7. data/app/components/ninetails/{section_template.rb → section.rb} +6 -8
  8. data/app/controllers/ninetails/application_controller.rb +1 -0
  9. data/app/controllers/ninetails/page_revisions_controller.rb +2 -3
  10. data/app/controllers/ninetails/pages_controller.rb +20 -1
  11. data/app/controllers/ninetails/sections_controller.rb +35 -0
  12. data/app/models/ninetails/page.rb +3 -16
  13. data/app/models/ninetails/page_revision.rb +2 -8
  14. data/app/models/ninetails/page_revision_section.rb +1 -1
  15. data/app/models/ninetails/page_section.rb +40 -0
  16. data/app/views/ninetails/page_revisions/index.json.jbuilder +1 -0
  17. data/app/views/ninetails/pages/_page.json.jbuilder +13 -0
  18. data/app/views/ninetails/pages/index.json.jbuilder +1 -0
  19. data/app/views/ninetails/pages/show.json.jbuilder +1 -0
  20. data/app/views/ninetails/sections/_section.json.jbuilder +1 -0
  21. data/app/views/ninetails/sections/validate.json.jbuilder +3 -0
  22. data/config/routes.rb +9 -5
  23. data/config/spring.rb +1 -0
  24. data/db/migrate/20151120120538_rename_sections_to_page_sections.rb +5 -0
  25. data/lib/ninetails/config.rb +8 -0
  26. data/lib/ninetails/engine.rb +5 -0
  27. data/lib/ninetails/key_conversion.rb +28 -0
  28. data/lib/ninetails/version.rb +1 -1
  29. data/lib/tasks/ninetails_tasks.rake +2 -2
  30. data/spec/components/element_spec.rb +3 -3
  31. data/spec/components/property_spec.rb +2 -2
  32. data/spec/components/property_type_spec.rb +5 -5
  33. data/spec/components/section_spec.rb +2 -2
  34. data/spec/dummy/app/components/{section_template → section}/billboard.rb +2 -2
  35. data/spec/dummy/app/components/{section_template → section}/document_head.rb +2 -2
  36. data/spec/dummy/app/components/section/minimal_billboard.rb +9 -0
  37. data/spec/dummy/bin/rails +5 -0
  38. data/spec/dummy/bin/rake +5 -0
  39. data/spec/dummy/bin/rspec +8 -0
  40. data/spec/dummy/bin/spring +15 -0
  41. data/spec/dummy/config/application.rb +0 -10
  42. data/spec/dummy/config/initializers/ninetails.rb +1 -0
  43. data/spec/dummy/config/spring.rb +1 -0
  44. data/spec/dummy/db/schema.rb +10 -10
  45. data/spec/dummy/log/development.log +2499 -0
  46. data/spec/dummy/log/test.log +55690 -0
  47. data/spec/factories/page_sections.rb +15 -0
  48. data/spec/models/{section_spec.rb → page_section_spec.rb} +17 -36
  49. data/spec/models/page_spec.rb +9 -14
  50. data/spec/requests/middleware_spec.rb +80 -0
  51. data/spec/requests/page_revisions_spec.rb +41 -42
  52. data/spec/requests/pages_spec.rb +54 -1
  53. data/spec/requests/sections_spec.rb +32 -0
  54. data/spec/spec_helper.rb +4 -0
  55. data/spec/support/section_helpers.rb +27 -0
  56. metadata +74 -35
  57. data/app/controllers/ninetails/section_templates_controller.rb +0 -17
  58. data/app/models/ninetails/section.rb +0 -30
  59. data/db/schema.rb +0 -53
  60. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  61. data/spec/dummy/config/initializers/inflections.rb +0 -16
  62. data/spec/dummy/config/initializers/mime_types.rb +0 -4
  63. data/spec/dummy/config/locales/en.yml +0 -23
  64. data/spec/dummy/public/404.html +0 -67
  65. data/spec/dummy/public/422.html +0 -67
  66. data/spec/dummy/public/500.html +0 -66
  67. data/spec/dummy/public/favicon.ico +0 -0
  68. data/spec/factories/sections.rb +0 -15
  69. data/spec/requests/section_templates_spec.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 83a860c0c94b90e7a5d698bf1d094ea30ddd5d02
4
- data.tar.gz: a5df117519887e875affca3ecd3a2cdb07e75620
3
+ metadata.gz: bf825500b2284d3a0d28a013d01cd244f777a340
4
+ data.tar.gz: e2dc5d6215e849d76dbc5288b26212b8e16453ab
5
5
  SHA512:
6
- metadata.gz: 99b0aa847e1b4700ef1fa8c6005f488a55b4fdd57ad03316b3702a5ce6939b4ecc50e7d46bd2dfdea16a2fe7fb7198d5b70bbaff9a643914840f4e18251aa3b9
7
- data.tar.gz: 6ed7b22e94609b662e391d0a89ad18270c630a726e7839dbd82275dd6834167d0ead1d08bf767714f5001dbef41eead94bd5ac791cc9d5e14e299a073a080cce
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 CMS API engine. 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.
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
- ## Getting started
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
- ## Thinking in sections
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
- ## Setting up Sections, Elements, and Components
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(:spec => 'app:db:test:prepare')
19
- task :default => :spec
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] = self.class.name.demodulize
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? &:valid?
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 &:properties_structure
54
+ elements.collect(&:properties_structure)
55
55
  else
56
56
  [properties_structure]
57
57
  end
@@ -18,8 +18,6 @@ module Ninetails
18
18
  serialized_values
19
19
  elsif type.respond_to? :serialize
20
20
  type.serialize
21
- else
22
- type.new
23
21
  end
24
22
  end
25
23
 
@@ -1,5 +1,5 @@
1
1
  module Ninetails
2
- class SectionTemplate
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
@@ -1,4 +1,5 @@
1
1
  module Ninetails
2
2
  class ApplicationController < ActionController::API
3
+ include ActionController::ImplicitRender
3
4
  end
4
5
  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 json: @page.to_builder.target!, status: :created
13
+ render "/ninetails/pages/show", status: :created
15
14
  else
16
- render json: @page.to_builder.target!, status: :bad_request
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
- deserialized_section = section.deserialize
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
 
@@ -1,6 +1,6 @@
1
1
  module Ninetails
2
2
  class PageRevisionSection < ActiveRecord::Base
3
3
  belongs_to :page_revision
4
- belongs_to :section
4
+ belongs_to :section, class_name: "PageSection"
5
5
  end
6
6
  end
@@ -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
@@ -0,0 +1,3 @@
1
+ json.section do
2
+ json.partial! "section", section: @section
3
+ end
@@ -1,9 +1,13 @@
1
1
  Ninetails::Engine.routes.draw do
2
- root 'pages#index'
2
+ with_options defaults: { format: :json } do
3
+ root 'pages#index'
3
4
 
4
- resources :pages, only: :show do
5
- resources :page_revisions, only: [:create, :show, :index], path: "revisions"
6
- end
5
+ resources :pages, only: [:show, :create, :index] do
6
+ resources :page_revisions, only: [:create, :show, :index], path: "revisions"
7
+ end
7
8
 
8
- resources :section_templates, only: [:show, :index]
9
+ resources :sections, only: [:show, :index] do
10
+ post :validate, on: :collection
11
+ end
12
+ end
9
13
  end
@@ -0,0 +1 @@
1
+ Spring.application_root = './spec/dummy'
@@ -0,0 +1,5 @@
1
+ class RenameSectionsToPageSections < ActiveRecord::Migration
2
+ def change
3
+ rename_table :ninetails_sections, :ninetails_page_sections
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module Ninetails
2
+ class Config
3
+
4
+ cattr_accessor :key_style
5
+ self.key_style = :camelcase
6
+
7
+ end
8
+ end
@@ -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