infopark_cloud_connector 6.8.0.beta.200.621.4c8e1b0
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 +71 -0
- data/lib/infopark_cloud_connector.rb +19 -0
- data/lib/rails_connector/attribute.rb +35 -0
- data/lib/rails_connector/blob.rb +66 -0
- data/lib/rails_connector/cache.rb +35 -0
- data/lib/rails_connector/cache_middleware.rb +17 -0
- data/lib/rails_connector/chain.rb +177 -0
- data/lib/rails_connector/cms_base_model.rb +61 -0
- data/lib/rails_connector/content_cache.rb +24 -0
- data/lib/rails_connector/controller_runtime.rb +35 -0
- data/lib/rails_connector/couch_blob.rb +44 -0
- data/lib/rails_connector/couchdb_views_source_path.rb +7 -0
- data/lib/rails_connector/date_attribute.rb +28 -0
- data/lib/rails_connector/default_search_request.rb +6 -0
- data/lib/rails_connector/elasticsearch_request.rb +78 -0
- data/lib/rails_connector/errors.rb +9 -0
- data/lib/rails_connector/link.rb +135 -0
- data/lib/rails_connector/log_subscriber.rb +29 -0
- data/lib/rails_connector/named_link.rb +72 -0
- data/lib/rails_connector/obj.rb +622 -0
- data/lib/rails_connector/obj_body.rb +60 -0
- data/lib/rails_connector/obj_class.rb +35 -0
- data/lib/rails_connector/path_conversion.rb +17 -0
- data/lib/rails_connector/permission.rb +39 -0
- data/lib/rails_connector/rack_middlewares.rb +6 -0
- data/lib/rails_connector/s3_blob.rb +67 -0
- data/lib/rails_connector/version.rb +39 -0
- data/lib/rails_connector/workspace.rb +139 -0
- data/lib/rails_connector/workspace_label.rb +10 -0
- data/lib/rails_connector/workspace_selection_middleware.rb +45 -0
- metadata +169 -0
data/README
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
= Infopark Rails Connector
|
2
|
+
|
3
|
+
Infopark Rails Connector allows you to integrate a number of ready-to-run features into your Rails application.
|
4
|
+
|
5
|
+
== Configuring Access to CMS Data
|
6
|
+
|
7
|
+
Place a file named <tt>rails_connector.yml</tt> into your Rails application's <tt>config</tt> directory.
|
8
|
+
The file should contain the following data:
|
9
|
+
|
10
|
+
cms_database_url: "http://<user>:<password>@<db_host>:<db_port>/<db_name>"
|
11
|
+
aws_access_key_id: "<amazon access key id>"
|
12
|
+
aws_secret_access_key: "<amazon secret access key>"
|
13
|
+
s3_bucket_name: "<s3_bucket_name>"
|
14
|
+
search: "http://<user>:<password>@<search_host>:<search_port>"
|
15
|
+
|
16
|
+
|
17
|
+
== Enabling Addons
|
18
|
+
Here is how all available addons would be enabled in the <tt>rails_connector.rb</tt> initializer file of your application:
|
19
|
+
|
20
|
+
RailsConnector::Configuration.enable(
|
21
|
+
:comments,
|
22
|
+
:google_analytics,
|
23
|
+
:infopark_tracking,
|
24
|
+
:omc,
|
25
|
+
:pdf_generator,
|
26
|
+
:ratings,
|
27
|
+
:rss,
|
28
|
+
:search,
|
29
|
+
:seo_sitemap,
|
30
|
+
:time_machine
|
31
|
+
)
|
32
|
+
|
33
|
+
== Layout Helpers
|
34
|
+
Some of those functions depend on html or javascript code being included in the application layout.
|
35
|
+
For example, Google Analytics does only track pageviews if their javascript snippet is included
|
36
|
+
at the bottom of the html body.
|
37
|
+
|
38
|
+
Rails Connector provides two helpers which manage that kind of layout code,
|
39
|
+
switching on or off parts depending on what features are enabled or not:
|
40
|
+
|
41
|
+
rails_connector_header_tags
|
42
|
+
|
43
|
+
This helper needs to be called inside the html head tag.
|
44
|
+
|
45
|
+
rails_connector_after_content_tags
|
46
|
+
|
47
|
+
That helper is to be put right before the closing body tag.
|
48
|
+
|
49
|
+
== Generators
|
50
|
+
Infopark Rails Connector comes with three generators to help you get up and running quickly:
|
51
|
+
|
52
|
+
<tt>rails generate rails_connector:install</tt>
|
53
|
+
|
54
|
+
Adds Time machine assets, a +rails_connector.rb+ initializer and rake tasks specific to the gem to your application.
|
55
|
+
|
56
|
+
<tt>rails generate rails_connector:comments</tt>
|
57
|
+
|
58
|
+
Specific to the +comments+ feature. Adds a comments migration to your application.
|
59
|
+
|
60
|
+
<tt>rails generate rails_connector:ratings</tt>
|
61
|
+
|
62
|
+
Specific to the +ratings+ feature. Adds a ratings migration to your application as well as an example stylesheet and background image.
|
63
|
+
|
64
|
+
== Infopark Tracking
|
65
|
+
|
66
|
+
If you enable the infopark_tracking, please refer to the OMC documentation for further details,
|
67
|
+
e.g. Apache logging options.
|
68
|
+
|
69
|
+
== Seo Sitemap
|
70
|
+
|
71
|
+
The default implementation for the seo sitemap will feature all files from your CMS in the generated <tt>sitemap.xml</tt>. For a large number of files, this can slow down your server. Please refer to <tt>RailsConnector::SEO::ClassMethods#find_all_for_sitemap</tt> for advise on optimization.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails_connector/rack_middlewares'
|
2
|
+
require 'rails_connector/couchdb_views_source_path'
|
3
|
+
require 'rails_connector/core_extensions'
|
4
|
+
require "rails_connector/errors"
|
5
|
+
|
6
|
+
module RailsConnector
|
7
|
+
def self.autoload_all_sources
|
8
|
+
source_files = Dir.glob(File.expand_path("../rails_connector/*.rb", __FILE__)).map do |file|
|
9
|
+
File.basename(file)
|
10
|
+
end
|
11
|
+
|
12
|
+
source_files.each do |file|
|
13
|
+
name = file.gsub(".rb", "")
|
14
|
+
autoload name.camelcase, "rails_connector/#{name}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
autoload_all_sources
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#:enddoc:
|
2
|
+
module RailsConnector
|
3
|
+
|
4
|
+
class Attribute < CmsBaseModel
|
5
|
+
def self.type_of(name)
|
6
|
+
attribute = attribute_cache[name.to_s]
|
7
|
+
attribute.type if attribute
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.reset_cache
|
11
|
+
@attribute_cache = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
attribute_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def type
|
19
|
+
attribute_type.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
private
|
24
|
+
|
25
|
+
def attribute_cache
|
26
|
+
@attribute_cache ||= find(:all).each_with_object({}) do |attr, map|
|
27
|
+
map[attr.name] = attr
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
extend ClassMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "aws-sdk"
|
2
|
+
|
3
|
+
module RailsConnector
|
4
|
+
|
5
|
+
class Blob
|
6
|
+
|
7
|
+
def self.find(id, options)
|
8
|
+
new(blob_class.find(id, options))
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.config
|
12
|
+
@config or raise "Blob storage has not been configured yet"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configure(spec)
|
16
|
+
@config = spec.symbolize_keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.blob_class
|
20
|
+
@blob_class ||= blob_class_from_config
|
21
|
+
end
|
22
|
+
|
23
|
+
# for testing purposes
|
24
|
+
def self.reset_blob_class_cache # :nodoc:
|
25
|
+
@blob_class = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.blob_class_from_config
|
29
|
+
blob_class =
|
30
|
+
case config[:type]
|
31
|
+
when "s3"
|
32
|
+
S3Blob
|
33
|
+
when "couch"
|
34
|
+
CouchBlob
|
35
|
+
else
|
36
|
+
raise "Unsupported blob storage type #{config[:type]}"
|
37
|
+
end
|
38
|
+
blob_class.configure(config)
|
39
|
+
blob_class
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :instance
|
43
|
+
|
44
|
+
def initialize(blob_instance)
|
45
|
+
@instance = blob_instance
|
46
|
+
end
|
47
|
+
|
48
|
+
def id
|
49
|
+
instance.id
|
50
|
+
end
|
51
|
+
|
52
|
+
def url
|
53
|
+
instance.url
|
54
|
+
end
|
55
|
+
|
56
|
+
def content_type
|
57
|
+
instance.content_type
|
58
|
+
end
|
59
|
+
|
60
|
+
def length
|
61
|
+
instance.length
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end # module RailsConnector
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#:enddoc:
|
2
|
+
module RailsConnector
|
3
|
+
|
4
|
+
class Cache
|
5
|
+
@cache = {}
|
6
|
+
@context = false
|
7
|
+
|
8
|
+
def self.begin_context
|
9
|
+
@context = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.end_context
|
13
|
+
@context = false
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.in_context?
|
18
|
+
@context
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.load(key)
|
22
|
+
@cache[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.store(key, value)
|
26
|
+
@cache[key] = value if @context
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.reset
|
30
|
+
@cache.clear
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end # module RailsConnector
|
35
|
+
|
@@ -0,0 +1,177 @@
|
|
1
|
+
#:enddoc:
|
2
|
+
module RailsConnector
|
3
|
+
|
4
|
+
class Chain
|
5
|
+
# the id of the content cache to use, or nil
|
6
|
+
attr_reader :content_cache
|
7
|
+
|
8
|
+
# a chain of patch operations that can be used to construct the target workspace
|
9
|
+
# each chain member is an array with two elements.
|
10
|
+
# the first element denotes the patch type (-1, 1), where
|
11
|
+
# 1 => apply the workspace
|
12
|
+
# -1 => revert the workspace
|
13
|
+
# and the second element is the workspace.
|
14
|
+
attr_reader :patches
|
15
|
+
|
16
|
+
def self.build_for(workspace, content_cache = nil)
|
17
|
+
if content_cache && content_cache.workspace
|
18
|
+
patches = patches_to(workspace, content_cache.workspace)
|
19
|
+
|
20
|
+
if content_cache.transfer_target
|
21
|
+
if patches.present? && patches.first.second == content_cache.transfer_target
|
22
|
+
# the transfer target is already included in the chain - no extra patch needed!
|
23
|
+
else
|
24
|
+
patches.unshift([-1, content_cache.transfer_target])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
new(content_cache, patches)
|
29
|
+
else
|
30
|
+
new(nil, patches_to(workspace))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.query_counter
|
35
|
+
@query_counter || 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.count_query
|
39
|
+
@query_counter = query_counter + 1
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(content_cache, patches)
|
43
|
+
@content_cache = content_cache
|
44
|
+
@patches = patches
|
45
|
+
end
|
46
|
+
|
47
|
+
# performs a workspace aware query on the cms database.
|
48
|
+
#
|
49
|
+
# returns a hash of results from the query.
|
50
|
+
# the keys are the criterion by which the query results are grouped (typically the OBJ ID).
|
51
|
+
# the values are tuples where
|
52
|
+
# the first element denotes the diff type and
|
53
|
+
# the second the document rows as returned by couchdb.
|
54
|
+
def query(index, key, is_compound_index)
|
55
|
+
self.class.count_query
|
56
|
+
result_map = perform_queries(index, key, is_compound_index)
|
57
|
+
version_map = result_map.merge(result_map) do |ignore, results|
|
58
|
+
results.map {|row| Version.new(row) }
|
59
|
+
end
|
60
|
+
workspace_aware_results(version_map)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# calculate a list of patches to get to the destionation workspace.
|
66
|
+
# if origin is given, the list will start at origin.
|
67
|
+
# otherwise the list will start at the initial workspace.
|
68
|
+
def self.patches_to(destination, origin = nil)
|
69
|
+
if destination.nil? && origin.nil?
|
70
|
+
[]
|
71
|
+
elsif origin.nil? || origin.generation < destination.generation
|
72
|
+
patches_to(destination.base_workspace, origin) + [[1, destination]]
|
73
|
+
elsif destination.nil? || origin.generation > destination.generation
|
74
|
+
[[-1, origin]] + patches_to(destination, origin.base_workspace)
|
75
|
+
elsif origin != destination
|
76
|
+
[[-1, origin]] + patches_to(destination.base_workspace, origin.base_workspace) + [[1, destination]]
|
77
|
+
else # origin == destination
|
78
|
+
[]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def perform_queries(index, key, is_compound_index)
|
83
|
+
result_map = {}
|
84
|
+
|
85
|
+
query_options = {
|
86
|
+
:hash_value => key,
|
87
|
+
}
|
88
|
+
query_options[:range_value_extender] = "/" if is_compound_index
|
89
|
+
|
90
|
+
if content_cache.present?
|
91
|
+
result_map[:content_cache] = CmsBaseModel.query_index(:obj, index, query_options.merge({
|
92
|
+
:range_value => "cc/#{content_cache.id}",
|
93
|
+
}))
|
94
|
+
end
|
95
|
+
if patches.present?
|
96
|
+
result_map[:patches] = CmsBaseModel.query_index(:obj, index, query_options.merge({
|
97
|
+
:range_value => workspace_range,
|
98
|
+
}))
|
99
|
+
end
|
100
|
+
|
101
|
+
result_map
|
102
|
+
end
|
103
|
+
|
104
|
+
# filter out those results that aren't on the chain.
|
105
|
+
# order the remaining results according to their chain position
|
106
|
+
def order_results_by_chain(result_map)
|
107
|
+
results = []
|
108
|
+
|
109
|
+
if result_map[:content_cache].present?
|
110
|
+
results << [1, result_map[:content_cache]]
|
111
|
+
end
|
112
|
+
if result_map[:patches].present?
|
113
|
+
results_by_workspace = result_map[:patches].group_by do |version|
|
114
|
+
version.workspace_id
|
115
|
+
end
|
116
|
+
patches.each do |(patch_diff_type, workspace)|
|
117
|
+
results_for_workspace = results_by_workspace[workspace.id]
|
118
|
+
results << [patch_diff_type, results_for_workspace] if results_for_workspace
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
results
|
123
|
+
end
|
124
|
+
|
125
|
+
# group the results by obj id
|
126
|
+
# i.e. calculate a separate chain for each obj
|
127
|
+
def group_results(result_map)
|
128
|
+
results_by_chain = order_results_by_chain(result_map)
|
129
|
+
|
130
|
+
result_index = {}
|
131
|
+
results_by_chain.each do |(patch_diff_type, versions)|
|
132
|
+
versions.each do |version|
|
133
|
+
id = version.obj_id
|
134
|
+
result_index[id] ||= []
|
135
|
+
result_index[id] << [patch_diff_type, version]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
result_index
|
140
|
+
end
|
141
|
+
|
142
|
+
# apply the diff chain for each of obj
|
143
|
+
# to determine which of them are included in the final workspace
|
144
|
+
def workspace_aware_results(result_map)
|
145
|
+
result_index = {}
|
146
|
+
|
147
|
+
# note: using merge as a map function for hashes
|
148
|
+
group_results(result_map).each do |id, apply_chain|
|
149
|
+
final_existance = apply_chain.inject(0) do |temp_existance, (patch_diff_type, version)|
|
150
|
+
# remember: -1 (revert) multiplied with -1 (revert) equals 1 (apply)
|
151
|
+
# and: +1 (create) added to -1 (delete) equals 0 (not existant)
|
152
|
+
temp_existance + patch_diff_type * version.diff
|
153
|
+
end
|
154
|
+
if final_existance > 0
|
155
|
+
patch_diff, version = apply_chain.last
|
156
|
+
result_index[id] = version.obj_data(patch_diff)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
result_index
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def workspace_range
|
166
|
+
@workspace_range ||=
|
167
|
+
begin
|
168
|
+
sorted_keys = patches.map do |(type, workspace)|
|
169
|
+
"#{"%010d" % workspace.generation}/#{workspace.id}"
|
170
|
+
end.sort
|
171
|
+
Range.new(sorted_keys.first, sorted_keys.last)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
end # module RailsConnector
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "kvom"
|
2
|
+
require "kvom/adapter/couchdb_adapter"
|
3
|
+
require "kvom/adapter/dynamodb_adapter"
|
4
|
+
|
5
|
+
module RailsConnector
|
6
|
+
|
7
|
+
# This is the abstract class from which all CMS models derive.
|
8
|
+
#
|
9
|
+
class CmsBaseModel < Kvom::Base #:nodoc:
|
10
|
+
class << self
|
11
|
+
def instance_name=(ignore) # :nodoc:
|
12
|
+
# this method is here only for compatibility with the fiona connector.
|
13
|
+
end
|
14
|
+
|
15
|
+
def adapter
|
16
|
+
@@cms_database_adapter
|
17
|
+
end
|
18
|
+
|
19
|
+
def configure_database(connection_spec)
|
20
|
+
@@cms_database_adapter =
|
21
|
+
case connection_spec["type"]
|
22
|
+
when "couch"
|
23
|
+
Kvom::Adapter::CouchdbAdapter.new(connection_spec)
|
24
|
+
when "dynamo"
|
25
|
+
Kvom::Adapter::DynamodbAdapter.new(connection_spec)
|
26
|
+
else
|
27
|
+
raise "Unexpected database type #{connection_spec["type"]}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def query_index(model, index, params = {})
|
32
|
+
hash_value = "#{model}/#{index}/#{params[:hash_value]}"
|
33
|
+
range_value = params[:range_value] || ""
|
34
|
+
range_value_extender = params[:range_value_extender]
|
35
|
+
|
36
|
+
# up to this point: backend independent
|
37
|
+
|
38
|
+
# this may be backend dependent (suffix character)
|
39
|
+
if range_value_extender
|
40
|
+
start_key, end_key =
|
41
|
+
case range_value
|
42
|
+
when Range
|
43
|
+
[range_value.begin, range_value.end]
|
44
|
+
else
|
45
|
+
[range_value, range_value]
|
46
|
+
end
|
47
|
+
range_value = Range.new(start_key + range_value_extender, end_key + range_value_extender + "~")
|
48
|
+
end
|
49
|
+
|
50
|
+
adapter.query(hash_value, range_value)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def save
|
56
|
+
raise "CmsBaseModel is read-only"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#:enddoc:
|
2
|
+
module RailsConnector
|
3
|
+
|
4
|
+
class ContentCache < CmsBaseModel
|
5
|
+
self.key_prefix = "cc"
|
6
|
+
|
7
|
+
property :workspace_id
|
8
|
+
property :transferring_to
|
9
|
+
|
10
|
+
# returns the workspace that is represented by this content cache.
|
11
|
+
# returns nil if the workspace cannot be found.
|
12
|
+
def workspace
|
13
|
+
@workspace ||= Workspace.find_by_id(workspace_id)
|
14
|
+
end
|
15
|
+
|
16
|
+
# returns the workspace that this content cache is currently transferring to.
|
17
|
+
# returns nil if no transfer is taking place or if the workspace cannot be found.
|
18
|
+
def transfer_target
|
19
|
+
@transfer_target ||= transferring_to ? Workspace.find_by_id(transferring_to) : nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end # module RailsConnector
|
24
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support/core_ext/module/attr_internal'
|
2
|
+
|
3
|
+
module RailsConnector
|
4
|
+
|
5
|
+
module ControllerRuntime
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
attr_internal :rc_runtime
|
11
|
+
|
12
|
+
def cleanup_view_runtime
|
13
|
+
rc_rt_before_render = RailsConnector::LogSubscriber.reset_runtime
|
14
|
+
runtime = super
|
15
|
+
rc_rt_after_render = RailsConnector::LogSubscriber.reset_runtime
|
16
|
+
self.rc_runtime = rc_rt_before_render + rc_rt_after_render
|
17
|
+
runtime - rc_rt_after_render
|
18
|
+
end
|
19
|
+
|
20
|
+
def append_info_to_payload(payload)
|
21
|
+
super
|
22
|
+
payload[:rc_runtime] = rc_runtime
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def log_process_action(payload)
|
27
|
+
messages, rc_runtime = super, payload[:rc_runtime]
|
28
|
+
messages << ("RailsConnector: %.1fms" % rc_runtime.to_f)
|
29
|
+
messages
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end # module RailsConnector
|
35
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RailsConnector
|
2
|
+
|
3
|
+
class CouchBlob
|
4
|
+
|
5
|
+
def self.find(id, options)
|
6
|
+
cache_key = [:blob, id]
|
7
|
+
attachment = Cache.load(cache_key)
|
8
|
+
unless attachment
|
9
|
+
ActiveSupport::Notifications.instrumenter.instrument("cms_load.rails_connector",
|
10
|
+
:name => "Blob Load", :index => "blobs", :keys => [options[:context]]
|
11
|
+
) do
|
12
|
+
attachment = CmsBaseModel.adapter.blob_attachment(id)
|
13
|
+
Cache.store(cache_key, attachment)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
new(id, attachment)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configure(spec)
|
20
|
+
# connection is taken from CmsBaseModel on demand
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :id, :attachment
|
24
|
+
|
25
|
+
def initialize(id, attachment)
|
26
|
+
@id = id
|
27
|
+
@attachment = attachment
|
28
|
+
end
|
29
|
+
|
30
|
+
def url
|
31
|
+
CmsBaseModel.adapter.blob_url(id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def content_type
|
35
|
+
attachment["content_type"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def length
|
39
|
+
attachment['length']
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end # module RailsConnector
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RailsConnector
|
2
|
+
|
3
|
+
# Adds support for string columns which contain ISO dates
|
4
|
+
module DateAttribute #:nodoc: all
|
5
|
+
module ClassMethods
|
6
|
+
def date_attribute(*names)
|
7
|
+
names.each do |name|
|
8
|
+
module_eval %Q!
|
9
|
+
def #{name}
|
10
|
+
DateAttribute.parse(#{name}_before_type_cast) unless #{name}_before_type_cast.nil?
|
11
|
+
end
|
12
|
+
!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.parse(iso_date_time)
|
22
|
+
Time.from_iso(iso_date_time).localtime
|
23
|
+
rescue ArgumentError
|
24
|
+
raise "The value is not a valid ISO date time: #{iso_date_time.inspect}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|