kiosk 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +17 -1
- data/lib/kiosk.rb +3 -1
- data/lib/kiosk/cacheable/resource.rb +26 -5
- data/lib/kiosk/content_teaser.rb +41 -0
- data/lib/kiosk/resource_controller.rb +49 -0
- data/lib/kiosk/rewriter.rb +8 -2
- data/lib/kiosk/version.rb +1 -1
- data/lib/kiosk/word_press.rb +14 -0
- data/lib/kiosk/word_press/attachment.rb +7 -0
- data/lib/kiosk/word_press/author.rb +9 -0
- data/lib/kiosk/word_press/category.rb +18 -0
- data/lib/kiosk/word_press/comment.rb +13 -0
- data/lib/kiosk/word_press/images.rb +6 -0
- data/lib/kiosk/word_press/page.rb +57 -0
- data/lib/kiosk/word_press/post.rb +96 -0
- data/lib/kiosk/word_press/resource.rb +321 -0
- data/lib/kiosk/word_press/tag.rb +6 -0
- data/lib/kiosk/word_press/video.rb +36 -0
- metadata +15 -3
- data/lib/kiosk/resource.rb +0 -313
data/README.rdoc
CHANGED
@@ -16,7 +16,23 @@ license.
|
|
16
16
|
|
17
17
|
== Configuration
|
18
18
|
|
19
|
-
|
19
|
+
CMS integration requires a WordPress installation that includes the {JSON
|
20
|
+
API Plugin}[http://wordpress.org/extend/plugins/json-api/].
|
21
|
+
|
22
|
+
Once the WP site is up and running, the site endpoint should be specified as
|
23
|
+
the +origin+ content server in `config/kiosk.yml` of your Rails application.
|
24
|
+
Different configurations may be specified for each Rails environment, along
|
25
|
+
with a default.
|
26
|
+
|
27
|
+
origins:
|
28
|
+
default:
|
29
|
+
site: 'http://dev.cms.example/site_name'
|
30
|
+
production:
|
31
|
+
site: 'http://cms.example/site_name'
|
32
|
+
|
33
|
+
Localization of content resources depends further on the installation of the
|
34
|
+
{WPML Multilingual CMS}[http://wpml.org/] (non-free) and {WPML JSON
|
35
|
+
API}[http://wordpress.org/extend/plugins/wpml-json-api/] plugins.
|
20
36
|
|
21
37
|
== Roadmap
|
22
38
|
|
data/lib/kiosk.rb
CHANGED
@@ -13,6 +13,7 @@ module Kiosk
|
|
13
13
|
autoload :Cdn, 'kiosk/cdn'
|
14
14
|
autoload :Claim, 'kiosk/claim'
|
15
15
|
autoload :ClaimedNode, 'kiosk/claimed_node'
|
16
|
+
autoload :ContentTeaser, 'kiosk/content_teaser'
|
16
17
|
autoload :Controller, 'kiosk/controller'
|
17
18
|
autoload :Document, 'kiosk/document'
|
18
19
|
autoload :Indexer, 'kiosk/indexer'
|
@@ -20,11 +21,12 @@ module Kiosk
|
|
20
21
|
autoload :Origin, 'kiosk/origin'
|
21
22
|
autoload :ProspectiveNode, 'kiosk/prospective_node'
|
22
23
|
autoload :Prospector, 'kiosk/prospector'
|
23
|
-
autoload :
|
24
|
+
autoload :ResourceController, 'kiosk/resource_controller'
|
24
25
|
autoload :ResourceURI, 'kiosk/resource_uri'
|
25
26
|
autoload :Rewrite, 'kiosk/rewrite'
|
26
27
|
autoload :Rewriter, 'kiosk/rewriter'
|
27
28
|
autoload :Searchable, 'kiosk/searchable'
|
29
|
+
autoload :WordPress, 'kiosk/word_press'
|
28
30
|
|
29
31
|
##############################################################################
|
30
32
|
# Module methods
|
@@ -4,6 +4,29 @@ module Kiosk
|
|
4
4
|
module Cacheable::Resource
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
+
included do
|
8
|
+
def self.inherited(sub)
|
9
|
+
super
|
10
|
+
|
11
|
+
sub.module_exec do
|
12
|
+
protected
|
13
|
+
|
14
|
+
# Returns expireable connection keys set by derived class and all
|
15
|
+
# parent classes.
|
16
|
+
#
|
17
|
+
def self.all_connection_keys_to_expire
|
18
|
+
keys = @connection_keys_to_expire || []
|
19
|
+
|
20
|
+
if superclass.respond_to?(:all_connection_keys_to_expire)
|
21
|
+
superclass.all_connection_keys_to_expire | keys
|
22
|
+
else
|
23
|
+
keys
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
7
30
|
module ClassMethods
|
8
31
|
# Specifies the length of time for which a resource should stay cached.
|
9
32
|
# Either a +Fixnum+ (time in seconds) or block can be given. The block
|
@@ -46,11 +69,9 @@ module Kiosk
|
|
46
69
|
expire(resource.id)
|
47
70
|
expire_by_slug(resource.slug)
|
48
71
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
rescue NotImplementedError
|
53
|
-
end
|
72
|
+
begin
|
73
|
+
all_connection_keys_to_expire.each { |key| connection.cache_expire_by_pattern(key) }
|
74
|
+
rescue NotImplementedError
|
54
75
|
end
|
55
76
|
|
56
77
|
notify_observers(:after_expire, resource)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'active_support/core_ext/string'
|
2
|
+
|
3
|
+
module Kiosk
|
4
|
+
module ContentTeaser
|
5
|
+
# Returns a teaser of the content with roughly the given length.
|
6
|
+
#
|
7
|
+
# The content is parsed as an HTML fragment and only text nodes are
|
8
|
+
# considered to have length in this context. As a result, the returned
|
9
|
+
# string will not have a length matching the given +horizon+ exactly, but
|
10
|
+
# should render as such.
|
11
|
+
#
|
12
|
+
def teaser(horizon, options = {})
|
13
|
+
if doc = content_document
|
14
|
+
|
15
|
+
# Traverse all text nodes until we reach the limit
|
16
|
+
length = 0
|
17
|
+
|
18
|
+
# Find the boundary node, where the text length crosses the horizon
|
19
|
+
node = doc.xpath('descendant::text()').detect do |n|
|
20
|
+
(length += n.content.length) >= horizon
|
21
|
+
end
|
22
|
+
|
23
|
+
if node
|
24
|
+
# Truncate the content of the boundary text node
|
25
|
+
node.content = node.content.truncate(node.content.length - (length - horizon), options)
|
26
|
+
|
27
|
+
# Remove all following nodes from the document
|
28
|
+
# Note that this mark-and-sweep method is due to the catch 22 of
|
29
|
+
# relative DOM traversal (next, parent, etc.) and node removal.
|
30
|
+
nodes_to_remove = []
|
31
|
+
nodes_to_remove << node while node = (node.next || (node.parent && node.parent.next))
|
32
|
+
nodes_to_remove.each { |n| n.remove if n.parent }
|
33
|
+
end
|
34
|
+
|
35
|
+
doc.to_html
|
36
|
+
else
|
37
|
+
""
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Kiosk
|
2
|
+
module ResourceController
|
3
|
+
def show
|
4
|
+
self.resource = resource_model.find_by_slug(params[:id] || params[:slug])
|
5
|
+
end
|
6
|
+
|
7
|
+
# Can be used as an endpoint for the CMS to expire the cache.
|
8
|
+
#
|
9
|
+
def update
|
10
|
+
model = resource_model
|
11
|
+
|
12
|
+
if model.respond_to?(:expire)
|
13
|
+
resource = model.new(params)
|
14
|
+
|
15
|
+
# Expire for all target locales if the resource is localizable
|
16
|
+
if model.respond_to?(:localized_to)
|
17
|
+
I18n.available_locales.each do |locale|
|
18
|
+
model.localized_to(locale) do
|
19
|
+
resource.expire
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
resource.expire
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
render :nothing => true
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Resolves the model from the controller name.
|
33
|
+
#
|
34
|
+
def resource_model
|
35
|
+
names = self.class.name.split('::')
|
36
|
+
controller = names.pop
|
37
|
+
(names.join('::') + '::' + controller.sub('Controller', '').singularize).constantize
|
38
|
+
end
|
39
|
+
|
40
|
+
def resource_name
|
41
|
+
resource_model.name.split('::').last.underscore
|
42
|
+
end
|
43
|
+
|
44
|
+
def resource=(resource)
|
45
|
+
instance_variable_set("@#{resource_name}".to_sym, resource)
|
46
|
+
@resource = resource
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/kiosk/rewriter.rb
CHANGED
@@ -44,10 +44,16 @@ module Kiosk
|
|
44
44
|
@rewrites.clear
|
45
45
|
end
|
46
46
|
|
47
|
+
# Returns the rewriten document from +rewrite_to_document+ as a string.
|
48
|
+
#
|
49
|
+
def rewrite(content)
|
50
|
+
rewrite_to_document(content).to_html
|
51
|
+
end
|
52
|
+
|
47
53
|
# Runs on claims on the given content, incorporates all controller
|
48
54
|
# rewrites, and returns the resulting content.
|
49
55
|
#
|
50
|
-
def
|
56
|
+
def rewrite_to_document(content)
|
51
57
|
document = Document.parse(content)
|
52
58
|
|
53
59
|
# Claims are grouped by priority. Process them in order.
|
@@ -66,7 +72,7 @@ module Kiosk
|
|
66
72
|
end
|
67
73
|
end
|
68
74
|
|
69
|
-
document
|
75
|
+
document
|
70
76
|
end
|
71
77
|
end
|
72
78
|
end
|
data/lib/kiosk/version.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Kiosk
|
2
|
+
module WordPress
|
3
|
+
autoload :Attachment, 'kiosk/word_press/attachment'
|
4
|
+
autoload :Author, 'kiosk/word_press/author'
|
5
|
+
autoload :Category, 'kiosk/word_press/category'
|
6
|
+
autoload :Comment, 'kiosk/word_press/comment'
|
7
|
+
autoload :Images, 'kiosk/word_press/images'
|
8
|
+
autoload :Page, 'kiosk/word_press/page'
|
9
|
+
autoload :Post, 'kiosk/word_press/post'
|
10
|
+
autoload :Resource, 'kiosk/word_press/resource'
|
11
|
+
autoload :Tag, 'kiosk/word_press/tag'
|
12
|
+
autoload :Video, 'kiosk/word_press/video'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kiosk
|
2
|
+
module WordPress
|
3
|
+
class Category < Resource
|
4
|
+
schema do
|
5
|
+
attribute 'title', :string
|
6
|
+
end
|
7
|
+
|
8
|
+
# Retrieves a specific category by its slug. This can't be done directly
|
9
|
+
# through the WordPress JSON API, so all categories are traversed instead.
|
10
|
+
#
|
11
|
+
def self.find_by_slug(slug)
|
12
|
+
category = all.detect { |category| category.slug == slug }
|
13
|
+
raise ResourceNotFound.new("unknown category `#{slug}'") unless category
|
14
|
+
category
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Kiosk
|
2
|
+
module WordPress
|
3
|
+
class Page < Resource
|
4
|
+
include Searchable::Resource
|
5
|
+
|
6
|
+
##############################################################################
|
7
|
+
# Content integration
|
8
|
+
##############################################################################
|
9
|
+
schema do
|
10
|
+
attribute 'title', :string
|
11
|
+
attribute 'title_plain', :string
|
12
|
+
attribute 'content', :string
|
13
|
+
attribute 'excerpt', :string
|
14
|
+
end
|
15
|
+
|
16
|
+
claims_path_content(:selector => 'a',
|
17
|
+
:pattern => ':slug',
|
18
|
+
:shims => {:slug => /[^\?]+/},
|
19
|
+
:priority => :low)
|
20
|
+
|
21
|
+
##############################################################################
|
22
|
+
# Indexes
|
23
|
+
##############################################################################
|
24
|
+
define_index(:content_page) do
|
25
|
+
indexes :title, :content
|
26
|
+
end
|
27
|
+
|
28
|
+
##############################################################################
|
29
|
+
# Instance methods
|
30
|
+
##############################################################################
|
31
|
+
|
32
|
+
# Returns the leading portion of the slug.
|
33
|
+
#
|
34
|
+
def section
|
35
|
+
(s = slug.to_s).empty? ? nil : s.split('/').first
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the full page slug. This differs in cases where the page is a
|
39
|
+
# child of another in the hierarchy. E.g. where a parent page has slug
|
40
|
+
# 'p1' and the child page has slug 'c1', 'p1/c1' would be returned, not
|
41
|
+
# simply 'c1'.
|
42
|
+
#
|
43
|
+
def slug
|
44
|
+
begin
|
45
|
+
#parse the url
|
46
|
+
uri = URI.parse(url)
|
47
|
+
#get the route from the original site uri
|
48
|
+
route = uri.route_from(Kiosk.origin.site_uri)
|
49
|
+
#return just the path
|
50
|
+
route.path
|
51
|
+
rescue
|
52
|
+
attributes[:slug]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Kiosk
|
2
|
+
module WordPress
|
3
|
+
class Post < Resource
|
4
|
+
include Searchable::Resource
|
5
|
+
include ContentTeaser
|
6
|
+
|
7
|
+
##############################################################################
|
8
|
+
# Content integration
|
9
|
+
##############################################################################
|
10
|
+
schema do
|
11
|
+
attribute 'title', :string
|
12
|
+
attribute 'title_plain', :string
|
13
|
+
attribute 'content', :string
|
14
|
+
attribute 'excerpt', :string
|
15
|
+
attribute 'date', :string
|
16
|
+
attribute 'modified', :string
|
17
|
+
end
|
18
|
+
|
19
|
+
claims_path_content(:selector => 'a', :pattern => '\d{4}/\d{2}/\d{2}/:slug')
|
20
|
+
|
21
|
+
expires_connection_methods('get_category_posts', 'get_tag_posts', 'get_recent_posts')
|
22
|
+
|
23
|
+
##############################################################################
|
24
|
+
# Indexes
|
25
|
+
##############################################################################
|
26
|
+
define_index(:content_post) do
|
27
|
+
indexes :title, :content
|
28
|
+
end
|
29
|
+
|
30
|
+
##############################################################################
|
31
|
+
# Class methods
|
32
|
+
##############################################################################
|
33
|
+
class << self
|
34
|
+
def all
|
35
|
+
Category.all.inject([]) { |posts,cat| posts += categorized_as(cat) }.uniq { |p| p.id }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Fetches posts for the given category.
|
39
|
+
#
|
40
|
+
def categorized_as(category, params = {})
|
41
|
+
category = Kiosk::WordPress::Category.find_by_slug(category) if category.is_a?(String)
|
42
|
+
find_by_associated(category, {:count => 100000}.merge(params))
|
43
|
+
rescue ResourceNotFound
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Fetches posts that were created on the given date.
|
48
|
+
#
|
49
|
+
def created_on(date)
|
50
|
+
find(:all, :method => :get_date_posts, :params => [:date => date])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Fetches recently made posts.
|
54
|
+
#
|
55
|
+
def recent
|
56
|
+
find(:all, :method => :get_recent_posts)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Fetches posts with the given tag.
|
60
|
+
#
|
61
|
+
def tagged_with(tag)
|
62
|
+
find_by_associated(tag)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
##############################################################################
|
67
|
+
# Instance methods
|
68
|
+
##############################################################################
|
69
|
+
|
70
|
+
# Returns the post categories.
|
71
|
+
#
|
72
|
+
def categories
|
73
|
+
attributes[:categories] || []
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the first category of the post.
|
77
|
+
#
|
78
|
+
def category
|
79
|
+
categories.first
|
80
|
+
end
|
81
|
+
|
82
|
+
# The time at which this post was authored.
|
83
|
+
#
|
84
|
+
def created_at
|
85
|
+
attributes[:date].to_time
|
86
|
+
end
|
87
|
+
|
88
|
+
# The time at which this post was modified.
|
89
|
+
#
|
90
|
+
def modified_at
|
91
|
+
attributes[:modified].to_time
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,321 @@
|
|
1
|
+
require 'active_resource'
|
2
|
+
|
3
|
+
# Proxy for content resources.
|
4
|
+
#
|
5
|
+
module Kiosk
|
6
|
+
module WordPress
|
7
|
+
class Resource < ::ActiveResource::Base
|
8
|
+
include Cacheable::Resource
|
9
|
+
include Localizable::Resource
|
10
|
+
include Prospector
|
11
|
+
|
12
|
+
##############################################################################
|
13
|
+
# ActiveResource config
|
14
|
+
##############################################################################
|
15
|
+
self.site = Kiosk.origin.site
|
16
|
+
self.format = :json
|
17
|
+
|
18
|
+
schema do
|
19
|
+
attribute 'slug', :string
|
20
|
+
end
|
21
|
+
|
22
|
+
##############################################################################
|
23
|
+
# Caching
|
24
|
+
##############################################################################
|
25
|
+
cached_expire_in { |resource| resource['status'] == 'error' ? 30.minutes : 1.day }
|
26
|
+
|
27
|
+
##############################################################################
|
28
|
+
# Class methods
|
29
|
+
##############################################################################
|
30
|
+
class << self
|
31
|
+
# Returns all instances of the resource.
|
32
|
+
#
|
33
|
+
def all
|
34
|
+
find(:all)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reimplements the +ActiveResource+ path constructor to work with the
|
38
|
+
# WordPress JSON-API plugin.
|
39
|
+
#
|
40
|
+
def element_path(id, prefix_options = {}, query_options = nil)
|
41
|
+
"#{api_path_to("get_#{element_name}")}#{query_string({:id => id}.merge(query_options || {}))}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def element_path_by_slug(slug, prefix_options = {}, query_options = nil)
|
45
|
+
"#{api_path_to("get_#{element_name}")}#{query_string({:slug => slug}.merge(query_options || {}))}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Adds functionality to the +ActiveResource.find+ method to allow for
|
49
|
+
# specifying the WordPress JSON API method that should be used. This
|
50
|
+
# simplifies definition of scopes in derived models.
|
51
|
+
#
|
52
|
+
def find(*arguments)
|
53
|
+
scope = arguments.slice!(0)
|
54
|
+
options = arguments.slice!(0) || {}
|
55
|
+
|
56
|
+
if options.key?(:method)
|
57
|
+
options[:from] = api_path_to(options[:method])
|
58
|
+
options.delete(:method)
|
59
|
+
end
|
60
|
+
|
61
|
+
super(scope, options)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Finds all resources by the given related resource.
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# Post.find_by_associated(category)
|
69
|
+
#
|
70
|
+
# Is the same as invoking:
|
71
|
+
#
|
72
|
+
# Post.find(:all, :method => "get_category_posts", :params => {:id => category.id})
|
73
|
+
#
|
74
|
+
def find_by_associated(resource, params = {})
|
75
|
+
find(:all,
|
76
|
+
:method => "get_#{resource.class.element_name}_#{element_name.pluralize}",
|
77
|
+
:params => params.merge({:id => resource.id}))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Finds the resource by the given slug.
|
81
|
+
#
|
82
|
+
def find_by_slug(slug)
|
83
|
+
find(:one, :method => "get_#{element_name}", :params => {:slug => slug})
|
84
|
+
end
|
85
|
+
|
86
|
+
# Reimplements the +ActiveResource+ path constructor to work with the
|
87
|
+
# WordPress JSON-API plugin.
|
88
|
+
#
|
89
|
+
def collection_path(prefix_options = {}, query_options = nil)
|
90
|
+
"#{api_path_to("get_#{element_name}_index")}#{query_string(query_options)}"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Reimplements the +ActiveResource+ method to check for bad responses
|
94
|
+
# before instantiating a collection.
|
95
|
+
#
|
96
|
+
def instantiate_collection(collection, prefix_options = {})
|
97
|
+
super(normalize_response(collection, true), prefix_options)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Reimplements the +ActiveResource+ method to check for bad responses
|
101
|
+
# before instantiating an object.
|
102
|
+
#
|
103
|
+
def instantiate_record(record, prefix_options = {})
|
104
|
+
super(normalize_response(record), prefix_options)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Executes the given block within a scope where all requests for this
|
108
|
+
# content resource are appended with the given parameters.
|
109
|
+
#
|
110
|
+
# class Post < Resource; end
|
111
|
+
#
|
112
|
+
# Post.with_parameters(:language => 'en') do
|
113
|
+
# english_posts = Post.find(:all)
|
114
|
+
# english_pages = Page.find(:all)
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# Scopes can be nested.
|
118
|
+
#
|
119
|
+
# Post.with_parameters(:language => 'es') do
|
120
|
+
# Post.with_parameters(:recent => true) do
|
121
|
+
# recent_spanish_posts = Post.find(:all)
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# Scopes are inherited.
|
126
|
+
#
|
127
|
+
# Resource.with_parameters(:language => 'fr') do
|
128
|
+
# french_posts = Post.find(:all)
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# However, nesting is still respected.
|
132
|
+
#
|
133
|
+
# Resource.with_parameters(:language => 'fr') do
|
134
|
+
# Post.with_parameters(:language => 'en') do
|
135
|
+
# english_posts = Post.find(:all)
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# Even with this nesting inverted.
|
140
|
+
#
|
141
|
+
# Post.with_parameters(:language => 'fr') do
|
142
|
+
# Resource.with_parameters(:language => 'en') do
|
143
|
+
# english_posts = Post.find(:all)
|
144
|
+
# end
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
def with_parameters(params = {})
|
148
|
+
push_to_query_scope_stack(params)
|
149
|
+
|
150
|
+
begin
|
151
|
+
yield
|
152
|
+
ensure
|
153
|
+
pop_from_query_scope_stack
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
protected
|
158
|
+
|
159
|
+
# Returns the path to the given method of the WordPress API.
|
160
|
+
#
|
161
|
+
def api_path_to(method)
|
162
|
+
"#{site.path}api/#{method}/"
|
163
|
+
end
|
164
|
+
|
165
|
+
# Checks the given response for errors and normalizes its structure. A
|
166
|
+
# response from the API includes an envelope, which must be checked for
|
167
|
+
# the response status ("ok" or "error"). If an error is found, an
|
168
|
+
# exception is raised.
|
169
|
+
#
|
170
|
+
def normalize_response(response, collection = false)
|
171
|
+
response = case response['status']
|
172
|
+
when 'ok'
|
173
|
+
response[collection ? element_name.pluralize : element_name]
|
174
|
+
when 'error'
|
175
|
+
raise_error(response['error'])
|
176
|
+
else
|
177
|
+
# This isn't a response envelope. Just let it pass through.
|
178
|
+
response
|
179
|
+
end
|
180
|
+
|
181
|
+
camelcase_keys(response)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Reimplements the parent method to include parameters of the current
|
185
|
+
# query scope. See +with_parameters+.
|
186
|
+
#
|
187
|
+
def query_string(options)
|
188
|
+
scoped_options = my_query_scope_stack.inject({}) do |scoped_options,(klass,opt_stack)|
|
189
|
+
if self.ancestors.include?(klass)
|
190
|
+
scoped_options = opt_stack.reduce(scoped_options) do |scoped_options,opts|
|
191
|
+
scoped_options.merge(opts)
|
192
|
+
end
|
193
|
+
else
|
194
|
+
scoped_options
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
super(scoped_options.merge(options))
|
199
|
+
end
|
200
|
+
|
201
|
+
# Handles errors returned by the WordPress JSON API.
|
202
|
+
#
|
203
|
+
def raise_error(error)
|
204
|
+
case error
|
205
|
+
when 'Not found.'
|
206
|
+
raise Kiosk::ResourceNotFound.new(error)
|
207
|
+
when /Un?known method '(\w+)'/ # note the possibility of a spelling error
|
208
|
+
raise NotImplementedError.new(error)
|
209
|
+
else
|
210
|
+
raise Kiosk::ResourceError.new(error)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
private
|
215
|
+
|
216
|
+
mattr_accessor :query_scope_stack
|
217
|
+
|
218
|
+
# Filters and sorts the query-scope stack so that only scopes relevant
|
219
|
+
# to this class are applied and are applied in order from furthest
|
220
|
+
# ancestor to nearest.
|
221
|
+
#
|
222
|
+
def my_query_scope_stack
|
223
|
+
if query_scope_stack
|
224
|
+
Hash[query_scope_stack.select do |klass,stack|
|
225
|
+
self.ancestors.include?(klass)
|
226
|
+
end.sort_by do |klass,stack|
|
227
|
+
self.ancestors.index(klass) * -1
|
228
|
+
end]
|
229
|
+
else
|
230
|
+
{}
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def push_to_query_scope_stack(params)
|
235
|
+
self.query_scope_stack ||= {}
|
236
|
+
self.query_scope_stack[self] ||= []
|
237
|
+
|
238
|
+
# Append stacks for this class and all descendent classes.
|
239
|
+
query_scope_stack.each_key do |klass|
|
240
|
+
query_scope_stack[klass].push(params) if klass.ancestors.include?(self)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def pop_from_query_scope_stack
|
245
|
+
# Pop stacks for this class and all descendent classes.
|
246
|
+
query_scope_stack.each_key do |klass|
|
247
|
+
query_scope_stack[klass].pop if klass.ancestors.include?(self)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Recursively changes the keys of the given hash (or array of hashes)
|
252
|
+
# to camelcase.
|
253
|
+
#
|
254
|
+
def camelcase_keys(obj)
|
255
|
+
case obj
|
256
|
+
when Hash
|
257
|
+
obj.inject({}) { |hash,(k,v)| hash[k.to_s.underscore] = camelcase_keys(v); hash }
|
258
|
+
when Array
|
259
|
+
obj.map { |v| camelcase_keys(v) }
|
260
|
+
else
|
261
|
+
obj
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
##############################################################################
|
267
|
+
# Instance methods
|
268
|
+
##############################################################################
|
269
|
+
|
270
|
+
# Returns the rewritten resource content. See +raw_content+ for untouched
|
271
|
+
# content.
|
272
|
+
#
|
273
|
+
def content
|
274
|
+
@content ||= raw_content && Kiosk.rewriter.rewrite(raw_content)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Returns the rewritten resource content as a +Document+.
|
278
|
+
#
|
279
|
+
def content_document
|
280
|
+
raw_content && Kiosk.rewriter.rewrite_to_document(raw_content)
|
281
|
+
end
|
282
|
+
|
283
|
+
# Returns the rewritten resource excerpt. See +raw_excerpt+ for untouched
|
284
|
+
# content.
|
285
|
+
#
|
286
|
+
def excerpt
|
287
|
+
@excerpt ||= raw_excerpt && Kiosk.rewriter.rewrite(raw_excerpt)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Destroying is not supported.
|
291
|
+
#
|
292
|
+
def destroy
|
293
|
+
raise NotImplementedError
|
294
|
+
end
|
295
|
+
|
296
|
+
# Returns the resource content, untouched by the content rewriter.
|
297
|
+
#
|
298
|
+
def raw_content
|
299
|
+
attributes[:content]
|
300
|
+
end
|
301
|
+
|
302
|
+
# Returns the resource excerpt, untouched by the content rewriter.
|
303
|
+
#
|
304
|
+
def raw_excerpt
|
305
|
+
attributes[:excerpt]
|
306
|
+
end
|
307
|
+
|
308
|
+
# Saving is not supported.
|
309
|
+
#
|
310
|
+
def save
|
311
|
+
raise NotImplementedError
|
312
|
+
end
|
313
|
+
|
314
|
+
# Returns the value used in constructing a URL to this object.
|
315
|
+
#
|
316
|
+
def to_param
|
317
|
+
attributes[:slug] || attributes[:id]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Kiosk
|
2
|
+
module WordPress
|
3
|
+
class Video < Resource
|
4
|
+
schema do
|
5
|
+
attribute 'id', :string
|
6
|
+
attribute 'slug', :string
|
7
|
+
attribute 'classid', :string
|
8
|
+
end
|
9
|
+
|
10
|
+
claims_content(:selector => 'object') do |object|
|
11
|
+
if object['id'] && (match = object['id'].match(/^viddler(?:player)?-(\w+)/))
|
12
|
+
{:slug => object['id'], :classid => object['classid']}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the video ID, which is either an explicitly set attribute or the
|
17
|
+
# trailing string identifier from the slug (following the last "-").
|
18
|
+
#
|
19
|
+
def id
|
20
|
+
attributes[:id] || (attributes[:slug] && attributes[:slug].match(/-(\w+)$/)[1])
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the path to the "thumbnail" version of the movie.
|
24
|
+
#
|
25
|
+
def thumbnail_url
|
26
|
+
"http://www.viddler.com/simple/#{id}/"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the path to the "full" version of the movie.
|
30
|
+
#
|
31
|
+
def url
|
32
|
+
"http://www.viddler.com/player/#{id}/"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: kiosk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Daniel Duvall
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-05-02 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: nokogiri
|
@@ -75,6 +75,7 @@ files:
|
|
75
75
|
- lib/kiosk/claim/path_claim.rb
|
76
76
|
- lib/kiosk/claim.rb
|
77
77
|
- lib/kiosk/claimed_node.rb
|
78
|
+
- lib/kiosk/content_teaser.rb
|
78
79
|
- lib/kiosk/controller.rb
|
79
80
|
- lib/kiosk/document.rb
|
80
81
|
- lib/kiosk/indexer/adapter/base.rb
|
@@ -87,7 +88,7 @@ files:
|
|
87
88
|
- lib/kiosk/origin.rb
|
88
89
|
- lib/kiosk/prospective_node.rb
|
89
90
|
- lib/kiosk/prospector.rb
|
90
|
-
- lib/kiosk/
|
91
|
+
- lib/kiosk/resource_controller.rb
|
91
92
|
- lib/kiosk/resource_error.rb
|
92
93
|
- lib/kiosk/resource_not_found.rb
|
93
94
|
- lib/kiosk/resource_uri.rb
|
@@ -100,6 +101,17 @@ files:
|
|
100
101
|
- lib/kiosk/searchable.rb
|
101
102
|
- lib/kiosk/tasks/kiosk.rake
|
102
103
|
- lib/kiosk/version.rb
|
104
|
+
- lib/kiosk/word_press/attachment.rb
|
105
|
+
- lib/kiosk/word_press/author.rb
|
106
|
+
- lib/kiosk/word_press/category.rb
|
107
|
+
- lib/kiosk/word_press/comment.rb
|
108
|
+
- lib/kiosk/word_press/images.rb
|
109
|
+
- lib/kiosk/word_press/page.rb
|
110
|
+
- lib/kiosk/word_press/post.rb
|
111
|
+
- lib/kiosk/word_press/resource.rb
|
112
|
+
- lib/kiosk/word_press/tag.rb
|
113
|
+
- lib/kiosk/word_press/video.rb
|
114
|
+
- lib/kiosk/word_press.rb
|
103
115
|
- lib/kiosk.rb
|
104
116
|
- MIT-LICENSE
|
105
117
|
- Rakefile
|
data/lib/kiosk/resource.rb
DELETED
@@ -1,313 +0,0 @@
|
|
1
|
-
require 'active_resource'
|
2
|
-
|
3
|
-
# Proxy for content resources.
|
4
|
-
#
|
5
|
-
module Kiosk
|
6
|
-
class Resource < ::ActiveResource::Base
|
7
|
-
include Cacheable::Resource
|
8
|
-
include Localizable::Resource
|
9
|
-
include Prospector
|
10
|
-
|
11
|
-
##############################################################################
|
12
|
-
# ActiveResource config
|
13
|
-
##############################################################################
|
14
|
-
self.site = Kiosk.origin.site
|
15
|
-
self.format = :json
|
16
|
-
|
17
|
-
schema do
|
18
|
-
attribute 'slug', :string
|
19
|
-
end
|
20
|
-
|
21
|
-
##############################################################################
|
22
|
-
# Caching
|
23
|
-
##############################################################################
|
24
|
-
cached_expire_in { |resource| resource['status'] == 'error' ? 30.minutes : 1.day }
|
25
|
-
|
26
|
-
##############################################################################
|
27
|
-
# Class methods
|
28
|
-
##############################################################################
|
29
|
-
class << self
|
30
|
-
# Returns all instances of the resource.
|
31
|
-
#
|
32
|
-
def all
|
33
|
-
find(:all)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Reimplements the +ActiveResource+ path constructor to work with the
|
37
|
-
# WordPress JSON-API plugin.
|
38
|
-
#
|
39
|
-
def element_path(id, prefix_options = {}, query_options = nil)
|
40
|
-
"#{api_path_to("get_#{element_name}")}#{query_string({:id => id}.merge(query_options || {}))}"
|
41
|
-
end
|
42
|
-
|
43
|
-
def element_path_by_slug(slug, prefix_options = {}, query_options = nil)
|
44
|
-
"#{api_path_to("get_#{element_name}")}#{query_string({:slug => slug}.merge(query_options || {}))}"
|
45
|
-
end
|
46
|
-
|
47
|
-
# Adds functionality to the +ActiveResource.find+ method to allow for
|
48
|
-
# specifying the WordPress JSON API method that should be used. This
|
49
|
-
# simplifies definition of scopes in derived models.
|
50
|
-
#
|
51
|
-
def find(*arguments)
|
52
|
-
scope = arguments.slice!(0)
|
53
|
-
options = arguments.slice!(0) || {}
|
54
|
-
|
55
|
-
if options.key?(:method)
|
56
|
-
options[:from] = api_path_to(options[:method])
|
57
|
-
options.delete(:method)
|
58
|
-
end
|
59
|
-
|
60
|
-
super(scope, options)
|
61
|
-
end
|
62
|
-
|
63
|
-
# Finds all resources by the given related resource.
|
64
|
-
#
|
65
|
-
# Example:
|
66
|
-
#
|
67
|
-
# Post.find_by_associated(category)
|
68
|
-
#
|
69
|
-
# Is the same as invoking:
|
70
|
-
#
|
71
|
-
# Post.find(:all, :method => "get_category_posts", :params => {:id => category.id})
|
72
|
-
#
|
73
|
-
def find_by_associated(resource, params = {})
|
74
|
-
find(:all,
|
75
|
-
:method => "get_#{resource.class.element_name}_#{element_name.pluralize}",
|
76
|
-
:params => params.merge({:id => resource.id}))
|
77
|
-
end
|
78
|
-
|
79
|
-
# Finds the resource by the given slug.
|
80
|
-
#
|
81
|
-
def find_by_slug(slug)
|
82
|
-
find(:one, :method => "get_#{element_name}", :params => {:slug => slug})
|
83
|
-
end
|
84
|
-
|
85
|
-
# Reimplements the +ActiveResource+ path constructor to work with the
|
86
|
-
# WordPress JSON-API plugin.
|
87
|
-
#
|
88
|
-
def collection_path(prefix_options = {}, query_options = nil)
|
89
|
-
"#{api_path_to("get_#{element_name}_index")}#{query_string(query_options)}"
|
90
|
-
end
|
91
|
-
|
92
|
-
# Reimplements the +ActiveResource+ method to check for bad responses
|
93
|
-
# before instantiating a collection.
|
94
|
-
#
|
95
|
-
def instantiate_collection(collection, prefix_options = {})
|
96
|
-
super(normalize_response(collection, true), prefix_options)
|
97
|
-
end
|
98
|
-
|
99
|
-
# Reimplements the +ActiveResource+ method to check for bad responses
|
100
|
-
# before instantiating an object.
|
101
|
-
#
|
102
|
-
def instantiate_record(record, prefix_options = {})
|
103
|
-
super(normalize_response(record), prefix_options)
|
104
|
-
end
|
105
|
-
|
106
|
-
# Executes the given block within a scope where all requests for this
|
107
|
-
# content resource are appended with the given parameters.
|
108
|
-
#
|
109
|
-
# class Post < Resource; end
|
110
|
-
#
|
111
|
-
# Post.with_parameters(:language => 'en') do
|
112
|
-
# english_posts = Post.find(:all)
|
113
|
-
# english_pages = Page.find(:all)
|
114
|
-
# end
|
115
|
-
#
|
116
|
-
# Scopes can be nested.
|
117
|
-
#
|
118
|
-
# Post.with_parameters(:language => 'es') do
|
119
|
-
# Post.with_parameters(:recent => true) do
|
120
|
-
# recent_spanish_posts = Post.find(:all)
|
121
|
-
# end
|
122
|
-
# end
|
123
|
-
#
|
124
|
-
# Scopes are inherited.
|
125
|
-
#
|
126
|
-
# Resource.with_parameters(:language => 'fr') do
|
127
|
-
# french_posts = Post.find(:all)
|
128
|
-
# end
|
129
|
-
#
|
130
|
-
# However, nesting is still respected.
|
131
|
-
#
|
132
|
-
# Resource.with_parameters(:language => 'fr') do
|
133
|
-
# Post.with_parameters(:language => 'en') do
|
134
|
-
# english_posts = Post.find(:all)
|
135
|
-
# end
|
136
|
-
# end
|
137
|
-
#
|
138
|
-
# Even with this nesting inverted.
|
139
|
-
#
|
140
|
-
# Post.with_parameters(:language => 'fr') do
|
141
|
-
# Resource.with_parameters(:language => 'en') do
|
142
|
-
# english_posts = Post.find(:all)
|
143
|
-
# end
|
144
|
-
# end
|
145
|
-
#
|
146
|
-
def with_parameters(params = {})
|
147
|
-
push_to_query_scope_stack(params)
|
148
|
-
|
149
|
-
begin
|
150
|
-
yield
|
151
|
-
ensure
|
152
|
-
pop_from_query_scope_stack
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
protected
|
157
|
-
|
158
|
-
# Returns the path to the given method of the WordPress API.
|
159
|
-
#
|
160
|
-
def api_path_to(method)
|
161
|
-
"#{site.path}api/#{method}/"
|
162
|
-
end
|
163
|
-
|
164
|
-
# Checks the given response for errors and normalizes its structure. A
|
165
|
-
# response from the API includes an envelope, which must be checked for
|
166
|
-
# the response status ("ok" or "error"). If an error is found, an
|
167
|
-
# exception is raised.
|
168
|
-
#
|
169
|
-
def normalize_response(response, collection = false)
|
170
|
-
response = case response['status']
|
171
|
-
when 'ok'
|
172
|
-
response[collection ? element_name.pluralize : element_name]
|
173
|
-
when 'error'
|
174
|
-
raise_error(response['error'])
|
175
|
-
else
|
176
|
-
# This isn't a response envelope. Just let it pass through.
|
177
|
-
response
|
178
|
-
end
|
179
|
-
|
180
|
-
camelcase_keys(response)
|
181
|
-
end
|
182
|
-
|
183
|
-
# Reimplements the parent method to include parameters of the current
|
184
|
-
# query scope. See +with_parameters+.
|
185
|
-
#
|
186
|
-
def query_string(options)
|
187
|
-
scoped_options = my_query_scope_stack.inject({}) do |scoped_options,(klass,opt_stack)|
|
188
|
-
if self.ancestors.include?(klass)
|
189
|
-
scoped_options = opt_stack.reduce(scoped_options) do |scoped_options,opts|
|
190
|
-
scoped_options.merge(opts)
|
191
|
-
end
|
192
|
-
else
|
193
|
-
scoped_options
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
super(scoped_options.merge(options))
|
198
|
-
end
|
199
|
-
|
200
|
-
# Handles errors returned by the WordPress JSON API.
|
201
|
-
#
|
202
|
-
def raise_error(error)
|
203
|
-
case error
|
204
|
-
when 'Not found.'
|
205
|
-
raise Kiosk::ResourceNotFound.new(error)
|
206
|
-
when /Un?known method '(\w+)'/ # note the possibility of a spelling error
|
207
|
-
raise NotImplementedError.new(error)
|
208
|
-
else
|
209
|
-
raise Kiosk::ResourceError.new(error)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
private
|
214
|
-
|
215
|
-
mattr_accessor :query_scope_stack
|
216
|
-
|
217
|
-
# Filters and sorts the query-scope stack so that only scopes relevant
|
218
|
-
# to this class are applied and are applied in order from furthest
|
219
|
-
# ancestor to nearest.
|
220
|
-
#
|
221
|
-
def my_query_scope_stack
|
222
|
-
if query_scope_stack
|
223
|
-
Hash[query_scope_stack.select do |klass,stack|
|
224
|
-
self.ancestors.include?(klass)
|
225
|
-
end.sort_by do |klass,stack|
|
226
|
-
self.ancestors.index(klass) * -1
|
227
|
-
end]
|
228
|
-
else
|
229
|
-
{}
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
def push_to_query_scope_stack(params)
|
234
|
-
self.query_scope_stack ||= {}
|
235
|
-
self.query_scope_stack[self] ||= []
|
236
|
-
|
237
|
-
# Append stacks for this class and all descendent classes.
|
238
|
-
query_scope_stack.each_key do |klass|
|
239
|
-
query_scope_stack[klass].push(params) if klass.ancestors.include?(self)
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
def pop_from_query_scope_stack
|
244
|
-
# Pop stacks for this class and all descendent classes.
|
245
|
-
query_scope_stack.each_key do |klass|
|
246
|
-
query_scope_stack[klass].pop if klass.ancestors.include?(self)
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
# Recursively changes the keys of the given hash (or array of hashes)
|
251
|
-
# to camelcase.
|
252
|
-
#
|
253
|
-
def camelcase_keys(obj)
|
254
|
-
case obj
|
255
|
-
when Hash
|
256
|
-
obj.inject({}) { |hash,(k,v)| hash[k.to_s.underscore] = camelcase_keys(v); hash }
|
257
|
-
when Array
|
258
|
-
obj.map { |v| camelcase_keys(v) }
|
259
|
-
else
|
260
|
-
obj
|
261
|
-
end
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
##############################################################################
|
266
|
-
# Instance methods
|
267
|
-
##############################################################################
|
268
|
-
|
269
|
-
# Returns the rewritten resource content. See +raw_content+ for untouched
|
270
|
-
# content.
|
271
|
-
#
|
272
|
-
def content
|
273
|
-
@content ||= raw_content && Kiosk.rewriter.rewrite(raw_content)
|
274
|
-
end
|
275
|
-
|
276
|
-
# Returns the rewritten resource excerpt. See +raw_excerpt+ for untouched
|
277
|
-
# content.
|
278
|
-
#
|
279
|
-
def excerpt
|
280
|
-
@excerpt ||= raw_excerpt && Kiosk.rewriter.rewrite(raw_excerpt)
|
281
|
-
end
|
282
|
-
|
283
|
-
# Destroying is not supported.
|
284
|
-
#
|
285
|
-
def destroy
|
286
|
-
raise NotImplementedError
|
287
|
-
end
|
288
|
-
|
289
|
-
# Returns the resource content, untouched by the content rewriter.
|
290
|
-
#
|
291
|
-
def raw_content
|
292
|
-
attributes[:content]
|
293
|
-
end
|
294
|
-
|
295
|
-
# Returns the resource excerpt, untouched by the content rewriter.
|
296
|
-
#
|
297
|
-
def raw_excerpt
|
298
|
-
attributes[:excerpt]
|
299
|
-
end
|
300
|
-
|
301
|
-
# Saving is not supported.
|
302
|
-
#
|
303
|
-
def save
|
304
|
-
raise NotImplementedError
|
305
|
-
end
|
306
|
-
|
307
|
-
# Returns the value used in constructing a URL to this object.
|
308
|
-
#
|
309
|
-
def to_param
|
310
|
-
attributes[:slug] || attributes[:id]
|
311
|
-
end
|
312
|
-
end
|
313
|
-
end
|