naranya_ecm-sdk 0.0.14 → 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/naranya_ecm/behaviors/localizable.rb +27 -31
- data/lib/naranya_ecm/behaviors/mediable.rb +25 -0
- data/lib/naranya_ecm/behaviors/timestampable.rb +18 -15
- data/lib/naranya_ecm/cache/key.rb +32 -56
- data/lib/naranya_ecm/cache/methods.rb +50 -63
- data/lib/naranya_ecm/lifecycles/content_lifecycle.rb +39 -16
- data/lib/naranya_ecm/models/category.rb +11 -30
- data/lib/naranya_ecm/models/content.rb +73 -73
- data/lib/naranya_ecm/models/content_version.rb +50 -29
- data/lib/naranya_ecm/models/download_authorization.rb +22 -26
- data/lib/naranya_ecm/models/media_resource.rb +49 -15
- data/lib/naranya_ecm/rest/associations.rb +156 -0
- data/lib/naranya_ecm/rest/client.rb +4 -0
- data/lib/naranya_ecm/rest/errors.rb +53 -0
- data/lib/naranya_ecm/rest/finder_methods.rb +50 -0
- data/lib/naranya_ecm/rest/model.rb +215 -0
- data/lib/naranya_ecm/rest/persistence.rb +122 -0
- data/lib/naranya_ecm/rest/relation.rb +54 -0
- data/lib/naranya_ecm/search/hit.rb +19 -14
- data/lib/naranya_ecm/search/methods.rb +18 -20
- data/lib/naranya_ecm/search/query.rb +229 -230
- data/lib/naranya_ecm/search/results.rb +136 -139
- data/lib/naranya_ecm-sdk/version.rb +1 -1
- data/lib/naranya_ecm-sdk.rb +54 -13
- data/naranya_ecm-sdk.gemspec +1 -1
- data/spec/models/category_spec.rb +7 -2
- data/spec/models/content_spec.rb +11 -2
- data/spec/models/media_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/support/naranya_ecms_shared_specs.rb +0 -12
- metadata +15 -19
- data/lib/naranya_ecm/behaviors/resourceable.rb +0 -22
- data/lib/naranya_ecm/behaviors.rb +0 -10
- data/lib/naranya_ecm/cache.rb +0 -9
- data/lib/naranya_ecm/has_many_patch.rb +0 -105
- data/lib/naranya_ecm/lifecycles/lifecycleable.rb +0 -43
- data/lib/naranya_ecm/lifecycles/version_lifecycle.rb +0 -75
- data/lib/naranya_ecm/lifecycles.rb +0 -10
- data/lib/naranya_ecm/models/embedded_hash.rb +0 -10
- data/lib/naranya_ecm/models/embedded_localized_hash.rb +0 -38
- data/lib/naranya_ecm/models/lifecycle.rb +0 -34
- data/lib/naranya_ecm/models.rb +0 -15
- data/lib/naranya_ecm/search.rb +0 -14
@@ -1,36 +1,57 @@
|
|
1
|
-
require 'active_resource'
|
2
1
|
|
3
|
-
module NaranyaEcm
|
4
|
-
class ContentVersion
|
2
|
+
module NaranyaEcm
|
3
|
+
class ContentVersion
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
include NaranyaEcm::Behaviors::Resourceable
|
5
|
+
include NaranyaEcm.document_module
|
9
6
|
include NaranyaEcm::Behaviors::Timestampable
|
7
|
+
include NaranyaEcm::Cache::Methods
|
8
|
+
include NaranyaEcm::Behaviors::Mediable
|
9
|
+
|
10
|
+
# Description (localized):
|
11
|
+
# A localized string that describes an overview of the significant changes
|
12
|
+
# particular in this version. Used to list the most significant updates, fixes, etc.
|
13
|
+
# to the end user.
|
14
|
+
field :description, type: String, localize: true
|
15
|
+
|
16
|
+
# tomar lifecycle de content:
|
17
|
+
attr_accessor :lifecycle_name
|
18
|
+
#field :lifecycle_name, type: String
|
19
|
+
|
20
|
+
# Version Lifecycle State:
|
21
|
+
# A managed string that describes the current state of this particular version.
|
22
|
+
# Depending on the Content's lifecycle, it changes upon several events.
|
23
|
+
# - draft:
|
24
|
+
# - awaiting_validation:
|
25
|
+
# - validated:
|
26
|
+
# - invalidated:
|
27
|
+
# - published:
|
28
|
+
# - superceded:
|
29
|
+
field :lifecycle_state, type: String, default: -> { :draft }
|
30
|
+
validates :lifecycle_state, presence: true
|
31
|
+
|
32
|
+
# Version Code:
|
33
|
+
# An internal version number. This number is used only to determine whether one version is more recent than another, with higher numbers indicating more recent versions. This is not the version number shown to users; that number is set by the versionName attribute.
|
34
|
+
# The value must be set as an integer, such as "100". You can define it however you want, as long as each successive version has a higher number. For example, it could be a build number. Or you could translate a version number in "x.y" format to an integer by encoding the "x" and "y" separately in the lower and upper 16 bits. Or you could simply increase the number by one each time a new version is released.
|
35
|
+
field :code, type: Integer
|
36
|
+
validates :code, presence: true, format: { with: /[0-9]+/ }
|
37
|
+
|
38
|
+
# Version Name:
|
39
|
+
# The version number shown to users.
|
40
|
+
# The string has no other purpose than to be displayed to users. The Version Code
|
41
|
+
# attribute holds the significant version number used internally.
|
42
|
+
field :name, type: String
|
43
|
+
|
44
|
+
# Content
|
45
|
+
# The content to which this particular version belongs:
|
46
|
+
belongs_to :content, class_name: :Content, inverse_of: :versions
|
10
47
|
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
string :id, :name, :lifecycle_name, :lifecycle_state
|
17
|
-
integer :code
|
18
|
-
end
|
19
|
-
|
20
|
-
delegate :description, to: :description_translations
|
21
|
-
|
22
|
-
belongs_to :content, class_name: "naranya_ecm/models/content"
|
23
|
-
has_many :media_resources, class_name: "naranya_ecm/models/media_resource"
|
24
|
-
|
25
|
-
before_save :filter_out_read_only_attributes
|
26
|
-
def filter_out_read_only_attributes
|
27
|
-
%w(lifecycle_name content media_resources url created_at updated_at).each do |roa|
|
28
|
-
self.attributes.delete roa
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
#include NaranyaEcm::Lifecycles::Lifecycleable
|
33
|
-
# include NaranyaEcm::Lifecycles::VersionLifecycle
|
48
|
+
# MediaResources:
|
49
|
+
# A list of media - images, audio, video, etc. associated
|
50
|
+
# directly to this particular content version. Some examples:
|
51
|
+
# - Icons, Banners, screenshots, gallery images
|
52
|
+
has_many :media_resources, class_name: :MediaResource, inverse_of: :content_version
|
34
53
|
|
54
|
+
include NaranyaEcm::ContentLifecycle
|
55
|
+
|
35
56
|
end
|
36
57
|
end
|
@@ -1,41 +1,37 @@
|
|
1
|
-
require 'active_resource'
|
2
1
|
|
3
|
-
module NaranyaEcm
|
4
|
-
class DownloadAuthorization
|
2
|
+
module NaranyaEcm
|
3
|
+
class DownloadAuthorization
|
5
4
|
|
6
|
-
include NaranyaEcm
|
7
|
-
|
8
|
-
class Tokens < HashWithIndifferentAccess
|
9
|
-
def initialize(*args)
|
10
|
-
super()
|
11
|
-
given_attrs = args.shift
|
12
|
-
given_attrs.each { |k,v| self[k] = v } unless given_attrs.nil?
|
13
|
-
end
|
14
|
-
def to_query_params
|
15
|
-
self.map { |key, value| "#{key}=#{value}" }.join '&'
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
include NaranyaEcm::Behaviors::Resourceable
|
5
|
+
include NaranyaEcm.document_module
|
20
6
|
include NaranyaEcm::Behaviors::Timestampable
|
7
|
+
include NaranyaEcm::Cache::Methods
|
21
8
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
belongs_to :media_resource, class_name: "naranya_ecm/models/media_resource"
|
9
|
+
belongs_to :media_resource
|
10
|
+
# validates :media_resource, presence: true
|
11
|
+
|
12
|
+
field :expires_at, type: DateTime, default: -> { 5.minutes.from_now }
|
13
|
+
field :tokens, type: Hash
|
30
14
|
|
31
15
|
def authorized_url
|
32
16
|
unless @authorized_url
|
33
17
|
uri = URI(self.media_resource.downloadable_url)
|
34
|
-
uri.query = self.tokens.
|
18
|
+
uri.query = self.tokens.map { |key, value| "#{key}=#{value}" }.join '&'
|
35
19
|
@authorized_url = uri.to_s
|
36
20
|
end
|
37
21
|
@authorized_url
|
38
22
|
end
|
39
23
|
|
24
|
+
# validate :requested_media_resource_is_private
|
25
|
+
# def requested_media_resource_is_private
|
26
|
+
# errors.add :media_resource, "must be private" unless self.media_resource.private?
|
27
|
+
# end
|
28
|
+
|
29
|
+
# validate :requested_media_resource_is_valid
|
30
|
+
# def requested_media_resource_is_valid
|
31
|
+
# unless self.media_resource.valid?
|
32
|
+
# errors.add :media_resource, self.media_resource.errors.messages
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
|
40
36
|
end
|
41
37
|
end
|
@@ -1,28 +1,62 @@
|
|
1
|
-
require 'active_resource'
|
2
1
|
|
3
|
-
module NaranyaEcm
|
4
|
-
class MediaResource
|
2
|
+
module NaranyaEcm
|
3
|
+
class MediaResource
|
5
4
|
|
6
|
-
include NaranyaEcm
|
5
|
+
include NaranyaEcm.document_module
|
7
6
|
include NaranyaEcm::Behaviors::Timestampable
|
7
|
+
include NaranyaEcm::Cache::Methods
|
8
8
|
|
9
|
-
|
9
|
+
# Media MIME/Internet Type:
|
10
|
+
field :type, type: String
|
10
11
|
|
11
|
-
|
12
|
+
# The roles of the media file (screenshot, icon, main_downloadable):
|
13
|
+
field :roles, type: Array, default: -> {[]}
|
14
|
+
|
15
|
+
# The URL to the downloadable file:
|
16
|
+
field :downloadable_url, type: String
|
17
|
+
validates :downloadable_url, presence: true, format: { with: URI.regexp }
|
12
18
|
|
13
|
-
|
19
|
+
# The access type of the media file (private|public)
|
20
|
+
field :access_type, type: String, default: -> { 'public' }
|
21
|
+
validates :access_type, presence: true
|
22
|
+
|
23
|
+
# The list of compatible device ids:
|
24
|
+
field :compatible_device_ids, type: Array, default: -> { [] }
|
14
25
|
|
15
|
-
#
|
16
|
-
|
17
|
-
# define each attribute separately
|
18
|
-
string :id, :access_type, :type, :roles, :downloadable_url
|
26
|
+
# The list of compatible device operating systems:
|
27
|
+
field :compatible_device_oses, type: Hash, default: -> { {} }
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
attribute 'compatible_device_oses', 'string'
|
29
|
+
belongs_to :content
|
30
|
+
belongs_to :content_version
|
31
|
+
belongs_to :category
|
24
32
|
|
33
|
+
def private?
|
34
|
+
self.access_type == 'private'
|
25
35
|
end
|
26
36
|
|
37
|
+
# Media Resource State:
|
38
|
+
# A managed string that describes the current state of this particular media.
|
39
|
+
# - unlinked: The media resource is linked to a category, content or content_version.
|
40
|
+
# - linked: The media resource is detached from any associated document.
|
41
|
+
field :state, type: String, default: -> { :unlinked }
|
42
|
+
validates :state, presence: true
|
43
|
+
state_machine :state, initial: :unlinked do
|
44
|
+
|
45
|
+
state :linked
|
46
|
+
state :unlinked
|
47
|
+
|
48
|
+
event :unlink do
|
49
|
+
transition :linked => :unlinked
|
50
|
+
end
|
51
|
+
|
52
|
+
after_transition :on => any, do: :commit_state_change!
|
53
|
+
end
|
54
|
+
|
55
|
+
def commit_state_change!(transition)
|
56
|
+
off_band_changes["#{transition.attribute}_event"] = transition.event
|
57
|
+
self.send "reset_#{transition.attribute}!".to_sym
|
58
|
+
self.save
|
59
|
+
end
|
60
|
+
|
27
61
|
end
|
28
62
|
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module NaranyaEcm::Rest
|
2
|
+
|
3
|
+
module Associations
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class Association
|
7
|
+
attr_reader :klass, :name, :associated_class, :options
|
8
|
+
|
9
|
+
def resolve_class_name(given_class_name)
|
10
|
+
given_class_name = "#{@klass.name.deconstantize}::#{given_class_name}" if given_class_name.is_a?(Symbol)
|
11
|
+
given_class_name.constantize
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(klass, name, options = {})
|
15
|
+
@klass, @name = klass, name
|
16
|
+
|
17
|
+
@associated_class = if (given_class_name = options.delete(:class_name))
|
18
|
+
resolve_class_name(given_class_name)
|
19
|
+
else
|
20
|
+
resolve_class_name(name.to_s.classify.to_sym)
|
21
|
+
end
|
22
|
+
|
23
|
+
@options = options.with_indifferent_access
|
24
|
+
|
25
|
+
klass.send :attr_accessor, name
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class CollectionAssociation < Association
|
31
|
+
end
|
32
|
+
|
33
|
+
class SingularAssociation < Association
|
34
|
+
end
|
35
|
+
|
36
|
+
class HasMany < Association
|
37
|
+
def foreign_key
|
38
|
+
@foreign_key ||= @options[:foreign_key] || @options.has_key?(:inverse_of) ? "#{@options[:inverse_of]}_id".to_sym : "#{@klass.name.demodulize.underscore}_id".to_sym
|
39
|
+
end
|
40
|
+
def initialize(klass, name, options = {})
|
41
|
+
super
|
42
|
+
# Define the Getter:
|
43
|
+
klass.send :define_method, name do
|
44
|
+
value = self.instance_variable_get "@#{name}".to_sym
|
45
|
+
if value.nil?
|
46
|
+
reflection = self.class.reflect_on_association name
|
47
|
+
foreign_key = (self.class.name.demodulize.underscore + '_id').to_sym
|
48
|
+
value = self.instance_variable_set(
|
49
|
+
"@#{name}".to_sym,
|
50
|
+
reflection.associated_class.where(foreign_key => self.id)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
value
|
54
|
+
end
|
55
|
+
|
56
|
+
# Define the setter
|
57
|
+
klass.send :define_method, "#{name}=".to_sym do |new_value|
|
58
|
+
reflection = self.class.reflect_on_association(name)
|
59
|
+
relation = Relation.new(reflection.associated_class, { reflection.foreign_key => self.id })
|
60
|
+
|
61
|
+
unless new_value.blank?
|
62
|
+
relation.load new_value
|
63
|
+
end
|
64
|
+
|
65
|
+
instance_variable_set "@#{name}".to_sym, relation
|
66
|
+
end
|
67
|
+
end
|
68
|
+
def macro; :has_many; end
|
69
|
+
end
|
70
|
+
|
71
|
+
class HasOne < SingularAssociation
|
72
|
+
def initialize(klass, name, options = {})
|
73
|
+
super
|
74
|
+
# Set the Getter:
|
75
|
+
klass.send :define_method, name do
|
76
|
+
value = self.instance_variable_get "@#{name}".to_sym
|
77
|
+
if value.nil?
|
78
|
+
reflection = self.class.reflect_on_association name
|
79
|
+
foreign_key = (self.class.name.demodulize.underscore + '_id').to_sym
|
80
|
+
value = self.instance_variable_set(
|
81
|
+
"@#{name}".to_sym,
|
82
|
+
reflection.associated_class.where(foreign_key => self.id).first
|
83
|
+
)
|
84
|
+
end
|
85
|
+
value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
def macro; :has_one; end
|
89
|
+
end
|
90
|
+
|
91
|
+
class BelongsTo < SingularAssociation
|
92
|
+
def initialize(klass, name, options = {})
|
93
|
+
super
|
94
|
+
|
95
|
+
# Define the foreign key:
|
96
|
+
klass.send :field, foreign_key, foreign_key: true
|
97
|
+
|
98
|
+
# Define the setter
|
99
|
+
klass.send :define_method, "#{name}=".to_sym do |new_value|
|
100
|
+
reflection = self.class.reflect_on_association(name)
|
101
|
+
foreign_key_name = reflection.foreign_key
|
102
|
+
|
103
|
+
new_value = reflection.associated_class.load new_value if new_value.is_a? Hash
|
104
|
+
raise "Type Mismatch on #{self.class.name} ##{name}= #{new_value.inspect}" unless new_value.nil? || new_value.class == reflection.associated_class
|
105
|
+
|
106
|
+
self.send "#{foreign_key_name}=".to_sym, new_value.nil? ? nil : new_value.id
|
107
|
+
instance_variable_set "@#{name}".to_sym, new_value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
def macro; :belongs_to; end
|
111
|
+
def foreign_key
|
112
|
+
@foreign_key ||= options[:foreign_key] || "#{name}_id".to_sym
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def is_association?(association_name)
|
117
|
+
self.class.is_association? association_name
|
118
|
+
end
|
119
|
+
|
120
|
+
module ClassMethods
|
121
|
+
|
122
|
+
def is_association?(association_name)
|
123
|
+
associations.has_key? association_name
|
124
|
+
end
|
125
|
+
|
126
|
+
def has_many(name, options={})
|
127
|
+
associations[name] = HasMany.new self, name, options
|
128
|
+
end
|
129
|
+
|
130
|
+
def has_one(name, options={})
|
131
|
+
associations[name] = HasOne.new self, name, options
|
132
|
+
end
|
133
|
+
|
134
|
+
def belongs_to(name, options={})
|
135
|
+
associations[name] = BelongsTo.new self, name, options
|
136
|
+
end
|
137
|
+
|
138
|
+
def reflect_on_association(association_name)
|
139
|
+
raise "Association #{name} does not exist" unless is_association? association_name
|
140
|
+
associations[association_name]
|
141
|
+
end
|
142
|
+
|
143
|
+
def reflect_on_all_associations
|
144
|
+
associations
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def associations
|
150
|
+
@associations ||= {}.with_indifferent_access
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module NaranyaEcm::Rest
|
2
|
+
# = REST Errors
|
3
|
+
#
|
4
|
+
# Generic REST exception class.
|
5
|
+
class RestError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Raised when an object assigned to an association has an incorrect type.
|
9
|
+
#
|
10
|
+
# class Ticket < ActiveRecord::Base
|
11
|
+
# has_many :patches
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class Patch < ActiveRecord::Base
|
15
|
+
# belongs_to :ticket
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Comments are not patches, this assignment raises AssociationTypeMismatch.
|
19
|
+
# @ticket.patches << Comment.new(content: "Please attach tests to your patch.")
|
20
|
+
class AssociationTypeMismatch < RestError
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raised when unserialized object's type mismatches one specified for serializable field.
|
24
|
+
class SerializationTypeMismatch < RestError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt>
|
28
|
+
# misses adapter field).
|
29
|
+
class AdapterNotSpecified < RestError
|
30
|
+
end
|
31
|
+
|
32
|
+
# Raised when REST cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
|
33
|
+
class AdapterNotFound < RestError
|
34
|
+
end
|
35
|
+
|
36
|
+
# Raised when connection to the database could not been established (for example when <tt>connection=</tt>
|
37
|
+
# is given a nil object).
|
38
|
+
class ConnectionNotEstablished < RestError
|
39
|
+
end
|
40
|
+
|
41
|
+
# Raised when REST cannot find record by given id or set of ids.
|
42
|
+
class ResourceNotFound < RestError
|
43
|
+
end
|
44
|
+
|
45
|
+
# Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
|
46
|
+
# saved because record is invalid.
|
47
|
+
class ResourceNotSaved < RestError
|
48
|
+
end
|
49
|
+
|
50
|
+
# Raised by ActiveRecord::Base.destroy! when a call to destroy would return false.
|
51
|
+
class ResourceNotDestroyed < RestError
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'naranya_ecm/rest/errors'
|
2
|
+
module NaranyaEcm::Rest
|
3
|
+
|
4
|
+
module FinderMethods
|
5
|
+
|
6
|
+
def find(*args)
|
7
|
+
if block_given?
|
8
|
+
to_a.find { |*block_args| yield(*block_args) }
|
9
|
+
else
|
10
|
+
find_with_ids(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def find_with_ids(*ids)
|
17
|
+
expects_array = ids.first.kind_of?(Array)
|
18
|
+
return ids.first if expects_array && ids.first.empty?
|
19
|
+
|
20
|
+
ids = ids.flatten.compact.uniq
|
21
|
+
|
22
|
+
case ids.size
|
23
|
+
when 0
|
24
|
+
raise ResourceNotFound, "Couldn't find #{self.name} without an ID"
|
25
|
+
when 1
|
26
|
+
result = find_one(ids.first)
|
27
|
+
expects_array ? [ result ] : result
|
28
|
+
else
|
29
|
+
find_some(ids)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_one(id)
|
34
|
+
self.load fetch_one(id)
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch_one(id)
|
38
|
+
format = NaranyaEcm.options[:format] || 'json'
|
39
|
+
|
40
|
+
response = self.get("#{path}/#{id}.#{format}")
|
41
|
+
|
42
|
+
case response.code
|
43
|
+
when 404
|
44
|
+
raise ResourceNotFound
|
45
|
+
when 200
|
46
|
+
response.to_hash
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|