orthorings 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Robotic Foo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,86 @@
1
+ # Orthorings
2
+
3
+ Orthorings is a gem to interact with and display content from Orthor.com
4
+
5
+ The 2 main things you can do with Orthorings are:
6
+
7
+ * Define an entire site backed by Orthor with the included DSL (more on this below)
8
+ * Display content on any page in your app using the included helper methods and the Orthorings class
9
+
10
+ ## Installation
11
+
12
+ As a gem (coming soon)
13
+ sudo gem install anthony-orthorings
14
+
15
+ ## Configuration
16
+
17
+ Add a new file (e.g. orthor_config.rb) with the something like the below to your startup directory (e.g. in Rails config/initializers)
18
+
19
+ Orthorings.setup do |config|
20
+ config.account_id = "orthor"
21
+ config.caching :memory, 300, {}
22
+ end
23
+
24
+ You can specify any Moneta cache class as the first argument to config.caching, the second argument is expiry time in seconds. The third is any additional arguments you want passed to the cache class on initialize.
25
+
26
+ ## DSL
27
+
28
+ To make creating a new site backed by Orthor super easy, we've included a DSL that lets you define your site. You don't need to supply any view files, you only need to create your own custom layout to override the included dull one. Currently the DSL plays with Sinatra and Rails - the Sinatra Extension is included in lib/sinatra/orthor.rb, Rails support is provided by engine functionality (seen in app/ and config/).
29
+
30
+ Here is some example usage (more docs to come, this is still a WIP and some aspects will change)
31
+
32
+ Orthor::Site.define do
33
+ cache_for 300 # in seconds, http caching
34
+ layout :application
35
+ keywords "your, site, specific, keywords"
36
+ description "what's your site doing?"
37
+
38
+ page "/", "Home"
39
+ page "/about", "About Us"
40
+
41
+ feed "/blog.rss", "Blog" do
42
+ id "our-blog"
43
+ end
44
+
45
+ category "/writings" do
46
+ keywords "lets, do, some, seo"
47
+ description "a more apt description for this section of the site"
48
+
49
+ page "/writings/why", "Why we write"
50
+ page_path "/writings/:id"
51
+
52
+ feed "/writings.rss" do
53
+ id "our-writings"
54
+ name "Our Writings"
55
+ end
56
+
57
+ category "/writings/announcements" do
58
+ page_path "/writings/announcements/:id"
59
+ end
60
+ end
61
+ end
62
+
63
+ In the above example, every route defined will be handled by the Orthorings gem and rendered through your specified layout, easy! In rails, you will also get named routes based off of the orthor id, e.g.
64
+
65
+ "our-blog" -> our_blog_path
66
+
67
+ Inside of any block in the dsl, you can specify the orthor id, if you don't give an id, the dsl assumes the last fragment of the specified path is the orthor object id, e.g. "/writings" has an orthor id of "writings". Stay tuned for more examples and documentation.
68
+
69
+ ## TODO
70
+
71
+ Things that are being mulled over
72
+
73
+ * Nav helpers (generate navigation based off of the DSL structure)
74
+ * Static Page use needs to be defined more (perhaps define a action to render?)
75
+ * Release as a 0.1 gem
76
+ * ...I'm sure there'll be something else here soon
77
+
78
+ ## Example Usage (as provided by the OrthorHelper module, defined as class methods of Orthorings)
79
+
80
+ orthor_content("content-item-id") or Orthorings.content("id")
81
+ orthor_query("query-id") or Orthorings.query("id")
82
+ orthor_category("category-id") or Orthorings.category("id")
83
+ orthor_feed("feed-id") or Orthorings.feed("id")
84
+
85
+
86
+ Copyright (c) 2009 Robotic Foo, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rake'
2
+ require 'spec'
3
+ require 'spec/rake/spectask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "orthorings"
9
+ gem.summary = "Orthorings is a gem for displaying content from orthor.com"
10
+ gem.email = "anthony@orthor.com"
11
+ gem.homepage = "http://github.com/anthony/orthorings"
12
+ gem.authors = ["Anthony Langhorne"]
13
+ gem.add_dependency("moneta", "0.6.0")
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
19
+
20
+ desc "Run all the Orthorings specs"
21
+ Spec::Rake::SpecTask.new(:ospec) do |t|
22
+ t.spec_opts = ['--options', "spec/spec.opts"]
23
+ t.spec_files = FileList['spec/**/*_spec.rb']
24
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,24 @@
1
+ class Orthor::ContentController < ApplicationController
2
+ def orthor_content
3
+ expires_in Orthor::Site.cache_for, :public => true if Orthor::Site.cache_for > 0
4
+
5
+ if params[:id]
6
+ @orthor_content = Orthorings.content(params[:id])
7
+ resource = Orthor::Site.find_resource_by_page_path(request.request_uri, params[:id])
8
+ else
9
+ resource = Orthor::Site.find_resource_by_path(request.request_uri)
10
+ @orthor_content = resource.content
11
+ end
12
+
13
+ if resource.is_a?(Orthor::Feed)
14
+ render :xml => @orthor_content
15
+ else
16
+ @name = resource.name
17
+ @meta_keywords = resource.keywords
18
+ @meta_description = resource.description
19
+ @orthor_feeds = resource.respond_to?(:feeds) ? resource.feeds : []
20
+
21
+ render :action => "orthor_content", :layout => Orthor::Site.layout.to_s
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ <html>
2
+ <head>
3
+ <title><%= @name %> | Orthorings Default Layout</title>
4
+ <%= keywords.untaint %>
5
+ <%= description.untaint %>
6
+ <%= orthor_feed_tags.untaint %>
7
+ <style>
8
+ * { margin: 0; }
9
+ body { margin: 20px; font-family: helvetica,arial,sans-serif; color: #333; background-color: #fff; }
10
+ div, p { margin: 10px 0px; }
11
+ div.template-explanation { float: right; margin-left: 20px; }
12
+ div.template-explanation p { margin: 0; }
13
+ div.template-explanation pre { padding: 5px; background-color: #ccc; }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <div class="template-explanation">
18
+ <p>Tell Orthor about your own template:</p>
19
+ <pre>
20
+ Orthor::Site.define do
21
+ layout :application
22
+ ...
23
+ end
24
+ </pre>
25
+ </div>
26
+
27
+ <div class="content">
28
+ <%= yield %>
29
+ </div>
30
+ </body>
31
+ </html>
@@ -0,0 +1 @@
1
+ <div class="orthor_content"><%= @orthor_content %></div>
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ Orthor::Site.resources.each do |resource|
3
+ if resource.is_a?(::Orthor::Category) && !resource.page_path.nil?
4
+ map.send("#{resource.path_name}_pages", resource.page_path, :controller => "orthor/content", :action => "orthor_content")
5
+ end
6
+
7
+ map.send(resource.path_name, resource.path, :controller => "orthor/content", :action => "orthor_content")
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ class Module
2
+ def dsl_accessor(*symbols)
3
+ symbols.each do |sym|
4
+ class_eval %{
5
+ def #{sym}(*val)
6
+ if val.empty?
7
+ @#{sym}
8
+ else
9
+ @#{sym} = val.size == 1 ? val[0] : val
10
+ end
11
+ end
12
+ }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module Orthor
2
+ class Category < Orthor::Object
3
+ include Orthor::Collections
4
+
5
+ dsl_accessor :page_path
6
+
7
+ def initialize(path, category_name, &block)
8
+ @feeds, @categories, @pages, @static_pages = [], [], [], []
9
+ super path, category_name, &block
10
+ end
11
+
12
+ def fetch_content
13
+ Orthorings.category(@id)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module Orthor
2
+ module Collections
3
+ attr_accessor :feeds, :categories, :pages, :static_pages
4
+
5
+ def feed(path, name = nil, &block)
6
+ feeds << Orthor::Feed.new(path, name, &block)
7
+ end
8
+
9
+ def category(path, name = nil, &block)
10
+ categories << Orthor::Category.new(path, name, &block)
11
+ end
12
+
13
+ def page(path, name = nil, &block)
14
+ pages << Orthor::Page.new(path, name, &block)
15
+ end
16
+
17
+ def static_page(path, name = nil, &block)
18
+ static_pages << Orthor::StaticPage.new(path, name, &block)
19
+ end
20
+
21
+ def resources
22
+ return [] unless @feeds && @pages && @static_pages && @categories
23
+ (@feeds + @pages + @static_pages + @categories + @categories.collect { |category| category.resources }).flatten
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ module Orthor
2
+ class Feed < Orthor::Object
3
+ def fetch_content
4
+ Orthorings.feed(@id)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module Orthor
2
+ module HttpCaching
3
+ def cache_for(time=nil)
4
+ if time
5
+ @cache_for = time
6
+ else
7
+ if @cache_for.nil?
8
+ Orthor::Site.cache_for
9
+ else
10
+ @cache_for
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Orthor
2
+ module MetaData
3
+ [ :keywords, :description ].each do |sym|
4
+ class_eval %{
5
+ def #{sym}(*val)
6
+ if val.empty?
7
+ if @#{sym}.nil?
8
+ Orthor::Site.#{sym}
9
+ else
10
+ @#{sym}
11
+ end
12
+ else
13
+ @#{sym} = val.size == 1 ? val[0] : val
14
+ end
15
+ end
16
+ }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Orthor
2
+ class Object
3
+ include Orthor::HttpCaching
4
+ include Orthor::MetaData
5
+
6
+ attr_accessor :path
7
+ dsl_accessor :name, :id
8
+
9
+ def initialize(path, object_name = nil, &block)
10
+ @path = path
11
+ raise "Path required" unless @path
12
+ @id = @path.split("/").last # by default, can be passed in
13
+ name(object_name)
14
+
15
+ instance_eval &block if block_given?
16
+ end
17
+
18
+ def content
19
+ fetch_content
20
+ end
21
+
22
+ def path_name
23
+ @id.gsub(/[^a-z0-9]+/i, '_')
24
+ end
25
+
26
+ def fetch_content
27
+ raise "This must be overriden in any Orthor::Object subclass"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ module Orthor
2
+ class Page < Orthor::Object
3
+ def fetch_content
4
+ Orthorings.content(@id)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module Orthor
2
+ class Site
3
+ class << self
4
+ include Orthor::Collections
5
+ include Orthor::HttpCaching
6
+ include Orthor::MetaData
7
+
8
+ dsl_accessor :layout
9
+
10
+ def define(&block)
11
+ @feeds, @categories, @pages, @static_pages = [], [], [], []
12
+ layout(:orthor_layout)
13
+ cache_for 0
14
+ keywords ""
15
+ description ""
16
+
17
+ class_eval &block
18
+ end
19
+
20
+ def find_resource_by_path(path)
21
+ Orthor::Site.resources.detect { |r| r.path == path }
22
+ end
23
+
24
+ def find_resource_by_page_path(path, id)
25
+ Orthor::Site.resources.detect { |r| r.is_a?(Orthor::Category) && r.page_path.gsub(":id", id) == path }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module Orthor
2
+ class StaticPage
3
+ include Orthor::HttpCaching
4
+ include Orthor::MetaData
5
+
6
+ attr_accessor :path
7
+ dsl_accessor :name
8
+
9
+ def initialize(path, page_name, &block)
10
+ @path = path
11
+ name(page_name)
12
+ raise "Path required" unless @path
13
+
14
+ instance_eval &block if block_given?
15
+ end
16
+ end
17
+ end
data/lib/orthor.rb ADDED
@@ -0,0 +1,5 @@
1
+ Dir[File.dirname(__FILE__) + '/core_ext/*.rb'].each { |e| require e }
2
+ %w(orthor/http_caching orthor/meta_data orthor/collections orthor/site orthor/object orthor/category orthor/feed orthor/page orthor/static_page).each { |r| require File.join(File.dirname(__FILE__), r) }
3
+ require File.join(File.dirname(__FILE__), 'orthorings')
4
+ require File.join(File.dirname(__FILE__), 'orthor_helper')
5
+ require 'moneta'
@@ -0,0 +1,35 @@
1
+ module OrthorHelper
2
+ def orthor_content(id)
3
+ return Orthorings.content(id)
4
+ end
5
+
6
+ def orthor_category(id)
7
+ return Orthorings.category(id)
8
+ end
9
+
10
+ def orthor_query(id)
11
+ return Orthorings.query(id)
12
+ end
13
+
14
+ def orthor_feed(id)
15
+ return Orthorings.feed(id)
16
+ end
17
+
18
+ def keywords
19
+ content = @meta_keywords || Orthor::Site.keywords
20
+ return if content.nil? || content.empty?
21
+ %Q(<meta content="#{content}" name="keywords" />)
22
+ end
23
+
24
+ def description
25
+ content = @meta_description || Orthor::Site.description
26
+ return if content.nil? || content.empty?
27
+ %Q(<meta content="#{content}" name="description" />)
28
+ end
29
+
30
+ def orthor_feed_tags
31
+ (Orthor::Site.feeds + (@orthor_feeds.nil? ? [] : @orthor_feeds)).inject("") do |html, feed|
32
+ html += %Q(<link rel="alternate" type="application/rss+xml" title="#{feed.name || "RSS"}" href="#{feed.path}" />)
33
+ end
34
+ end
35
+ end
data/lib/orthorings.rb ADDED
@@ -0,0 +1,69 @@
1
+ class Orthorings
2
+ require 'net/http'
3
+
4
+ class << self
5
+ attr_accessor :account_id, :cache, :cache_expiry
6
+
7
+ def content(id)
8
+ get_content(id, "http://content.orthor.com/#{@account_id}/content_items/#{id}.html")
9
+ end
10
+
11
+ def query(id)
12
+ get_content(id, "http://content.orthor.com/#{@account_id}/queries/#{id}.html")
13
+ end
14
+
15
+ def category(id)
16
+ get_content(id, "http://content.orthor.com/#{@account_id}/categories/#{id}.html")
17
+ end
18
+
19
+ def feed(id)
20
+ get_content(id, "http://content.orthor.com/#{@account_id}/feeds/#{id}.rss")
21
+ end
22
+
23
+ def caching(file, expiry = 300, options = {})
24
+ klass = file.to_s.split('_').collect { |e| e.capitalize }.join
25
+ Moneta.autoload(klass.to_sym, "moneta/#{file}")
26
+ @cache = Moneta.const_get(klass).new(options)
27
+ @cache_expiry = expiry
28
+ end
29
+
30
+ def setup
31
+ @cache = false # default
32
+
33
+ yield self
34
+
35
+ raise "No orthor account id given... Please add one to your Orthorings setup block" if Orthorings.account_id.nil?
36
+ ActionView::Base.send(:include, OrthorHelper) if defined?(ActionView::Base) && !ActionView::Base.include?(OrthorHelper)
37
+ end
38
+
39
+ private
40
+ def get_content(id, url)
41
+ if @cache
42
+ get_cached_content(id, url)
43
+ else
44
+ get_response(url)
45
+ end
46
+ end
47
+
48
+ def get_cached_content(id, url)
49
+ content = @cache[id]
50
+ if content.empty?
51
+ content = get_response(url)
52
+ cache_content(id, content)
53
+ end
54
+ content
55
+ end
56
+
57
+ def cache_content(id, content)
58
+ @cache.store(id, content, :expires_in => @cache_expiry) unless content.empty?
59
+ end
60
+
61
+ def get_response(uri)
62
+ r = Net::HTTP.get_response(URI.parse(uri))
63
+ return (r.code.to_i == 200) ? r.body : ""
64
+ rescue
65
+ return ""
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,82 @@
1
+ require 'sinatra/base'
2
+ require 'erb'
3
+
4
+ module Sinatra
5
+ module OrthorRequestHelpers
6
+ def handle_request(resource)
7
+ @name = resource.name
8
+ @meta_keywords = resource.keywords
9
+ @meta_description = resource.description
10
+ @orthor_feeds = resource.respond_to?(:feeds) ? resource.feeds : []
11
+ headers['Cache-Control'] = "public, max-age=#{resource.cache_for}"
12
+ erb :orthor_content, { :layout => ::Orthor::Site.layout }
13
+ end
14
+ end
15
+
16
+ module Orthor
17
+ def self.registered(app)
18
+ app.helpers ::OrthorHelper
19
+ app.helpers Sinatra::OrthorRequestHelpers
20
+
21
+ ::Orthor::Site.resources.each do |resource|
22
+ if resource.is_a?(::Orthor::Category) && !resource.page_path.nil?
23
+ get resource.page_path do |id|
24
+ @orthor_content = orthor_content(id)
25
+ handle_request(resource)
26
+ end
27
+ end
28
+
29
+ if resource.is_a?(::Orthor::Feed)
30
+ get resource.path do
31
+ headers['Cache-Control'] = "public, max-age=#{resource.cache_for}"
32
+ resource.content
33
+ end
34
+ else
35
+ get resource.path do
36
+ @orthor_content = resource.content
37
+ handle_request(resource)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ register Orthor
44
+ end
45
+
46
+ template :orthor_layout do
47
+ %{<html>
48
+ <head>
49
+ <title><%= @name %> | Orthorings Default Layout</title>
50
+ <%= keywords %>
51
+ <%= description %>
52
+ <%= orthor_feed_tags %>
53
+ <style>
54
+ * { margin: 0; }
55
+ body { margin: 20px; font-family: helvetica,arial,sans-serif; color: #333; background-color: #fff; }
56
+ div, p { margin: 10px 0px; }
57
+ div.template-explanation { float: right; margin-left: 20px; }
58
+ div.template-explanation p { margin: 0; }
59
+ div.template-explanation pre { padding: 5px; background-color: #ccc; }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <div class="template-explanation">
64
+ <p>Tell Orthor about your own template:</p>
65
+ <pre>
66
+ Orthor::Site.define do
67
+ layout :application
68
+ ...
69
+ end
70
+ </pre>
71
+ </div>
72
+
73
+ <div class="content">
74
+ <%= yield %>
75
+ </div>
76
+ </body>
77
+ </html>}
78
+ end
79
+
80
+ template :orthor_content do
81
+ '<div class="orthor_content"><%= @orthor_content %></div>'
82
+ end
@@ -0,0 +1,77 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{orthorings}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Anthony Langhorne"]
12
+ s.date = %q{2009-09-28}
13
+ s.email = %q{anthony@orthor.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.markdown"
17
+ ]
18
+ s.files = [
19
+ "LICENSE",
20
+ "README.markdown",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "app/controllers/orthor/content_controller.rb",
24
+ "app/views/layouts/orthor_layout.html.erb",
25
+ "app/views/orthor/content/orthor_content.html.erb",
26
+ "config/routes.rb",
27
+ "lib/core_ext/dsl_accessor.rb",
28
+ "lib/orthor.rb",
29
+ "lib/orthor/category.rb",
30
+ "lib/orthor/collections.rb",
31
+ "lib/orthor/feed.rb",
32
+ "lib/orthor/http_caching.rb",
33
+ "lib/orthor/meta_data.rb",
34
+ "lib/orthor/object.rb",
35
+ "lib/orthor/page.rb",
36
+ "lib/orthor/site.rb",
37
+ "lib/orthor/static_page.rb",
38
+ "lib/orthor_helper.rb",
39
+ "lib/orthorings.rb",
40
+ "lib/sinatra/orthor.rb",
41
+ "orthorings.gemspec",
42
+ "spec/orthor/dsl_spec.rb",
43
+ "spec/orthor_helper_spec.rb",
44
+ "spec/orthorings_spec.rb",
45
+ "spec/resources/homepage.html",
46
+ "spec/resources/latest-news-items.html",
47
+ "spec/resources/news.html",
48
+ "spec/sinatra/orthor_spec.rb",
49
+ "spec/spec.opts",
50
+ "spec/spec_helper.rb"
51
+ ]
52
+ s.homepage = %q{http://github.com/anthony/orthorings}
53
+ s.rdoc_options = ["--charset=UTF-8"]
54
+ s.require_paths = ["lib"]
55
+ s.rubygems_version = %q{1.3.5}
56
+ s.summary = %q{Orthorings is a gem for displaying content from orthor.com}
57
+ s.test_files = [
58
+ "spec/orthor/dsl_spec.rb",
59
+ "spec/orthor_helper_spec.rb",
60
+ "spec/orthorings_spec.rb",
61
+ "spec/sinatra/orthor_spec.rb",
62
+ "spec/spec_helper.rb"
63
+ ]
64
+
65
+ if s.respond_to? :specification_version then
66
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
67
+ s.specification_version = 3
68
+
69
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
70
+ s.add_runtime_dependency(%q<moneta>, ["= 0.6.0"])
71
+ else
72
+ s.add_dependency(%q<moneta>, ["= 0.6.0"])
73
+ end
74
+ else
75
+ s.add_dependency(%q<moneta>, ["= 0.6.0"])
76
+ end
77
+ end
@@ -0,0 +1,362 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Orthor::Site, "orthor site dsl" do
4
+ describe "base attributes" do
5
+ before(:all) do
6
+ Orthor::Site.define do
7
+ cache_for 7200
8
+ layout :application
9
+ end
10
+ end
11
+
12
+ it 'should allow a http cache time to be set' do
13
+ Orthor::Site.cache_for.should == 7200
14
+ end
15
+
16
+ it 'should allow setting of a layout' do
17
+ Orthor::Site.layout.should == :application
18
+ end
19
+
20
+ [ :categories, :feeds, :pages, :static_pages ].each do |collection|
21
+ it "should have a #{collection} array" do
22
+ Orthor::Site.send(collection).should == []
23
+ end
24
+ end
25
+ end
26
+
27
+ describe "default values" do
28
+ before(:all) do
29
+ Orthor::Site.define do
30
+ end
31
+ end
32
+
33
+ it 'should set a default cache for time' do
34
+ Orthor::Site.cache_for.should == 0
35
+ end
36
+
37
+ it 'should set a default layout' do
38
+ Orthor::Site.layout.should == :orthor_layout
39
+ end
40
+ end
41
+
42
+ describe "children" do
43
+ before(:all) do
44
+ Orthor::Site.define do
45
+ page "/", "Home"
46
+
47
+ category "/writings" do
48
+ page "/writings/why", "Why we write"
49
+
50
+ feed "/writings.rss" do
51
+ id "our-writings"
52
+ name "Our Writings"
53
+ end
54
+
55
+ category "/writings/announcements" do
56
+ page "/3rd-level", "3rd level"
57
+ end
58
+ end
59
+ end
60
+ @category_1 = Orthor::Site.categories.first
61
+ @category_2 = @category_1.categories.first
62
+ @feed = @category_1.feeds.first
63
+ @page_1 = Orthor::Site.pages.first
64
+ @page_2 = @category_1.pages.first
65
+ @page_3 = @category_2.pages.first
66
+ end
67
+
68
+ it 'should return all resources defined in the dsl' do
69
+ Orthor::Site.resources.length.should == 6
70
+
71
+ Orthor::Site.resources.should include(@category_1)
72
+ Orthor::Site.resources.should include(@category_2)
73
+ Orthor::Site.resources.should include(@feed)
74
+ Orthor::Site.resources.should include(@page_1)
75
+ Orthor::Site.resources.should include(@page_2)
76
+ Orthor::Site.resources.should include(@page_3)
77
+ end
78
+ end
79
+
80
+ describe "categories" do
81
+ before(:all) do
82
+ Orthor::Site.define do
83
+ category "/writings" do
84
+ page_path "/writings/:id"
85
+ name "Writings"
86
+ id "blog-writings"
87
+ cache_for 900
88
+
89
+ page "/writings/why" do
90
+ name "Why we write"
91
+ end
92
+
93
+ feed "/writings.rss" do
94
+ id "our-writings"
95
+ name "Our Writings"
96
+ end
97
+
98
+ category "/writings/announcements" do
99
+ page_path "/writings/announcements/:id"
100
+ name "Announcements"
101
+ cache_for 300
102
+
103
+ page "/3rd-level" do
104
+ name "3rd level"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ @category = Orthor::Site.categories.first
110
+ @category_2 = Orthor::Site.categories.first.categories.first
111
+ end
112
+
113
+ it "should have one categories" do
114
+ Orthor::Site.categories.length.should == 1
115
+ @category.name.should == "Writings"
116
+ end
117
+
118
+ it "should support categories in categories" do
119
+ @category.categories.length.should == 1
120
+ @category_2.name.should == "Announcements"
121
+ end
122
+
123
+ it "should support pages in categories" do
124
+ @category.pages.length.should == 1
125
+ @category.pages.first.name.should == "Why we write"
126
+ end
127
+
128
+ it "id should be inferred from the path if none is given" do
129
+ @category_2.id.should == "announcements"
130
+ end
131
+
132
+ it "should support pages in categories in categories" do
133
+ @category_2.pages.length.should == 1
134
+ @category_2.pages.first.name.should == "3rd level"
135
+ end
136
+
137
+ it "should support a child page path attribute" do
138
+ @category.page_path.should == "/writings/:id"
139
+ end
140
+
141
+ it "should support setting the orthor id" do
142
+ @category.id.should == "blog-writings"
143
+ end
144
+
145
+ it "should support specifying http cache time" do
146
+ @category.cache_for.should == 900
147
+ @category_2.cache_for.should == 300
148
+ end
149
+
150
+ it "should support feeds specific to categories" do
151
+ @category.feeds.length.should == 1
152
+ @category.feeds.first.name.should == "Our Writings"
153
+ end
154
+ end
155
+
156
+ describe "pages" do
157
+ before(:all) do
158
+ Orthor::Site.define do
159
+ page "/about-us", "About us"
160
+ page "/contact", "Contact Us"
161
+ page "/" do
162
+ name "Home"
163
+ cache_for 3600
164
+ id "homepage"
165
+ end
166
+ end
167
+ @page_1 = Orthor::Site.pages[0]
168
+ @page_2 = Orthor::Site.pages[1]
169
+ @page_3 = Orthor::Site.pages[2]
170
+ end
171
+
172
+ it 'should support multiple pages' do
173
+ Orthor::Site.pages.length.should == 3
174
+ end
175
+
176
+ it 'should support specifying pages with or without a block' do
177
+ @page_1.name.should == "About us"
178
+ @page_1.path.should == "/about-us"
179
+
180
+ @page_3.name.should == "Home"
181
+ @page_3.path.should == "/"
182
+ end
183
+
184
+ it "id should be inferred from the path if none is given" do
185
+ @page_1.id.should == "about-us"
186
+ @page_2.id.should == "contact"
187
+ end
188
+
189
+ it 'should allow setting specific http cache lengths' do
190
+ @page_1.cache_for.should == 0 # sites default
191
+ @page_3.cache_for.should == 3600
192
+ end
193
+
194
+ it 'should support setting an id' do
195
+ @page_3.id.should == "homepage"
196
+ end
197
+ end
198
+
199
+ describe "feeds" do
200
+ before(:all) do
201
+ Orthor::Site.define do
202
+ feed "/blog.rss" do
203
+ id "orthor-blog"
204
+ name "Our blag"
205
+ end
206
+
207
+ feed "/bookmarks.rss" do
208
+ name "Our bookmarks"
209
+ end
210
+ end
211
+
212
+ @feed_1 = Orthor::Site.feeds.first
213
+ @feed_2 = Orthor::Site.feeds.last
214
+ end
215
+
216
+ it "should support multiple feeds" do
217
+ Orthor::Site.feeds.length.should == 2
218
+ end
219
+
220
+ it "should support setting a feeds id" do
221
+ @feed_1.id.should == "orthor-blog"
222
+ end
223
+
224
+ it "id should be inferred from the path if none is given" do
225
+ @feed_2.id.should == "bookmarks.rss"
226
+ end
227
+
228
+ it 'should support setting a feeds name' do
229
+ @feed_1.name.should == "Our blag"
230
+ @feed_2.name.should == "Our bookmarks"
231
+ end
232
+
233
+ it 'should store the given path' do
234
+ @feed_1.path.should == "/blog.rss"
235
+ @feed_2.path.should == "/bookmarks.rss"
236
+ end
237
+ end
238
+
239
+ describe 'static pages' do
240
+ before(:all) do
241
+ Orthor::Site.define do
242
+ static_page "/about-us", "About us"
243
+ static_page "/contact", "Contact Us"
244
+ static_page "/" do
245
+ name "Home"
246
+ cache_for 3600
247
+ end
248
+ end
249
+ @page_1 = Orthor::Site.static_pages[0]
250
+ @page_2 = Orthor::Site.static_pages[1]
251
+ @page_3 = Orthor::Site.static_pages[2]
252
+ end
253
+
254
+ it 'should support multiple pages' do
255
+ Orthor::Site.static_pages.length.should == 3
256
+ end
257
+
258
+ it 'should support specifying pages with or without a block' do
259
+ @page_1.name.should == "About us"
260
+ @page_1.path.should == "/about-us"
261
+
262
+ @page_3.name.should == "Home"
263
+ @page_3.path.should == "/"
264
+ end
265
+
266
+ it 'should allow setting specific http cache lengths' do
267
+ @page_3.cache_for.should == 3600
268
+ end
269
+ end
270
+
271
+ describe 'finding by path' do
272
+ before(:all) do
273
+ Orthor::Site.define do
274
+ page "/", "Home"
275
+
276
+ category "/writings" do
277
+ page_path "/writings/:id"
278
+ page "/writings/why", "Why we write"
279
+
280
+ feed "/writings.rss" do
281
+ id "our-writings"
282
+ name "Our Writings"
283
+ end
284
+
285
+ category "/writings/announcements" do
286
+ page_path "/writings/announcements/:id"
287
+ page "/3rd-level", "3rd level"
288
+ end
289
+ end
290
+ end
291
+ @home = Orthor::Site.pages.first
292
+ @writings = Orthor::Site.categories.first
293
+ @feed = @writings.feeds.first
294
+ @announcements = @writings.categories.first
295
+ @third_level = @announcements.pages.first
296
+ end
297
+
298
+ it 'should be able to find pages by path' do
299
+ Orthor::Site.find_resource_by_path("/").should == @home
300
+ Orthor::Site.find_resource_by_path("/3rd-level").should == @third_level
301
+ end
302
+
303
+ it 'should be able to find feeds by path' do
304
+ Orthor::Site.find_resource_by_path("/writings.rss").should == @feed
305
+ end
306
+
307
+ it 'should be able to find categories by path' do
308
+ Orthor::Site.find_resource_by_path("/writings").should == @writings
309
+ Orthor::Site.find_resource_by_path("/writings/announcements").should == @announcements
310
+ end
311
+
312
+ it 'should find the parent category by page path' do
313
+ Orthor::Site.find_resource_by_page_path("/writings/announcements/first-entry", "first-entry").should == @announcements
314
+ Orthor::Site.find_resource_by_page_path("/writings/first-writing", "first-writing").should == @writings
315
+ end
316
+ end
317
+
318
+ describe 'meta data' do
319
+ before(:all) do
320
+ Orthor::Site.define do
321
+ keywords "asdf, bob, frank"
322
+ description "a site being defined"
323
+
324
+ page "/features", "Features" do
325
+ keywords "features"
326
+ description "feature page description"
327
+ end
328
+
329
+ category "/news", "News" do
330
+ keywords "category"
331
+ description "category description"
332
+ end
333
+
334
+ page "/ihavenothing"
335
+ end
336
+
337
+ @page_with_meta = Orthor::Site.pages.first
338
+ @category = Orthor::Site.categories.first
339
+ @page_no_meta = Orthor::Site.pages.last
340
+ end
341
+
342
+ it 'should allow setting of meta data at a site level' do
343
+ Orthor::Site.keywords.should == "asdf, bob, frank"
344
+ Orthor::Site.description.should == "a site being defined"
345
+ end
346
+
347
+ it 'should allow setting of meta data at a page level' do
348
+ @page_with_meta.keywords.should == "features"
349
+ @page_with_meta.description.should == "feature page description"
350
+ end
351
+
352
+ it 'should allow setting of meta data at a category level' do
353
+ @category.keywords.should == "category"
354
+ @category.description.should == "category description"
355
+ end
356
+
357
+ it 'should default to sites meta data if none given' do
358
+ @page_no_meta.keywords.should == "asdf, bob, frank"
359
+ @page_no_meta.description.should == "a site being defined"
360
+ end
361
+ end
362
+ end
@@ -0,0 +1,61 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe OrthorHelper do
4
+ include OrthorHelper
5
+
6
+ describe 'default meta tag handling' do
7
+ before(:all) do
8
+ Orthor::Site.define do
9
+ keywords "site keywords"
10
+ description "site description"
11
+ end
12
+ end
13
+
14
+ it 'should return a meta tag for keywords' do
15
+ keywords.should == %Q(<meta content="site keywords" name="keywords" />)
16
+ end
17
+
18
+ it 'should return a meta tag for description' do
19
+ description.should == %Q(<meta content="site description" name="description" />)
20
+ end
21
+
22
+ it 'should use @meta_keywords when exists' do
23
+ @meta_keywords = "asdf"
24
+ keywords.should == %Q(<meta content="asdf" name="keywords" />)
25
+ end
26
+
27
+ it 'should use @meta_description when exists' do
28
+ @meta_description = "asdf"
29
+ description.should == %Q(<meta content="asdf" name="description" />)
30
+ end
31
+ end
32
+
33
+ describe 'feed links' do
34
+ before(:all) do
35
+ Orthor::Site.define do
36
+ keywords "site keywords"
37
+ description "site description"
38
+
39
+ feed "/news.rss", "News"
40
+
41
+ category "/writings" do
42
+ feed "/writings.rss" do
43
+ id "our-writings"
44
+ name "Writings"
45
+ end
46
+ end
47
+ end
48
+ @feed_2 = Orthor::Site.categories.first.feeds.first
49
+ @feed = Orthor::Site.feeds.first
50
+ end
51
+
52
+ it 'should generate a list of link tags rss feeds' do
53
+ orthor_feed_tags.should == %Q(<link rel="alternate" type="application/rss+xml" title="News" href="/news.rss" />)
54
+ end
55
+
56
+ it 'should generate additional links when @orthor_feeds is set' do
57
+ @orthor_feeds = [ @feed_2 ]
58
+ orthor_feed_tags.should == %Q(<link rel="alternate" type="application/rss+xml" title="News" href="/news.rss" /><link rel="alternate" type="application/rss+xml" title="Writings" href="/writings.rss" />)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,56 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'moneta/memory'
3
+
4
+ describe Orthorings, "fetching content" do
5
+ before(:all) do
6
+ FakeWeb.register_uri(:get, "http://content.orthor.com/orthor/content_items/homepage.html", :body => File.join(SPEC_DIR, 'resources', 'homepage.html'))
7
+ FakeWeb.register_uri(:get, "http://content.orthor.com/orthor/queries/latest-news-items.html", :body => File.join(SPEC_DIR, 'resources', 'latest-news-items.html'))
8
+ FakeWeb.register_uri(:get, "http://content.orthor.com/orthor/categories/news.html", :body => File.join(SPEC_DIR, 'resources', 'news.html'))
9
+
10
+ Orthorings.setup do |config|
11
+ config.account_id = "orthor"
12
+ end
13
+ end
14
+
15
+ it 'should be able to fetch content items' do
16
+ Orthorings.content("homepage").include?("<h2>So what is Orthor anyway?</h2>").should be_true
17
+ end
18
+
19
+ it 'should be able to fetch queries' do
20
+ Orthorings.query("latest-news-items").include?("Comparisons to traditional Content Management").should be_true
21
+ end
22
+
23
+ it 'should be able to fetch categories' do
24
+ Orthorings.category("news").include?("<h2>Orthor's Features </h2>").should be_true
25
+ end
26
+
27
+ it 'should not cache content if no config is given' do
28
+ Orthorings.cache.should == false
29
+ end
30
+ end
31
+
32
+ describe Orthorings, "caching content" do
33
+ before(:all) do
34
+ FakeWeb.register_uri(:get, "http://content.orthor.com/orthor/content_items/homepage.html", :body => File.join(SPEC_DIR, 'resources', 'homepage.html'))
35
+ FakeWeb.register_uri(:get, "http://content.orthor.com/orthor/queries/latest-news-items.html", :body => File.join(SPEC_DIR, 'resources', 'latest-news-items.html'))
36
+ FakeWeb.register_uri(:get, "http://content.orthor.com/orthor/categories/news.html", :body => File.join(SPEC_DIR, 'resources', 'news.html'))
37
+
38
+ Orthorings.setup do |config|
39
+ config.account_id = "orthor"
40
+ config.caching :memory
41
+ end
42
+ end
43
+
44
+ it 'expiry should default to 300 seconds if none is given' do
45
+ Orthorings.cache_expiry.should == 300
46
+ end
47
+
48
+ it 'should add content to the cache after requests' do
49
+ Orthorings.cache["homepage"].should be_empty
50
+ Orthorings.content("homepage")
51
+ Orthorings.cache["homepage"].should_not be_empty
52
+
53
+ Orthorings.should_not_receive(:cache_content)
54
+ Orthorings.content("homepage")
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ <h2>So what is Orthor anyway?</h2>
2
+ <p>Orthor is a web content management system that requires no additional software installation!. You can get up and running in a matter of minutes. Orthor is built to be reliable, priced to be affordable and above all - it's real easy to use. Designed from the ground up to fit the Software as a Service model, Orthor gives you all the tools you need to manage content on your new or existing site.</p>
3
+ <p>With Orthor you dont have to install complicated software or buy any hardware</p>
4
+ <p>&nbsp;</p>
5
+ <h2>Comparisons to Traditional Content Management</h2>
6
+ <p>Jarrod has recently written a great overview of how Orthor contrasts to existing Content management systems, its a great <a href="http://blog.orthor.com/2009/06/15/comparisons-to-traditional-content-management/" title="Orthor - Simple content management">overview</a>.</p>
7
+ <p>With all the growth and change in this market many CMS products still follow the principle of &lsquo;complete ownership&lsquo;. This principle mandates that the CMS will control the life cycle of each page, asset, and content item presented on your site. This results in you having to define content types, presentation templates, navigational structures inside of the CMS. You may also need to force your development staff (or development agency) to build custom site functions using technologies that the CMS vendor tells you to. For me that is way too much control to hand over to someone else (but then again maybe Im just a control freak).</p>
8
+
9
+ <p>It is on this point of &lsquo;complete control&lsquo; that Orthor strays from the pack. We do not attempt to own your site and we certainly don&rsquo;t dictate how you go about building your custom functions. Orthor focuses completely on only managing the content that is presented on your site. To put it more simply, you control the web pages and Orthor controls the content that is presented on those pages.</p>
10
+ <p>Read the whole post <a href="http://blog.orthor.com/2009/06/15/comparisons-to-traditional-content-management/" title="Orthor - Simple content management">here</a></p>
11
+ <p>&nbsp;</p>
@@ -0,0 +1 @@
1
+ <div><a href="http://blog.orthor.com/2009/06/15/comparisons-to-traditional-content-management/" title="Comparisons to traditional Content Management">Comparisons to traditional Content Management</a></div><div><a href="http://blog.orthor.com/2009/06/21/orthors-features/" title="Orthor's Features">Orthor's Features</a></div><div><a href="http://blog.orthor.com/2009/06/29/we-are-live/" title="Orthor is live and accepting signups!">Orthor is live and accepting signups!</a></div>
@@ -0,0 +1,5 @@
1
+ <h2>Orthor's Features </h2><div><p>So today I thought we should take a quick tour of Orthor&rsquo;s features. In a series of upcoming posts I will explain each feature in more detail and present ideas and suggestions how each can be used to benefit you. If you have read previous posts you&rsquo;ll have picked up that Orthor is built with a single purpose, &lsquo;<em>to manage your content and not your site</em>&lsquo;. That means you wont find any fancy features to alter site navigation, add sub-sites, build custom forms etc. All Orthor&rsquo;s features are specific to creating, managing, publishing and retrieving content.</p>
2
+ <p>Read the whole article <a href="http://blog.orthor.com/2009/06/21/orthors-features/">here</a>.</p>
3
+ </div><h2>Orthor is live and accepting signups! </h2><div><p>Just a quick note to announce that we are now officially live. All our plans are now available <a href="https://orthor.com/pricing_signup" title="Orthor subscription plans">here</a>. This marks a small milestone for us in getting Orthor up and running. We can now move onto implementing some more great features that we have on our road map.</p> </div><h2>Comparisons to traditional Content Management </h2><div><p>Over the last five plus years it seems that a new CMS has popped into the scene on an almost daily basis. The CMS market is completely saturated with products and services, so much so that it is quite an ordeal to select one. I firmly believe that a saturated market is a good thing for the consumer, just look at the amount of choice you have. Regardless of what web technology underpins your organization, what your budget constraints are, what your in-house skills are there is bound to be a CMS out there for you</p>
4
+
5
+ <p>Read the whole article <a href="http://blog.orthor.com/2009/06/15/comparisons-to-traditional-content-management/">here</a>.</p> </div>
File without changes
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'fake_web'
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'orthor')
5
+
6
+ SPEC_DIR = File.dirname(__FILE__) unless defined? SPEC_DIR
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: orthorings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Anthony Langhorne
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-28 00:00:00 +10:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: moneta
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.6.0
24
+ version:
25
+ description:
26
+ email: anthony@orthor.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.markdown
34
+ files:
35
+ - LICENSE
36
+ - README.markdown
37
+ - Rakefile
38
+ - VERSION
39
+ - app/controllers/orthor/content_controller.rb
40
+ - app/views/layouts/orthor_layout.html.erb
41
+ - app/views/orthor/content/orthor_content.html.erb
42
+ - config/routes.rb
43
+ - lib/core_ext/dsl_accessor.rb
44
+ - lib/orthor.rb
45
+ - lib/orthor/category.rb
46
+ - lib/orthor/collections.rb
47
+ - lib/orthor/feed.rb
48
+ - lib/orthor/http_caching.rb
49
+ - lib/orthor/meta_data.rb
50
+ - lib/orthor/object.rb
51
+ - lib/orthor/page.rb
52
+ - lib/orthor/site.rb
53
+ - lib/orthor/static_page.rb
54
+ - lib/orthor_helper.rb
55
+ - lib/orthorings.rb
56
+ - lib/sinatra/orthor.rb
57
+ - orthorings.gemspec
58
+ - spec/orthor/dsl_spec.rb
59
+ - spec/orthor_helper_spec.rb
60
+ - spec/orthorings_spec.rb
61
+ - spec/resources/homepage.html
62
+ - spec/resources/latest-news-items.html
63
+ - spec/resources/news.html
64
+ - spec/sinatra/orthor_spec.rb
65
+ - spec/spec.opts
66
+ - spec/spec_helper.rb
67
+ has_rdoc: true
68
+ homepage: http://github.com/anthony/orthorings
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options:
73
+ - --charset=UTF-8
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.3.5
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Orthorings is a gem for displaying content from orthor.com
95
+ test_files:
96
+ - spec/orthor/dsl_spec.rb
97
+ - spec/orthor_helper_spec.rb
98
+ - spec/orthorings_spec.rb
99
+ - spec/sinatra/orthor_spec.rb
100
+ - spec/spec_helper.rb