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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +19 -0
- data/app/components/ninetails/element.rb +55 -0
- data/app/components/ninetails/element_definition.rb +67 -0
- data/app/components/ninetails/property.rb +11 -0
- data/app/components/ninetails/property_store.rb +22 -0
- data/app/components/ninetails/property_type.rb +27 -0
- data/app/components/ninetails/section_template.rb +66 -0
- data/app/controllers/ninetails/application_controller.rb +4 -0
- data/app/controllers/ninetails/page_revisions_controller.rb +31 -0
- data/app/controllers/ninetails/pages_controller.rb +14 -0
- data/app/controllers/ninetails/section_templates_controller.rb +17 -0
- data/app/models/ninetails/page.rb +37 -0
- data/app/models/ninetails/page_revision.rb +28 -0
- data/app/models/ninetails/page_revision_section.rb +6 -0
- data/app/models/ninetails/section.rb +30 -0
- data/config/initializers/error_classes.rb +4 -0
- data/config/initializers/json_stuff.rb +9 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20151006093040_create_pages.rb +10 -0
- data/db/migrate/20151006093559_create_page_revisions.rb +8 -0
- data/db/migrate/20151006093754_create_sections.rb +11 -0
- data/db/migrate/20151006094337_create_page_revision_sections.rb +8 -0
- data/db/migrate/20151111085201_add_message_to_page_revisions.rb +5 -0
- data/db/schema.rb +53 -0
- data/lib/ninetails.rb +4 -0
- data/lib/ninetails/engine.rb +37 -0
- data/lib/ninetails/version.rb +3 -0
- data/lib/tasks/ninetails_tasks.rake +8 -0
- data/spec/components/element_definition_spec.rb +104 -0
- data/spec/components/element_spec.rb +92 -0
- data/spec/components/property_spec.rb +34 -0
- data/spec/components/property_store_spec.rb +64 -0
- data/spec/components/property_type_spec.rb +69 -0
- data/spec/components/section_spec.rb +72 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/components/element/button.rb +7 -0
- data/spec/dummy/app/components/element/button_icon.rb +8 -0
- data/spec/dummy/app/components/element/figure.rb +7 -0
- data/spec/dummy/app/components/element/meta.rb +7 -0
- data/spec/dummy/app/components/element/text.rb +7 -0
- data/spec/dummy/app/components/property/icon.rb +8 -0
- data/spec/dummy/app/components/property/image.rb +10 -0
- data/spec/dummy/app/components/property/link.rb +11 -0
- data/spec/dummy/app/components/property/text.rb +8 -0
- data/spec/dummy/app/components/section_template/billboard.rb +14 -0
- data/spec/dummy/app/components/section_template/document_head.rb +10 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +35 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +12 -0
- data/spec/dummy/config/database.yml.travis +4 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/schema.rb +53 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/page_revisions.rb +8 -0
- data/spec/factories/pages.rb +25 -0
- data/spec/factories/sections.rb +15 -0
- data/spec/models/page_revision_spec.rb +5 -0
- data/spec/models/page_spec.rb +26 -0
- data/spec/models/section_spec.rb +113 -0
- data/spec/rails_helper.rb +60 -0
- data/spec/requests/page_revisions_spec.rb +117 -0
- data/spec/requests/pages_spec.rb +68 -0
- data/spec/requests/section_templates_spec.rb +17 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/support/factory_girl.rb +5 -0
- data/spec/support/request_helpers.rb +9 -0
- data/spec/support/shared_examples.rb +34 -0
- metadata +378 -0
|
@@ -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
|
data/config/routes.rb
ADDED
data/db/schema.rb
ADDED
|
@@ -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
|
data/lib/ninetails.rb
ADDED
|
@@ -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,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
|