simple_ams 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+