glue 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,438 @@
1
+ require 'mega/synchash'
2
+ require 'mega/syncarray'
3
+
4
+ require 'glue/attribute'
5
+ require 'glue/flexob'
6
+
7
+ module Glue
8
+
9
+ # Ruby attributes are typeless and generally this is good.
10
+ # Some times we need extra metadata though, for example in
11
+ # relational mapping, or web form population.
12
+ #
13
+ # Only Fixnums, Strings, Floats, Times, Booleans are
14
+ # converted.
15
+ #
16
+ # The default = methods do not force the types. A special
17
+ # __force_set method should be used instead.
18
+ #--
19
+ # TODO:
20
+ # Perhaps a sync is needed in evals (!!!!)
21
+ #++
22
+
23
+ class Property
24
+
25
+ # If set to true, perform type checking on property set.
26
+ # Useful when debugging.
27
+
28
+ cattr_accessor :type_checking, false
29
+
30
+ # The symbol of the property.
31
+
32
+ attr_accessor :symbol
33
+
34
+ # The class of the property.
35
+
36
+ attr_accessor :klass
37
+
38
+ # Additional metadata (like sql declaration, sql index, etc)
39
+ # Here is a list of predefined metadata:
40
+ #
41
+ # [+:reader+]
42
+ # create reader?
43
+ #
44
+ # [+:writer+]
45
+ # create writer?
46
+ #
47
+ # [+:sql_index+]
48
+ # create an sql index for the column this poperty maps to?
49
+ #
50
+ # You can use this mechanism to add your own, custom,
51
+ # metadata.
52
+
53
+ attr_accessor :metadata
54
+
55
+ # Support legacy code, will be deprecated.
56
+
57
+ alias_method :meta, :metadata
58
+ alias_method :meta=, :metadata=
59
+
60
+ def initialize(symbol, klass, metadata = {})
61
+ @symbol, @klass = symbol, klass
62
+ @metadata = metadata
63
+ end
64
+
65
+ def ==(other)
66
+ return @symbol == other.symbol
67
+ end
68
+
69
+ def to_s
70
+ return @symbol.to_s
71
+ end
72
+ end
73
+
74
+ # A collection of Property related utility methods.
75
+
76
+ module PropertyUtils
77
+
78
+ # Add accessors to the properties to the given target
79
+ # (Module or Class). For simplicity also create the
80
+ # meta accessors.
81
+ #
82
+ # [+target+]
83
+ # The target class or module
84
+ #--
85
+ # gmosx: Perhaps we 'll optimize this in the future.
86
+ #++
87
+
88
+ def self.enchant(target, force = false)
89
+ unless target.instance_variables.include?('@__props')
90
+ # FIXME: should be thread safe here!
91
+ target.instance_variable_set('@__meta', Flexob.new)
92
+ target.instance_variable_set('@__props', SyncArray.new)
93
+
94
+ # gmosx: Ruby surprises and amazes me! We are in the Metaclass
95
+ # when defining methods and attributes so @__props is really
96
+ # a class scoped variable that unlike @@__props is not shared
97
+ # through the hierarchy.
98
+
99
+ target.module_eval %{
100
+ def self.__props
101
+ @__props
102
+ end
103
+
104
+ def self.properties
105
+ @__props
106
+ end
107
+
108
+ def self.__props=(props)
109
+ @__props = props
110
+ end
111
+
112
+ def self.__meta
113
+ @__meta
114
+ end
115
+
116
+ def self.__meta=(meta)
117
+ @__meta = meta
118
+ end
119
+
120
+ def self.metadata
121
+ @__meta
122
+ end
123
+ }
124
+
125
+ if target.is_a?(Class)
126
+
127
+ # Add some extra code to append features to
128
+ # subclasses.
129
+
130
+ target.module_eval %{
131
+ def self.inherited(child)
132
+ Glue::PropertyUtils.enchant(child)
133
+ Glue::PropertyUtils.copy_props(self, child)
134
+ # gmosx: We have to define @@__props first to avoid
135
+ # reusing the hash from the module. super must stay
136
+ # at the end.
137
+ super
138
+ end
139
+ }
140
+
141
+ else
142
+
143
+ # Add some extra code for modules to append
144
+ # their features to classes that include it.
145
+
146
+ target.module_eval %{
147
+ def self.append_features(base)
148
+ # gmosx: We have to define @@__props first to avoid
149
+ # reusing the hash from the module. super must stay
150
+ # at the end.
151
+ Glue::PropertyUtils.copy_features(self, base)
152
+ super
153
+ end
154
+ }
155
+
156
+ end
157
+ end
158
+ end
159
+
160
+ # Copy properties from src (Module or Class) to dest.
161
+
162
+ def self.copy_props(src, dest)
163
+ src.__props.each do |p|
164
+ add_prop(dest, p)
165
+ end
166
+
167
+ # copy the metadata.
168
+ src.__meta.each do |k, val|
169
+ if val.is_a?(TrueClass)
170
+ dest.__meta[k] = val
171
+ else
172
+ dest.__meta[k] = val.dup
173
+ end
174
+ # val.each { |v| dest.meta(k, v) } if val
175
+ end
176
+ end
177
+
178
+ # Add the property to the target (Class or Module)
179
+
180
+ def self.add_prop(target, prop)
181
+ if idx = target.__props.index(prop)
182
+ # override in case of duplicates. Keep the order of the props.
183
+ target.__props[idx] = prop
184
+ else
185
+ target.__props << prop
186
+ end
187
+
188
+ # Store the property in the :props_and_relations
189
+ # metadata array.
190
+
191
+ target.meta :props_and_relations, prop
192
+
193
+ # Precompile the property read/write methods
194
+
195
+ s, klass = prop.symbol, prop.klass
196
+
197
+ if prop.meta[:reader]
198
+ target.module_eval %{
199
+ def #{s}
200
+ return @#{s}
201
+ end
202
+ }
203
+ end
204
+
205
+ # gmosx: __force_xxx reuses xxx= to allow for easier
206
+ # overrides.
207
+
208
+ if prop.meta[:writer]
209
+ target.module_eval %{
210
+ #{prop_setter(prop)}
211
+
212
+ def __force_#{s}(val)
213
+ self.#{s}=(} + case klass.name
214
+ when Fixnum.name
215
+ "val.to_i()"
216
+ when String.name
217
+ "val.to_s()"
218
+ when Float.name
219
+ "val.to_f()"
220
+ when Time.name
221
+ "Time.parse(val.to_s())"
222
+ when TrueClass.name, FalseClass.name
223
+ "val.to_i() > 0"
224
+ else
225
+ "val"
226
+ end + %{)
227
+ end
228
+ }
229
+ end
230
+ end
231
+
232
+ # Generates the property setter code. Can be overriden
233
+ # to support extra functionality (example: markup)
234
+
235
+ def self.prop_setter(prop)
236
+ s = prop.symbol
237
+
238
+ code = %{
239
+ def #{s}=(val)
240
+ }
241
+
242
+ if Glue::Property.type_checking
243
+ code << %{
244
+ unless #{prop.klass} == val.class
245
+ raise "Invalid type, expected '#{prop.klass}', is '\#\{val.class\}'."
246
+ end
247
+ }
248
+ end
249
+
250
+ code << %{
251
+ @#{s} = val
252
+ end
253
+ }
254
+
255
+ return code
256
+ end
257
+
258
+ # Get the property metadata for the given symbol.
259
+
260
+ def self.get_prop(klass, sym)
261
+ return klass.__props.find { |p| p.symbol == sym }
262
+ end
263
+
264
+ # Include meta-language mixins
265
+
266
+ def self.include_meta_mixins(target)
267
+ target.module_eval %{ include Glue::Validation } if defined?(Glue::Validation)
268
+ # gmosx: TODO, make Og::MetaLanguage equivalent to Validation.
269
+ # target.module_eval %{ extend Og::MetaLanguage } if defined?(Og::MetaLanguage)
270
+ target.module_eval %{ include Glue::Aspects } if defined?(Glue::Aspects)
271
+ target.send(:include, Og::EntityMixin) if defined?(Og::EntityMixin)
272
+ end
273
+
274
+ def self.copy_features(this, other)
275
+ Glue::PropertyUtils.enchant(other)
276
+ Glue::PropertyUtils.copy_props(this, other)
277
+ Glue::PropertyUtils.include_meta_mixins(other)
278
+ end
279
+
280
+ # Resolves the parameters passed to the propxxx macros
281
+ # to generate the meta, klass and symbols variables. This
282
+ # way the common functionality is factored out.
283
+ #
284
+ # [+params+]
285
+ # The params to resolve.
286
+ # [+one_symbol+]
287
+ # If true, only resolves one symbol (used in prop).
288
+
289
+ def self.resolve_prop_params(*params)
290
+ meta = {}
291
+ klass = Object
292
+ symbols = []
293
+
294
+ for param in params.flatten
295
+ if param.is_a?(Class)
296
+ klass = param
297
+ elsif param.is_a?(Symbol)
298
+ symbols << param
299
+ elsif param.is_a?(TrueClass) or param.is_a?(TrueClass)
300
+ writer = param
301
+ elsif param.is_a?(Hash)
302
+ # the meta hash.
303
+ meta.update(param) { |k, a, b| [a,b].join(' ') }
304
+ else
305
+ raise 'Error when defining property!'
306
+ end
307
+ end
308
+
309
+ raise 'No symbols provided!' if symbols.empty?
310
+
311
+ return meta, klass, symbols
312
+ end
313
+
314
+ end
315
+
316
+ end
317
+
318
+ class Module
319
+
320
+ # Define a property (== typed attribute)
321
+ # This works like Ruby's standard attr method, ie creates
322
+ # only one property.
323
+ #
324
+ # Use the prop_reader, prop_writer, prop_accessor methods
325
+ # for multiple properties.
326
+ #
327
+ # === Examples
328
+ #
329
+ # prop String, :name, :sql => "char(32), :sql_index => "name(32)"
330
+ # --> creates only writer.
331
+ # prop Fixnum, :oid, writer = true, :sql => "integer PRIMARY KEY"
332
+ # --> creates reader and writer.
333
+
334
+ def prop(*params)
335
+ meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params)
336
+ symbol = symbols.first
337
+
338
+ Glue::PropertyUtils.enchant(self)
339
+
340
+ property = Glue::Property.new(symbol, klass, meta)
341
+
342
+ reader = meta[:reader] || true
343
+ writer = writer || meta[:writer] || false
344
+
345
+ meta[:reader] = true if meta[:reader].nil?
346
+ if defined?(writer)
347
+ meta[:writer] = writer
348
+ else
349
+ meta[:writer] = true if meta[:writer].nil?
350
+ end
351
+
352
+ Glue::PropertyUtils.add_prop(self, property)
353
+
354
+ # gmosx: should be placed AFTER enchant!
355
+
356
+ Glue::PropertyUtils.include_meta_mixins(self)
357
+ end
358
+
359
+ # Helper method. Accepts a collection of symbols and generates
360
+ # properties. Only generates reader.
361
+ #
362
+ # Example:
363
+ # prop_reader String, :name, :title, :body, :sql => "char(32)"
364
+
365
+ def prop_reader(*params)
366
+ meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params)
367
+
368
+ meta[:reader] = true
369
+ meta[:writer] = false
370
+
371
+ for symbol in symbols
372
+ prop(klass, symbol, meta)
373
+ end
374
+ end
375
+
376
+ # Helper method. Accepts a collection of symbols and generates
377
+ # properties. Only generates writer.
378
+ #
379
+ # Example:
380
+ # prop_writer String, :name, :title, :body, :sql => "char(32)"
381
+
382
+ def prop_writer(*params)
383
+ meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params)
384
+
385
+ meta[:reader] = false
386
+ meta[:writer] = true
387
+
388
+ for symbol in symbols
389
+ prop(klass, symbol, meta)
390
+ end
391
+ end
392
+
393
+ # Helper method. Accepts a collection of symbols and generates
394
+ # properties. Generates reader and writer.
395
+ #
396
+ # Example:
397
+ # prop_accessor String, :name, :title, :body, :sql => "char(32)"
398
+
399
+ def prop_accessor(*params)
400
+ meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params)
401
+
402
+ meta[:reader] = true
403
+ meta[:writer] = true
404
+
405
+ for symbol in symbols
406
+ prop(klass, symbol, meta)
407
+ end
408
+ end
409
+ alias_method :property, :prop_accessor
410
+
411
+ # Attach metadata.
412
+ # Guard against duplicates, no need to keep order.
413
+ # This method uses closures :)
414
+ #--
415
+ # gmosx: crappy implementation, recode.
416
+ #++
417
+
418
+ def meta(key, *val)
419
+ Glue::PropertyUtils.enchant(self)
420
+
421
+ if val.empty?
422
+ self.module_eval %{
423
+ @__meta[key] = true
424
+ }
425
+ else
426
+ val = val.first if val.size == 1
427
+
428
+ self.module_eval %{
429
+ @__meta[key] ||= []
430
+ @__meta[key].delete_if { |v| val == v }
431
+ @__meta[key] << val
432
+ }
433
+ end
434
+ end
435
+
436
+ end
437
+
438
+ # * George Moschovitis <gm@navel.gr>
@@ -18,7 +18,7 @@ module TemplateMixin
18
18
  #
19
19
  # [+buffer+]
20
20
  # The variable to act as a buffer where the ruby code
21
- # for this template will be generated. Passed as a|
21
+ # for this template will be generated. Passed as a
22
22
  # String.
23
23
  #
24
24
  # [+base_dir+]
@@ -1,7 +1,7 @@
1
1
  require 'uri'
2
2
  require 'cgi'
3
3
 
4
- require 'glue/string'
4
+ require 'nano/string/blank%3F'
5
5
 
6
6
  module Glue
7
7
 
@@ -160,7 +160,7 @@ module UriUtils
160
160
  hash.update(parameters)
161
161
  query_string = self.hash_to_query_string(hash)
162
162
 
163
- if Glue::StringUtils.valid?(query_string)
163
+ unless query_string.blank?
164
164
  return "#{rest}?#{query_string}"
165
165
  else
166
166
  return rest