naranya_ecm-sdk 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +33 -0
  6. data/Rakefile +1 -0
  7. data/dev/create_cvm.rb +31 -0
  8. data/dev/init.rb +2 -0
  9. data/dev/load_cvm.rb +12 -0
  10. data/dev/search.rb +3 -0
  11. data/lib/naranya_ecm/behaviors/localizable.rb +41 -0
  12. data/lib/naranya_ecm/behaviors/timestampable.rb +22 -0
  13. data/lib/naranya_ecm/behaviors.rb +9 -0
  14. data/lib/naranya_ecm/cache/key.rb +73 -0
  15. data/lib/naranya_ecm/cache/methods.rb +78 -0
  16. data/lib/naranya_ecm/cache.rb +9 -0
  17. data/lib/naranya_ecm/has_many_patch.rb +105 -0
  18. data/lib/naranya_ecm/lifecycles/content_lifecycle.rb +36 -0
  19. data/lib/naranya_ecm/lifecycles/lifecycleable.rb +43 -0
  20. data/lib/naranya_ecm/lifecycles/version_lifecycle.rb +75 -0
  21. data/lib/naranya_ecm/lifecycles.rb +10 -0
  22. data/lib/naranya_ecm/models/category.rb +42 -0
  23. data/lib/naranya_ecm/models/content.rb +102 -0
  24. data/lib/naranya_ecm/models/content_version.rb +38 -0
  25. data/lib/naranya_ecm/models/download_authorization.rb +55 -0
  26. data/lib/naranya_ecm/models/lifecycle.rb +36 -0
  27. data/lib/naranya_ecm/models/media_resource.rb +33 -0
  28. data/lib/naranya_ecm/models.rb +13 -0
  29. data/lib/naranya_ecm/search/hit.rb +17 -0
  30. data/lib/naranya_ecm/search/methods.rb +26 -0
  31. data/lib/naranya_ecm/search/query.rb +296 -0
  32. data/lib/naranya_ecm/search/results.rb +161 -0
  33. data/lib/naranya_ecm/search.rb +14 -0
  34. data/lib/naranya_ecm/site_from_configuration.rb +15 -0
  35. data/lib/naranya_ecm-sdk/version.rb +3 -0
  36. data/lib/naranya_ecm-sdk.rb +84 -0
  37. data/naranya_ecm-sdk.gemspec +33 -0
  38. data/spec/models/category_spec.rb +11 -0
  39. data/spec/models/content_spec.rb +11 -0
  40. data/spec/models/content_version_spec.rb +7 -0
  41. data/spec/models/download_authorization.rb +7 -0
  42. data/spec/models/media_spec.rb +7 -0
  43. data/spec/models/module_spec.rb +18 -0
  44. data/spec/spec_helper.rb +47 -0
  45. data/spec/support/naranya_ecms_shared_specs.rb +33 -0
  46. metadata +236 -0
@@ -0,0 +1,42 @@
1
+ require 'active_resource'
2
+
3
+ module NaranyaEcm
4
+ module Models
5
+ class Category < ::ActiveResource::Base
6
+
7
+ include SiteFromConfiguration
8
+
9
+ include NaranyaEcm::Behaviors::Timestampable
10
+
11
+ include NaranyaEcm::Search::Methods
12
+ include NaranyaEcm::Cache::Methods
13
+
14
+ class NameTranslations < ::ActiveResource::Base
15
+ include NaranyaEcm::Behaviors::Localizable
16
+ end
17
+
18
+ self.include_root_in_json = true
19
+
20
+ # Definir los atributos conocidos:
21
+ schema do
22
+ # define each attribute separately
23
+ string :id, :code, :created_at, :updated_at
24
+
25
+ # unsupported types should be left as strings
26
+ # overload the accessor methods if you need to convert them
27
+ #attribute 'created_at', 'string'
28
+ attribute 'name_translations', 'string'
29
+ attribute 'description_translations', 'string'
30
+
31
+ end
32
+
33
+ delegate :name, to: :name_translations
34
+ delegate :description, to: :title_translations
35
+
36
+ def contents
37
+ Content.find :all, params: { category_id: self.id }
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,102 @@
1
+ require 'active_resource'
2
+
3
+ module NaranyaEcm
4
+ module Models
5
+ class Content < ::ActiveResource::Base
6
+
7
+ include ActiveModel::Dirty
8
+
9
+ include SiteFromConfiguration
10
+
11
+ include NaranyaEcm::Search::Methods
12
+ include NaranyaEcm::Cache::Methods
13
+
14
+ #include HasManyPatch
15
+
16
+ class TitleTranslations < ::ActiveResource::Base
17
+ include NaranyaEcm::Behaviors::Localizable
18
+ end
19
+
20
+ class DescriptionTranslations < ::ActiveResource::Base
21
+ include NaranyaEcm::Behaviors::Localizable
22
+ end
23
+
24
+ class ContentRating < HashWithIndifferentAccess
25
+ def initialize(*args)
26
+ super()
27
+ given_attrs = args.shift
28
+ given_attrs.each { |k,v| self[k] = v } unless given_attrs.nil?
29
+ end
30
+ end
31
+
32
+ self.include_root_in_json = true
33
+
34
+ has_many :versions, class_name: "naranya_ecm/models/content_version"
35
+
36
+ has_one :current_version, class_name: "naranya_ecm/models/content_version"
37
+ belongs_to :category, class_name: "naranya_ecm/models/category"
38
+
39
+ # Definir los atributos conocidos:
40
+ schema do
41
+ # define each attribute separately
42
+ string :id, :type, :lifecycle_name, :lifecycle_state, :content_rating, :created_at, :updated_at
43
+
44
+ string :author
45
+ string :main_url
46
+
47
+ # unsupported types should be left as strings
48
+ # overload the accessor methods if you need to convert them
49
+ #attribute 'created_at', 'string'
50
+ attribute 'title_translations', 'string'
51
+ attribute 'description_translations', 'string'
52
+ attribute 'keywords', 'string'
53
+
54
+ attribute 'media_resources', 'string'
55
+ attribute 'current_version', 'string'
56
+
57
+ # Extra attributes:
58
+ NaranyaEcm.options['extra_attributes']['content'].each do |key, val|
59
+ attribute key, val
60
+ end
61
+
62
+ end
63
+
64
+ schema_attrs = self.schema.keys.map(&:to_sym)
65
+ define_attribute_methods(*schema_attrs)
66
+ schema_attrs.each do |attr_sym|
67
+ # getter:
68
+ define_method(attr_sym) { @attributes[attr_sym] }
69
+ # setter:
70
+ define_method("#{attr_sym}=".to_sym) do |value|
71
+ self.send "#{attr_sym}_will_change!".to_sym unless value == @attributes[attr_sym]
72
+ @attributes[attr_sym] = value
73
+ end
74
+ end
75
+
76
+ delegate :title, to: :title_translations
77
+ delegate :description, to: :description_translations
78
+
79
+ include NaranyaEcm::Behaviors::Timestampable
80
+
81
+ # include NaranyaEcm::Lifecycles::Lifecycleable
82
+ # ##################################################################
83
+ # # StateMachine:
84
+ # state_machine :lifecycle_state, initial: :draft do
85
+
86
+ # state :draft
87
+
88
+ # state :awaiting_validation
89
+
90
+ # state :rejected
91
+
92
+ # state :validated
93
+
94
+ # state :published
95
+
96
+ # state :deactivated
97
+
98
+ # end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,38 @@
1
+ require 'active_resource'
2
+
3
+ module NaranyaEcm
4
+ module Models
5
+ class ContentVersion < ::ActiveResource::Base
6
+
7
+ include SiteFromConfiguration
8
+ include NaranyaEcm::Behaviors::Timestampable
9
+
10
+ self.include_root_in_json = true
11
+
12
+ # Definir los atributos conocidos:
13
+ schema do
14
+ # define each attribute separately
15
+ string :id, :name, :lifecycle_name, :lifecycle_state
16
+
17
+ integer :code
18
+
19
+ end
20
+
21
+ class DescriptionTranslations < ::ActiveResource::Base
22
+ include NaranyaEcm::Behaviors::Localizable
23
+ end
24
+
25
+ delegate :description, to: :description_translations
26
+
27
+ belongs_to :content,
28
+ class_name: "naranya_ecm/models/content"
29
+
30
+ has_many :media_resources,
31
+ class_name: "naranya_ecm/models/media_resource"
32
+
33
+ #include NaranyaEcm::Lifecycles::Lifecycleable
34
+ # include NaranyaEcm::Lifecycles::VersionLifecycle
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,55 @@
1
+ require 'active_resource'
2
+
3
+ module NaranyaEcm
4
+ module Models
5
+ class DownloadAuthorization < ActiveResource::Base
6
+
7
+ class Tokens < ::ActiveResource::Base
8
+ # Delegar el acceso tipo "Enumerable":
9
+ delegate :any?, to: :@attributes
10
+
11
+ # Re-establece el acceso tipo Hash:
12
+ delegate :[], to: :@attributes
13
+ delegate :[]=, to: :@attributes
14
+ self.site = "http://www.example.com"
15
+
16
+ def to_query_params
17
+ @attributes.map { |key, value| "#{key}=#{value}" }.join '&'
18
+ end
19
+ end
20
+
21
+ include SiteFromConfiguration
22
+ include NaranyaEcm::Behaviors::Timestampable
23
+
24
+ self.include_root_in_json = true
25
+
26
+ # Definir los atributos conocidos:
27
+ schema do
28
+ string :id, :media_resource_id, :expires_at, :tokens, :created_at, :updated_at
29
+ end
30
+
31
+ belongs_to :media_resource, class_name: "naranya_ecm/models/media_resource"
32
+
33
+ def media_resource=(given_media_resource)
34
+ @attributes[:media_resource_id] = given_media_resource.id
35
+ end
36
+
37
+ def initialize(attributes = {}, persisted = false)
38
+ if given_media_resource = attributes.delete(:media_resource)
39
+ attributes[:media_resource_id] = given_media_resource.id
40
+ end
41
+ super
42
+ end
43
+
44
+ def authorized_url
45
+ unless @authorized_url
46
+ uri = URI(self.media_resource.downloadable_url)
47
+ uri.query = self.tokens.to_query_params
48
+ @authorized_url = uri.to_s
49
+ end
50
+ @authorized_url
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,36 @@
1
+
2
+ module NaranyaEcm
3
+ module Models
4
+
5
+ # Value Object de Lifecycle de documentos / versiones / media / etc.
6
+ class Lifecycle < String
7
+ include Comparable
8
+
9
+ KNOWN_LIFECYCLES = %w(simple validatable)
10
+ .map { |m| m.to_sym }
11
+ .freeze
12
+
13
+ def is_lifecycle?(given_lifecycle)
14
+ given_lifecycle.to_s == self
15
+ end
16
+
17
+ KNOWN_LIFECYCLES.each do |lifecycle_name|
18
+ define_method("is_#{lifecycle_name}?") do
19
+ is_lifecycle?(lifecycle_name)
20
+ end
21
+ end
22
+
23
+ def method_missing(method_name, *args, &block)
24
+ if method_name.to_s =~ /^is_(\w+)\?$/
25
+ is_lifecycle?($1)
26
+ else
27
+ super # You *must* call super if you don't handle the
28
+ # method, otherwise you'll mess up Ruby's method
29
+ # lookup.
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_resource'
2
+
3
+ module NaranyaEcm
4
+ module Models
5
+ class MediaResource < ActiveResource::Base
6
+
7
+ include SiteFromConfiguration
8
+ include NaranyaEcm::Behaviors::Timestampable
9
+
10
+ self.include_root_in_json = true
11
+
12
+ # Definir los atributos conocidos:
13
+ schema do
14
+ # define each attribute separately
15
+ string :id, :access_type, :type, :role, :downloadable_url
16
+
17
+ string :author
18
+ string :main_url
19
+
20
+ # unsupported types should be left as strings
21
+ # overload the accessor methods if you need to convert them
22
+ #attribute 'created_at', 'string'
23
+ attribute 'created_at', 'string'
24
+ attribute 'updated_at', 'string'
25
+ attribute 'compatible_device_ids', 'string'
26
+ attribute 'compatible_device_oses', 'string'
27
+ attribute 'compatible_device_os_versions', 'string'
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_support/dependencies/autoload'
2
+
3
+ module NaranyaEcm
4
+ module Models
5
+ extend ActiveSupport::Autoload
6
+ autoload :Category
7
+ autoload :Content
8
+ autoload :ContentVersion
9
+ autoload :Lifecycle
10
+ autoload :DownloadAuthorization
11
+ autoload :MediaResource
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module NaranyaEcm
2
+ module Search
3
+ class Hit
4
+ attr_reader :id, :type, :updated_at, :order, :score
5
+ attr_accessor :cache_key
6
+ def initialize(attributes={})
7
+ @id = attributes.delete :_id
8
+ @type = attributes.delete :_type
9
+ @score = attributes.delete :_score
10
+
11
+ source = attributes.delete :_source
12
+ @updated_at = source.has_key?(:updated_at) ? source.delete(:updated_at).to_datetime : nil
13
+ @order = attributes.delete :order
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'active_support/concern'
2
+ module NaranyaEcm
3
+ module Search
4
+ module Methods
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_reader :search_score, :search_order
9
+ end
10
+
11
+ def merge_search_hit_data(hit)
12
+ @search_score, @search_order = hit.score, hit.order
13
+ end
14
+
15
+ module ClassMethods
16
+ def search(*args)
17
+ Results.new(*args, self)
18
+ end
19
+ def kick_search(terms, options={})
20
+ search(Query.build_searchkick_like(terms, options))
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,296 @@
1
+ module NaranyaEcm
2
+ module Search
3
+ class Query < Hash
4
+ class << self
5
+ def build_searchkick_like(term, options = {})
6
+ if term.is_a?(Hash)
7
+ options = term
8
+ term = nil
9
+ else
10
+ term = term.to_s
11
+ end
12
+
13
+ fields =
14
+ if options[:fields]
15
+ if options[:autocomplete]
16
+ options[:fields].map{|f| "#{f}.autocomplete" }
17
+ else
18
+ options[:fields].map do |value|
19
+ k, v = value.is_a?(Hash) ? value.to_a.first : [value, :word]
20
+ "#{k}.#{v == :word ? "analyzed" : v}"
21
+ end
22
+ end
23
+ else
24
+ ["_all"]
25
+ end
26
+
27
+ operator = options[:partial] ? "or" : "and"
28
+
29
+ # model and eagar loading
30
+ load = options[:load].nil? ? true : options[:load]
31
+ load = (options[:include] ? {include: options[:include]} : true) if load
32
+
33
+ # pagination
34
+ page = [options[:page].to_i, 1].max
35
+ per_page = (options[:limit] || options[:per_page] || 10).to_i
36
+ offset = options[:offset] || (page - 1) * per_page
37
+
38
+ all = term == "*"
39
+
40
+ if options[:query]
41
+ payload = options[:query]
42
+ elsif options[:similar]
43
+ payload = {
44
+ more_like_this: {
45
+ fields: fields,
46
+ like_text: term,
47
+ min_doc_freq: 1,
48
+ min_term_freq: 1,
49
+ analyzer: "searchkick_search2"
50
+ }
51
+ }
52
+ elsif all
53
+ payload = {
54
+ match_all: {}
55
+ }
56
+ else
57
+ if options[:autocomplete]
58
+ payload = {
59
+ multi_match: {
60
+ fields: fields,
61
+ query: term,
62
+ analyzer: "searchkick_autocomplete_search"
63
+ }
64
+ }
65
+ else
66
+ queries = []
67
+ fields.each do |field|
68
+ if field == "_all" or field.end_with?(".analyzed")
69
+ shared_options = {
70
+ fields: [field],
71
+ query: term,
72
+ use_dis_max: false,
73
+ operator: operator,
74
+ cutoff_frequency: 0.001
75
+ }
76
+ queries.concat [
77
+ {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search")},
78
+ {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search2")}
79
+ ]
80
+ if options[:misspellings] != false
81
+ distance = (options[:misspellings].is_a?(Hash) && options[:misspellings][:distance]) || 1
82
+ queries.concat [
83
+ {multi_match: shared_options.merge(fuzziness: distance, max_expansions: 3, analyzer: "searchkick_search")},
84
+ {multi_match: shared_options.merge(fuzziness: distance, max_expansions: 3, analyzer: "searchkick_search2")}
85
+ ]
86
+ end
87
+ else
88
+ queries << {
89
+ multi_match: {
90
+ fields: [field],
91
+ query: term,
92
+ analyzer: "searchkick_autocomplete_search"
93
+ }
94
+ }
95
+ end
96
+ end
97
+
98
+ payload = {
99
+ dis_max: {
100
+ queries: queries
101
+ }
102
+ }
103
+ end
104
+
105
+ end
106
+
107
+ custom_filters = []
108
+
109
+ if options[:boost]
110
+ custom_filters << {
111
+ filter: {
112
+ exists: {
113
+ field: options[:boost]
114
+ }
115
+ },
116
+ script: "log(doc['#{options[:boost]}'].value + 2.718281828)"
117
+ }
118
+ end
119
+
120
+ if custom_filters.any?
121
+ payload = {
122
+ custom_filters_score: {
123
+ query: payload,
124
+ filters: custom_filters,
125
+ score_mode: "total"
126
+ }
127
+ }
128
+ end
129
+
130
+ payload = {
131
+ query: payload,
132
+ size: per_page,
133
+ from: offset
134
+ }
135
+ payload[:explain] = options[:explain] if options[:explain]
136
+
137
+ # order
138
+ if options[:order]
139
+ order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
140
+ payload[:sort] = Hash[ order.map{|k, v| [k.to_s == "id" ? :_id : k, v] } ]
141
+ end
142
+
143
+ term_filters =
144
+ proc do |field, value|
145
+ if value.is_a?(Array) # in query
146
+ if value.any?
147
+ {or: value.map{|v| term_filters.call(field, v) }}
148
+ else
149
+ {terms: {field => value}} # match nothing
150
+ end
151
+ elsif value.nil?
152
+ {missing: {"field" => field, existence: true, null_value: true}}
153
+ else
154
+ {term: {field => value}}
155
+ end
156
+ end
157
+
158
+ # where
159
+ where_filters =
160
+ proc do |where|
161
+ filters = []
162
+ (where || {}).each do |field, value|
163
+ field = :_id if field.to_s == "id"
164
+
165
+ if field == :or
166
+ value.each do |or_clause|
167
+ filters << {or: or_clause.map{|or_statement| {and: where_filters.call(or_statement)} }}
168
+ end
169
+ else
170
+ # expand ranges
171
+ if value.is_a?(Range)
172
+ value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last}
173
+ end
174
+
175
+ if value.is_a?(Hash)
176
+ if value[:near]
177
+ filters << {
178
+ geo_distance: {
179
+ field => value.delete(:near).map(&:to_f).reverse,
180
+ distance: value.delete(:within) || "50mi"
181
+ }
182
+ }
183
+ end
184
+
185
+ if value[:top_left]
186
+ filters << {
187
+ geo_bounding_box: {
188
+ field => {
189
+ top_left: value.delete(:top_left).map(&:to_f).reverse,
190
+ bottom_right: value.delete(:bottom_right).map(&:to_f).reverse
191
+ }
192
+ }
193
+ }
194
+ end
195
+
196
+ value.each do |op, op_value|
197
+ if op == :not # not equal
198
+ filters << {not: term_filters.call(field, op_value)}
199
+ elsif op == :all
200
+ filters << {terms: {field => op_value, execution: "and"}}
201
+ else
202
+ range_query =
203
+ case op
204
+ when :gt
205
+ {from: op_value, include_lower: false}
206
+ when :gte
207
+ {from: op_value, include_lower: true}
208
+ when :lt
209
+ {to: op_value, include_upper: false}
210
+ when :lte
211
+ {to: op_value, include_upper: true}
212
+ else
213
+ raise "Unknown where operator"
214
+ end
215
+ filters << {range: {field => range_query}}
216
+ end
217
+ end
218
+ else
219
+ filters << term_filters.call(field, value)
220
+ end
221
+ end
222
+ end
223
+ filters
224
+ end
225
+
226
+ # filters
227
+ filters = where_filters.call(options[:where])
228
+ if filters.any?
229
+ payload[:filter] = {
230
+ and: filters
231
+ }
232
+ end
233
+
234
+ # facets
235
+ facet_limits = {}
236
+ if options[:facets]
237
+ facets = options[:facets] || {}
238
+ if facets.is_a?(Array) # convert to more advanced syntax
239
+ facets = Hash[ facets.map{|f| [f, {}] } ]
240
+ end
241
+
242
+ payload[:facets] = {}
243
+ facets.each do |field, facet_options|
244
+ # ask for extra facets due to
245
+ # https://github.com/elasticsearch/elasticsearch/issues/1305
246
+
247
+ if facet_options[:ranges]
248
+ payload[:facets][field] = {
249
+ range: {
250
+ field.to_sym => facet_options[:ranges]
251
+ }
252
+ }
253
+ else
254
+ payload[:facets][field] = {
255
+ terms: {
256
+ field: field,
257
+ size: facet_options[:limit] ? facet_options[:limit] + 150 : 100000
258
+ }
259
+ }
260
+ end
261
+
262
+ facet_limits[field] = facet_options[:limit] if facet_options[:limit]
263
+
264
+ # offset is not possible
265
+ # http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
266
+
267
+ facet_filters = where_filters.call(facet_options[:where])
268
+ if facet_filters.any?
269
+ payload[:facets][field][:facet_filter] = {
270
+ and: {
271
+ filters: facet_filters
272
+ }
273
+ }
274
+ end
275
+ end
276
+ end
277
+
278
+ # highlight
279
+ if options[:highlight]
280
+ payload[:highlight] = {
281
+ fields: Hash[ fields.map{|f| [f, {}] } ]
282
+ }
283
+ if options[:highlight].is_a?(Hash) and tag = options[:highlight][:tag]
284
+ payload[:highlight][:pre_tags] = [tag]
285
+ payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
286
+ end
287
+ end
288
+
289
+ payload[:_source] = 'updated_at'
290
+
291
+ self.new.merge payload
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end