kiosk 0.0.1 → 0.0.2
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.
- 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
|