simple_ams 0.1.0

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.
@@ -0,0 +1,93 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Document
4
+ attr_reader :options, :serializer, :resource
5
+
6
+ def initialize(options = SimpleAMS::Options.new)
7
+ @options = options
8
+ @serializer = options.serializer
9
+ @resource = options.resource
10
+ end
11
+
12
+ def primary_id
13
+ options.primary_id
14
+ end
15
+
16
+ def fields
17
+ return @fields ||= self.class::Fields.new(options)
18
+ end
19
+
20
+ def relations
21
+ return @relations ||= self.class::Relations.new(options)
22
+ end
23
+
24
+ def name
25
+ options.name
26
+ end
27
+
28
+ def type
29
+ options.type
30
+ end
31
+
32
+ def adapter
33
+ options.adapter
34
+ end
35
+
36
+ def links
37
+ return @links ||= self.class::Links.new(options)
38
+ end
39
+
40
+ def metas
41
+ return @metas ||= self.class::Metas.new(options)
42
+ end
43
+
44
+ def folder?
45
+ self.is_a?(self.class::Folder)
46
+ end
47
+
48
+ def document?
49
+ !folder?
50
+ end
51
+
52
+ class Folder < self
53
+ attr_reader :collection
54
+
55
+ def initialize(options)
56
+ @_options = options
57
+ @options = @_options.collection_options
58
+
59
+ @collection = options.collection
60
+ end
61
+
62
+ def documents
63
+ @documents = collection.map do |resource|
64
+ SimpleAMS::Document.new(options_for(resource))
65
+ end
66
+ end
67
+
68
+ def resource_options
69
+ _options
70
+ end
71
+
72
+ private
73
+ attr_reader :_options
74
+
75
+ #TODO: OBS! here we have extra cost for nothing
76
+ #can't we just pass the resource_option with different resource?
77
+ def options_for(resource)
78
+ SimpleAMS::Options.new(resource, {
79
+ injected_options: resource_options.injected_options.merge({
80
+ serializer: serializer_for(resource)
81
+ }),
82
+ allowed_options: resource_options.allowed_options
83
+ })
84
+ end
85
+
86
+ def serializer_for(resource)
87
+ _serializer = resource_options.serializer_class
88
+ _serializer = _serializer.call(resource) if _serializer.respond_to?(:call)
89
+
90
+ return _serializer
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,216 @@
1
+ require "simple_ams"
2
+
3
+ module SimpleAMS::DSL
4
+ def self.included(host_class)
5
+ host_class.extend ClassMethods
6
+
7
+ _klass = Class.new(Object).extend(ClassMethods)
8
+ _klass.instance_eval do
9
+ def options
10
+ {
11
+ adapter: adapter,
12
+ primary_id: primary_id,
13
+ type: type,
14
+ fields: fields,
15
+ relations: relations,
16
+ includes: includes,
17
+ links: links,
18
+ metas: metas,
19
+ }
20
+ end
21
+ end
22
+
23
+ host_class.const_set('Collection', _klass)
24
+ end
25
+
26
+ module ClassMethods
27
+ #TODO: Shouldn't we call here super to presever user's behavior ?
28
+ def inherited(subclass)
29
+ _klass = Class.new(Object).extend(ClassMethods)
30
+ _klass.instance_eval do
31
+ def options
32
+ {
33
+ adapter: adapter,
34
+ primary_id: primary_id,
35
+ type: type,
36
+ fields: fields,
37
+ relations: relations,
38
+ includes: includes,
39
+ links: links,
40
+ metas: metas,
41
+ }
42
+ end
43
+ end
44
+
45
+ subclass.const_set('Collection', _klass)
46
+ end
47
+
48
+ def default_options
49
+ @_default_options ||= {
50
+ adapter: [SimpleAMS::Adapters::AMS, {}],
51
+ primary_id: [:id, {}],
52
+ type: [_default_type_name, {}]
53
+ }
54
+ end
55
+
56
+ #TODO: Add tests !!
57
+ def _default_type_name
58
+ if self.to_s.end_with?('::Collection')
59
+ _name = self.to_s.gsub(
60
+ 'Serializer',''
61
+ ).gsub(
62
+ '::Collection', ''
63
+ ).downcase.split('::').last
64
+
65
+ return "#{_name}_collection".to_sym
66
+ else
67
+ return self.to_s.gsub('Serializer','').downcase.split('::').last.to_sym
68
+ end
69
+ end
70
+ def with_options(options = {})
71
+ @_options = options
72
+ meths = SimpleAMS::DSL::ClassMethods.instance_methods(false)
73
+ @_options.each do |key, value|
74
+ if key.to_sym == :collection
75
+ self.send(:collection){}.with_options(value)
76
+ elsif meths.include?(key)
77
+ self.send(key, value) if value.is_a?(Array)
78
+ self.send(key, value)
79
+ else
80
+ #TODO: Add a proper logger
81
+ puts "SimpeAMS: #{key} is not recognized, ignoring (from #{self.to_s})"
82
+ end
83
+ end
84
+
85
+ return @_options
86
+ end
87
+
88
+ #same for other ValueHashes
89
+ def adapter(name = nil, options = {})
90
+ @_adapter ||= default_options[:adapter]
91
+ return @_adapter if name.nil?
92
+
93
+ @_adapter = [name, options]
94
+ end
95
+
96
+ def primary_id(value = nil, options = {})
97
+ @_primary_id ||= default_options[:primary_id]
98
+ return @_primary_id if value.nil?
99
+
100
+ @_primary_id = [value, options]
101
+ end
102
+
103
+ def type(value = nil, options = {})
104
+ @_type ||= default_options[:type]
105
+ return @_type if value.nil?
106
+
107
+ @_type = [value, options]
108
+ end
109
+
110
+ def attributes(*args)
111
+ @_attributes ||= []
112
+ return @_attributes.uniq if (args&.empty? || args.nil?)
113
+
114
+ append_attributes(args)
115
+ end
116
+ alias attribute attributes
117
+ alias fields attributes
118
+
119
+ def has_many(name, options = {})
120
+ append_relationship([__method__, name, options])
121
+ end
122
+
123
+ def has_one(name, options = {})
124
+ append_relationship([__method__, name, options])
125
+ end
126
+
127
+ def belongs_to(name, options = {})
128
+ append_relationship([__method__, name, options])
129
+ end
130
+
131
+ def relations
132
+ @_relations || []
133
+ end
134
+
135
+ #TODO: there is no memoization here, hence we ignore includes manually set !!
136
+ #Consider fixing it by employing an observer that will clean the instance var
137
+ #each time @_relations is updated
138
+ def includes(_ = [])
139
+ relations.map{|rel| rel[1] }
140
+ end
141
+
142
+ def link(name, value, options = {})
143
+ append_link([name, value, options])
144
+ end
145
+
146
+ def meta(name = nil, value = nil, options = {})
147
+ append_meta([name, value, options])
148
+ end
149
+
150
+ def links(links = [])
151
+ links.map{|key, value| append_link([key, value].flatten(1))} if links.is_a?(Hash)
152
+
153
+ @_links ||= links
154
+ end
155
+
156
+ def metas(metas = [])
157
+ metas.map{|key, value| append_meta([key, value].flatten(1))} if metas.is_a?(Hash)
158
+
159
+ @_metas || []
160
+ end
161
+
162
+ def collection(name = nil, &block)
163
+ if block
164
+ self::Collection.class_eval do
165
+ instance_exec(&block)
166
+ end
167
+ end
168
+
169
+ self::Collection.type(name) if name
170
+
171
+ return self::Collection
172
+ end
173
+
174
+ def options
175
+ {
176
+ adapter: adapter,
177
+ primary_id: primary_id,
178
+ type: type,
179
+ fields: fields,
180
+ relations: relations,
181
+ includes: includes,
182
+ links: links,
183
+ metas: metas,
184
+ collection: collection
185
+ }
186
+ end
187
+
188
+ private
189
+ def append_relationship(rel)
190
+ @_relations ||= []
191
+
192
+ @_relations << rel
193
+ end
194
+
195
+ def append_attributes(*attrs)
196
+ @_attributes ||= []
197
+
198
+ @_attributes = (@_attributes << attrs).flatten.compact.uniq
199
+ end
200
+
201
+ def append_link(link)
202
+ @_links ||= []
203
+
204
+ @_links << link
205
+ end
206
+
207
+ def append_meta(meta)
208
+ @_metas ||= []
209
+
210
+ @_metas << meta
211
+ end
212
+
213
+ def empty_options
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,10 @@
1
+ module SimpleAMS::Methy
2
+ def self.of(hash = {})
3
+ m = Module.new
4
+ hash.each do |key, value|
5
+ m.send(:define_method, key){ value }
6
+ end
7
+
8
+ return m
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ class Adapter
5
+ include SimpleAMS::Options::Concerns::ValueHash
6
+
7
+ alias_method :klass, :value
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ module Concerns
5
+ #works for arrays that can hold either elements or object that respond_to? :name
6
+ module Filterable
7
+ #for optimizing performance, ask only the first element
8
+ #other idea is to create another module just for (Name)ValueHash objects
9
+ def &(other_filterables)
10
+ other_is_object = other_filterables.first.respond_to?(:name)
11
+
12
+ return self.class.new(
13
+ self.select{|m|
14
+ if other_is_object
15
+ other_filterables.include?(m.name)
16
+ else
17
+ other_filterables.include?(m)
18
+ end
19
+ }
20
+ )
21
+ end
22
+
23
+ #for optimizing performance, ask only the first element of self and save it as state
24
+ def include?(member)
25
+ unless defined?(@self_is_object)
26
+ @self_is_object = self.first.respond_to?(:name)
27
+ end
28
+
29
+ if @self_is_object
30
+ self.map(&:name).include?(member)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def raw
37
+ if self.first.respond_to?(:raw)
38
+ self.map(&:raw)
39
+ else
40
+ self.map{|i| i}
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ module Concerns
5
+ module NameValueHash
6
+ attr_reader :name, :value, :options
7
+
8
+ def initialize(name, value, options = {}, resource:)
9
+ @name = name.is_a?(String) ? name.to_sym : name
10
+ if value.is_a?(Proc)
11
+ _value = value.call(resource)
12
+ @value = _value.first
13
+ if _value.is_a?(Array) && _value.length > 1
14
+ @options = (_value.last || {}).merge(options || {})
15
+ else
16
+ @options = options || {}
17
+ end
18
+ else
19
+ @value = value
20
+ @options = options || {}
21
+ end
22
+ end
23
+
24
+ def raw
25
+ [name, value, options]
26
+ end
27
+
28
+ private
29
+ attr_writer :name, :value, :options
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ module Concerns
5
+ module ValueHash
6
+ attr_reader :value, :options
7
+
8
+ def initialize(value, options = {})
9
+ @value = value.is_a?(String) ? value.to_sym : value
10
+ @options = options.kind_of?(Hash) ? options || {} : options
11
+ end
12
+
13
+ alias_method :name, :value
14
+
15
+ def raw
16
+ [value, options]
17
+ end
18
+
19
+ private
20
+ attr_writer :value, :options
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ class Fields < Array
5
+ include SimpleAMS::Options::Concerns::Filterable
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ class Includes < Array
5
+ include SimpleAMS::Options::Concerns::Filterable
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ class Links < Array
5
+ include SimpleAMS::Options::Concerns::Filterable
6
+
7
+ class Link
8
+ include SimpleAMS::Options::Concerns::NameValueHash
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ class Metas < Array
5
+ include SimpleAMS::Options::Concerns::Filterable
6
+
7
+ #TODO: should it check empty value ?
8
+ class Meta
9
+ include SimpleAMS::Options::Concerns::NameValueHash
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ class PrimaryId
5
+ include SimpleAMS::Options::Concerns::ValueHash
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ class Relation
5
+ attr_reader :type, :name, :options
6
+ def initialize(type, name, options = {})
7
+ @type = type.to_sym
8
+ @name = name.is_a?(String) ? name.to_sym : name
9
+ @options = options
10
+
11
+ @many = type == :has_many ? true : false
12
+ end
13
+
14
+ alias relation name
15
+
16
+ def raw
17
+ [type, name, options]
18
+ end
19
+
20
+ def collection?
21
+ @many
22
+ end
23
+
24
+ def single?
25
+ !array
26
+ end
27
+
28
+ private
29
+ attr_writer :type, :name, :options
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Options
4
+ class Type
5
+ include SimpleAMS::Options::Concerns::ValueHash
6
+ end
7
+ end
8
+