ninetails 0.0.1

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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +17 -0
  4. data/Rakefile +19 -0
  5. data/app/components/ninetails/element.rb +55 -0
  6. data/app/components/ninetails/element_definition.rb +67 -0
  7. data/app/components/ninetails/property.rb +11 -0
  8. data/app/components/ninetails/property_store.rb +22 -0
  9. data/app/components/ninetails/property_type.rb +27 -0
  10. data/app/components/ninetails/section_template.rb +66 -0
  11. data/app/controllers/ninetails/application_controller.rb +4 -0
  12. data/app/controllers/ninetails/page_revisions_controller.rb +31 -0
  13. data/app/controllers/ninetails/pages_controller.rb +14 -0
  14. data/app/controllers/ninetails/section_templates_controller.rb +17 -0
  15. data/app/models/ninetails/page.rb +37 -0
  16. data/app/models/ninetails/page_revision.rb +28 -0
  17. data/app/models/ninetails/page_revision_section.rb +6 -0
  18. data/app/models/ninetails/section.rb +30 -0
  19. data/config/initializers/error_classes.rb +4 -0
  20. data/config/initializers/json_stuff.rb +9 -0
  21. data/config/routes.rb +9 -0
  22. data/db/migrate/20151006093040_create_pages.rb +10 -0
  23. data/db/migrate/20151006093559_create_page_revisions.rb +8 -0
  24. data/db/migrate/20151006093754_create_sections.rb +11 -0
  25. data/db/migrate/20151006094337_create_page_revision_sections.rb +8 -0
  26. data/db/migrate/20151111085201_add_message_to_page_revisions.rb +5 -0
  27. data/db/schema.rb +53 -0
  28. data/lib/ninetails.rb +4 -0
  29. data/lib/ninetails/engine.rb +37 -0
  30. data/lib/ninetails/version.rb +3 -0
  31. data/lib/tasks/ninetails_tasks.rake +8 -0
  32. data/spec/components/element_definition_spec.rb +104 -0
  33. data/spec/components/element_spec.rb +92 -0
  34. data/spec/components/property_spec.rb +34 -0
  35. data/spec/components/property_store_spec.rb +64 -0
  36. data/spec/components/property_type_spec.rb +69 -0
  37. data/spec/components/section_spec.rb +72 -0
  38. data/spec/dummy/Rakefile +6 -0
  39. data/spec/dummy/app/components/element/button.rb +7 -0
  40. data/spec/dummy/app/components/element/button_icon.rb +8 -0
  41. data/spec/dummy/app/components/element/figure.rb +7 -0
  42. data/spec/dummy/app/components/element/meta.rb +7 -0
  43. data/spec/dummy/app/components/element/text.rb +7 -0
  44. data/spec/dummy/app/components/property/icon.rb +8 -0
  45. data/spec/dummy/app/components/property/image.rb +10 -0
  46. data/spec/dummy/app/components/property/link.rb +11 -0
  47. data/spec/dummy/app/components/property/text.rb +8 -0
  48. data/spec/dummy/app/components/section_template/billboard.rb +14 -0
  49. data/spec/dummy/app/components/section_template/document_head.rb +10 -0
  50. data/spec/dummy/bin/bundle +3 -0
  51. data/spec/dummy/bin/rails +4 -0
  52. data/spec/dummy/bin/rake +4 -0
  53. data/spec/dummy/bin/setup +29 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +35 -0
  56. data/spec/dummy/config/boot.rb +5 -0
  57. data/spec/dummy/config/database.yml +12 -0
  58. data/spec/dummy/config/database.yml.travis +4 -0
  59. data/spec/dummy/config/environment.rb +5 -0
  60. data/spec/dummy/config/environments/development.rb +41 -0
  61. data/spec/dummy/config/environments/production.rb +79 -0
  62. data/spec/dummy/config/environments/test.rb +42 -0
  63. data/spec/dummy/config/initializers/assets.rb +11 -0
  64. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  65. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  66. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  67. data/spec/dummy/config/initializers/inflections.rb +16 -0
  68. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  69. data/spec/dummy/config/initializers/session_store.rb +3 -0
  70. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  71. data/spec/dummy/config/locales/en.yml +23 -0
  72. data/spec/dummy/config/routes.rb +3 -0
  73. data/spec/dummy/config/secrets.yml +22 -0
  74. data/spec/dummy/db/schema.rb +53 -0
  75. data/spec/dummy/public/404.html +67 -0
  76. data/spec/dummy/public/422.html +67 -0
  77. data/spec/dummy/public/500.html +66 -0
  78. data/spec/dummy/public/favicon.ico +0 -0
  79. data/spec/factories/page_revisions.rb +8 -0
  80. data/spec/factories/pages.rb +25 -0
  81. data/spec/factories/sections.rb +15 -0
  82. data/spec/models/page_revision_spec.rb +5 -0
  83. data/spec/models/page_spec.rb +26 -0
  84. data/spec/models/section_spec.rb +113 -0
  85. data/spec/rails_helper.rb +60 -0
  86. data/spec/requests/page_revisions_spec.rb +117 -0
  87. data/spec/requests/pages_spec.rb +68 -0
  88. data/spec/requests/section_templates_spec.rb +17 -0
  89. data/spec/spec_helper.rb +98 -0
  90. data/spec/support/factory_girl.rb +5 -0
  91. data/spec/support/request_helpers.rb +9 -0
  92. data/spec/support/shared_examples.rb +34 -0
  93. metadata +378 -0
@@ -0,0 +1,6 @@
1
+ module Ninetails
2
+ class PageRevisionSection < ActiveRecord::Base
3
+ belongs_to :page_revision
4
+ belongs_to :section
5
+ end
6
+ end
@@ -0,0 +1,30 @@
1
+ module Ninetails
2
+ class Section < ActiveRecord::Base
3
+ self.inheritance_column = nil
4
+ has_many :page_revision_sections
5
+ has_many :page_revisions, through: :page_revision_sections
6
+
7
+ def to_builder
8
+ Jbuilder.new do |json|
9
+ json.call(self, :id, :name, :type, :tags, :elements)
10
+ end
11
+ end
12
+
13
+ def template
14
+ @template ||= "SectionTemplate::#{type}".safe_constantize.new
15
+ end
16
+
17
+ def deserialize
18
+ template.elements_instances = []
19
+
20
+ elements.each do |name, element_json|
21
+ element = template.class.find_element name
22
+ element.deserialize element_json
23
+ template.elements_instances << element
24
+ end
25
+
26
+ template
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module Ninetails
2
+ class SectionConfigurationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ Jbuilder.key_format camelize: :lower
2
+
3
+ module StringToApi
4
+ def to_api
5
+ camelize :lower
6
+ end
7
+ end
8
+
9
+ String.send :prepend, StringToApi
@@ -0,0 +1,9 @@
1
+ Ninetails::Engine.routes.draw do
2
+ root 'pages#index'
3
+
4
+ resources :pages, only: :show do
5
+ resources :page_revisions, only: [:create, :show, :index], path: "revisions"
6
+ end
7
+
8
+ resources :section_templates, only: [:show, :index]
9
+ end
@@ -0,0 +1,10 @@
1
+ class CreatePages < ActiveRecord::Migration
2
+ def change
3
+ create_table :ninetails_pages do |t|
4
+ t.references :current_revision, index: true
5
+ t.string :name
6
+ t.string :url
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ class CreatePageRevisions < ActiveRecord::Migration
2
+ def change
3
+ create_table :ninetails_page_revisions do |t|
4
+ t.belongs_to :page
5
+ t.timestamps null: false
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ class CreateSections < ActiveRecord::Migration
2
+ def change
3
+ create_table :ninetails_sections do |t|
4
+ t.string :name
5
+ t.string :type
6
+ t.json :elements
7
+ t.json :tags
8
+ t.timestamps null: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ class CreatePageRevisionSections < ActiveRecord::Migration
2
+ def change
3
+ create_table :ninetails_page_revision_sections do |t|
4
+ t.belongs_to :page_revision, index: true
5
+ t.belongs_to :section, index: true
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddMessageToPageRevisions < ActiveRecord::Migration
2
+ def change
3
+ add_column :ninetails_page_revisions, :message, :string
4
+ end
5
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended that you check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(version: 20151111085201) do
15
+
16
+ # These are extensions that must be enabled in order to support this database
17
+ enable_extension "plpgsql"
18
+
19
+ create_table "ninetails_page_revision_sections", force: :cascade do |t|
20
+ t.integer "page_revision_id"
21
+ t.integer "section_id"
22
+ end
23
+
24
+ add_index "ninetails_page_revision_sections", ["page_revision_id"], name: "index_ninetails_page_revision_sections_on_page_revision_id", using: :btree
25
+ add_index "ninetails_page_revision_sections", ["section_id"], name: "index_ninetails_page_revision_sections_on_section_id", using: :btree
26
+
27
+ create_table "ninetails_page_revisions", force: :cascade do |t|
28
+ t.integer "page_id"
29
+ t.datetime "created_at", null: false
30
+ t.datetime "updated_at", null: false
31
+ t.string "message"
32
+ end
33
+
34
+ create_table "ninetails_pages", force: :cascade do |t|
35
+ t.integer "current_revision_id"
36
+ t.string "name"
37
+ t.string "url"
38
+ t.datetime "created_at", null: false
39
+ t.datetime "updated_at", null: false
40
+ end
41
+
42
+ add_index "ninetails_pages", ["current_revision_id"], name: "index_ninetails_pages_on_current_revision_id", using: :btree
43
+
44
+ create_table "ninetails_sections", force: :cascade do |t|
45
+ t.string "name"
46
+ t.string "type"
47
+ t.json "elements"
48
+ t.json "tags"
49
+ t.datetime "created_at", null: false
50
+ t.datetime "updated_at", null: false
51
+ end
52
+
53
+ end
@@ -0,0 +1,4 @@
1
+ require "ninetails/engine"
2
+
3
+ module Ninetails
4
+ end
@@ -0,0 +1,37 @@
1
+ require "rails"
2
+ require "active_model/railtie"
3
+ require "active_record/railtie"
4
+ require "action_controller/railtie"
5
+
6
+ require "rails-api"
7
+ require "pg"
8
+ require "jbuilder"
9
+ require "hash-pipe"
10
+ require "virtus"
11
+
12
+ begin
13
+ require "pry"
14
+ rescue LoadError
15
+ end
16
+
17
+ module Ninetails
18
+ class Engine < ::Rails::Engine
19
+ isolate_namespace Ninetails
20
+
21
+ config.generators do |g|
22
+ g.test_framework :rspec, fixture: false
23
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
24
+ g.assets false
25
+ g.helper false
26
+ end
27
+
28
+ initializer :append_migrations do |app|
29
+ unless app.root.to_s.match root.to_s
30
+ config.paths["db/migrate"].expanded.each do |expanded_path|
31
+ app.config.paths["db/migrate"] << expanded_path
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Ninetails
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,8 @@
1
+ namespace :ninetails_generate do
2
+
3
+ desc "Generate a blank section as a hash"
4
+ task :section, [:type] => :environment do |task, args|
5
+ puts "SectionTemplate::#{args[:type]}".safe_constantize.new.serialize
6
+ end
7
+
8
+ end
@@ -0,0 +1,104 @@
1
+ require 'rails_helper'
2
+
3
+ class CustomProperty < Ninetails::Property
4
+ property :text, String
5
+ validates :text, presence: true
6
+ end
7
+
8
+ class BasicElement < Ninetails::Element
9
+ property :foo, String
10
+ property :bar, String
11
+ end
12
+
13
+ describe Ninetails::ElementDefinition do
14
+
15
+ let(:text_element) { Ninetails::ElementDefinition.new(:foo, Element::Text, :single) }
16
+
17
+ it "should store the name of the element" do
18
+ expect(text_element.name).to eq :foo
19
+ end
20
+
21
+ it "should store the type of the element" do
22
+ expect(text_element.type).to eq Element::Text
23
+ end
24
+
25
+ it "should store the count of the element" do
26
+ expect(text_element.count).to eq :single
27
+ end
28
+
29
+ describe "adding type structure with singles" do
30
+ let(:hash) { Hash.new }
31
+
32
+ before do
33
+ text_element.add_to_hash hash
34
+ end
35
+
36
+ it "should add the type's structure to a hash" do
37
+ expect(hash[:foo]).to eq text_element.properties_structure
38
+ end
39
+ end
40
+
41
+ describe "adding type structure with multiples" do
42
+ let(:hash) { Hash.new }
43
+
44
+ before do
45
+ text_element.count = :multiple
46
+ text_element.add_to_hash hash
47
+ end
48
+
49
+ it "should add the type's structure to a hash as the single item of an array" do
50
+ expect(hash[:foo]).to eq [text_element.properties_structure]
51
+ end
52
+ end
53
+
54
+ describe "deserialization" do
55
+ describe "single elements" do
56
+
57
+ let(:definition) { Ninetails::ElementDefinition.new(:foo, Element::Text, :single) }
58
+ let(:hash) { {"type"=>"Text", "content"=>{"text"=>"Hello world!"}} }
59
+
60
+ it "should add one element to the elements array" do
61
+ expect {
62
+ definition.deserialize hash
63
+ }.to change { definition.elements.size }.from(0).to(1)
64
+ end
65
+
66
+ it "should pass the hash into the element to be deserialized" do
67
+ element_instance = Element::Text.new
68
+ allow(Element::Text).to receive(:new) { element_instance }
69
+ expect(element_instance).to receive(:deserialize).with(hash)
70
+
71
+ definition.deserialize hash
72
+ end
73
+ end
74
+
75
+ describe "multiple elements" do
76
+
77
+ let(:definition) { Ninetails::ElementDefinition.new(:foo, Element::ButtonIcon, :multiple) }
78
+ let(:hash) do
79
+ [
80
+ {"type"=>"ButtonIcon", "link"=>{"url"=>"/foo", "title"=>"Bar", "text"=>"hello"}, "icon"=>{"name"=>"person"}},
81
+ {"type"=>"ButtonIcon", "link"=>{"url"=>"/bar", "title"=>"Baz", "text"=>"box"}, "icon"=>{"name"=>"tree"}}
82
+ ]
83
+ end
84
+
85
+ it "should duplicate property types in properties_instances to be the correct number of passed in items" do
86
+ expect {
87
+ definition.deserialize hash
88
+ }.to change { definition.elements.size }.from(0).to(2)
89
+ end
90
+
91
+ it "should set the correct property on each instance" do
92
+ first_element_instance = Element::ButtonIcon.new
93
+ second_element_instance = Element::ButtonIcon.new
94
+ allow(Element::ButtonIcon).to receive(:new).and_return(first_element_instance, second_element_instance)
95
+ expect(first_element_instance).to receive(:deserialize).with(hash[0])
96
+ expect(second_element_instance).to receive(:deserialize).with(hash[1])
97
+
98
+ definition.deserialize hash
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+ end
@@ -0,0 +1,92 @@
1
+ require 'rails_helper'
2
+
3
+ class CustomProperty < Ninetails::Property
4
+ property :text, String
5
+ validates :text, presence: true
6
+ end
7
+
8
+ class ExampleElement < Ninetails::Element
9
+ property :foo, String
10
+ property :bar, String
11
+ end
12
+
13
+ class ExampleElement2 < Ninetails::Element
14
+ property :foo, CustomProperty
15
+ property :bar, CustomProperty
16
+ end
17
+
18
+ class ElementForValidating < Ninetails::Element
19
+ property :something, CustomProperty
20
+ end
21
+
22
+ RSpec.describe Ninetails::Element do
23
+
24
+ describe "properties" do
25
+ it "should store properties as an array of Property instances" do
26
+ expect(ExampleElement.properties).to be_a(Array)
27
+ expect(ExampleElement.properties.first).to be_a(Ninetails::PropertyType)
28
+ end
29
+
30
+ it "should store the property names" do
31
+ expect(ExampleElement.properties[0].name).to eq(:foo)
32
+ expect(ExampleElement.properties[1].name).to eq(:bar)
33
+ end
34
+ end
35
+
36
+ describe "generating a reference" do
37
+ it "should use a SecureRandom uuid" do
38
+ allow(SecureRandom).to receive(:uuid) { "ABC" }
39
+ expect(ExampleElement.new.reference).to eq "ABC"
40
+ end
41
+
42
+ it "should be unique for each instance of the Element" do
43
+ expect(ExampleElement.new.reference).not_to eq ExampleElement.new.reference
44
+ end
45
+ end
46
+
47
+ describe "serialization" do
48
+ let(:element) { ExampleElement.new }
49
+ let(:structure) { element.properties_structure }
50
+
51
+ it "should be a hash" do
52
+ expect(structure).to be_a(Hash)
53
+ end
54
+
55
+ it "should contain a unique element id" do
56
+ expect(structure[:reference]).to eq element.reference
57
+ end
58
+
59
+ it "should have a :type key which is the class name" do
60
+ expect(structure[:type]).to eq "ExampleElement"
61
+ end
62
+
63
+ it "should have a key for each property with a blank string as the value" do
64
+ expect(structure[:foo]).to eq ""
65
+ expect(structure[:bar]).to eq ""
66
+ end
67
+ end
68
+
69
+ describe "deserialization" do
70
+ let(:element) { ExampleElement2.new }
71
+ let(:hash) do
72
+ {"type"=>"ExampleElement2", "reference"=>"6ebf107b-c8e5-48f3-bd65-c6d3f8beba90", "foo"=>{"text"=>"hello"}, "bar"=>{"text"=>"world"}}
73
+ end
74
+
75
+ it "should set the serialized_values on each property" do
76
+ expect(element.send(:properties_instances).first).to receive(:serialized_values=).with("text"=>"hello")
77
+ expect(element.send(:properties_instances).last).to receive(:serialized_values=).with("text"=>"world")
78
+ element.deserialize hash
79
+ end
80
+ end
81
+
82
+ describe "validating properties" do
83
+ let(:invalid_element) { ElementForValidating.new }
84
+ let(:valid_element) { ExampleElement.new }
85
+
86
+ it "should check if each property is valid" do
87
+ expect(invalid_element.valid?).to eq false
88
+ expect(valid_element.valid?).to eq true
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,34 @@
1
+ require 'rails_helper'
2
+
3
+ class SecondProperty < Ninetails::Property
4
+ property :finally, String
5
+ end
6
+
7
+ class FirstProperty < Ninetails::Property
8
+ property :first, String
9
+ property :second, SecondProperty
10
+ end
11
+
12
+ RSpec.describe Ninetails::Property do
13
+
14
+ describe "storing properties" do
15
+ it "should store properties" do
16
+ expect(FirstProperty.properties.collect(&:name)).to include(:first)
17
+ expect(FirstProperty.properties.collect(&:name)).to include(:second)
18
+ end
19
+
20
+ it "should store properties with types" do
21
+ prop = FirstProperty.properties.find{|p| p.name == :first}
22
+ expect(prop.type).to eq String
23
+ end
24
+
25
+ it "should represent single-level structure correctly" do
26
+ expect(SecondProperty.serialize).to eq({ finally: "" })
27
+ end
28
+
29
+ it "should represent nested properties with nested structure" do
30
+ expect(FirstProperty.serialize).to eq({ first: "", second: { finally: "" }})
31
+ end
32
+ end
33
+
34
+ end