glue 0.23.0 → 0.24.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.
@@ -7,10 +7,8 @@ module OutgoingMailer
7
7
 
8
8
  attr_accessor :template_root
9
9
 
10
- def self.included(base)
11
- base.extend ClassMethods
12
- end
13
-
10
+ on_included %{ base.extend ClassMethods }
11
+
14
12
  def initialize(from = nil, to = nil, subject = nil, body = FileTemplate.new)
15
13
  super
16
14
  @charset = Mailer.default_charset.dup
@@ -0,0 +1,48 @@
1
+ # George Moschovitis <gm@navel.gr>
2
+
3
+ class Module
4
+
5
+ # A useful macro for dynamic modules.
6
+ #--
7
+ # FIXME: quick and easy implementation, should
8
+ # come up with something better. The name
9
+ # sucks too.
10
+ #++
11
+ def on_included(code)
12
+ tag = caller[0].split(' ').first.split(/\/|\\/).last.gsub(/:|\.|\(|\)/, '_')
13
+ old = "__included_#{tag}"
14
+ module_eval %{
15
+ class << self
16
+ alias_method :#{old}, :included
17
+ def included(base)
18
+ #{old}(base)
19
+ #{code}
20
+ end
21
+ end
22
+ }
23
+ end
24
+ end
25
+
26
+ =begin Testing
27
+
28
+ module M
29
+ on_included %{
30
+ puts 'hello'
31
+ }
32
+ end
33
+
34
+ module Q
35
+ on_included %{
36
+ puts 'world'
37
+ }
38
+ on_included %{
39
+ puts 'it works'
40
+ }
41
+ end
42
+
43
+ class C
44
+ include M
45
+ include Q
46
+ end
47
+
48
+ =end
data/lib/glue/property.rb CHANGED
@@ -1,457 +1,250 @@
1
- require 'mega/synchash'
2
- require 'mega/syncarray'
3
-
4
- require 'nano/time/change'
5
-
6
- require 'glue/attribute'
7
- require 'glue/flexob'
8
-
9
- module Glue
10
-
11
- # Ruby attributes are typeless and generally this is good.
12
- # Some times we need extra metadata though, for example in
13
- # relational mapping, or web form population.
14
- #
15
- # Only Fixnums, Strings, Floats, Times, Booleans are
16
- # converted.
17
- #
18
- # The default = methods do not force the types. A special
19
- # __force_set method should be used instead.
1
+ require 'mega/annotation'
2
+ require 'mega/inheritor'
3
+ require 'mega/ohash'
4
+
5
+ require 'glue/on_included'
6
+ require 'og/entity'
7
+ require 'glue/validation'
8
+ require 'og/relation/all'
9
+ require 'glue/aspects'
10
+
11
+ # A convienience structure that holds property
12
+ # metadata.
20
13
  #--
21
- # TODO:
22
- # Perhaps a sync is needed in evals (!!!!)
14
+ # TODO: reimplement type checking.
23
15
  #++
24
16
 
25
17
  class Property
26
-
27
- # If set to true, perform type checking on property set.
28
- # Useful when debugging.
29
-
30
- cattr_accessor :type_checking, false
31
-
32
- # The symbol of the property.
33
-
34
- attr_accessor :symbol
18
+ attr_accessor :hash
35
19
 
36
- # The class of the property.
37
-
38
- attr_accessor :klass
39
-
40
- # Additional metadata (like sql declaration, sql index, etc)
41
- # Here is a list of predefined metadata:
42
- #
43
- # [+:reader+]
44
- # create reader?
45
- #
46
- # [+:writer+]
47
- # create writer?
48
- #
49
- # [+:sql_index+]
50
- # create an sql index for the column this poperty maps to?
51
- #
52
- # You can use this mechanism to add your own, custom,
53
- # metadata.
54
-
55
- attr_accessor :metadata
56
-
57
- # Support legacy code, will be deprecated.
20
+ def initialize(hash)
21
+ @hash = hash
22
+ @hash = @hash.to_h unless @hash.is_a?(Hash)
23
+ end
58
24
 
59
- alias_method :meta, :metadata
60
- alias_method :meta=, :metadata=
25
+ def [](key)
26
+ @hash[key]
27
+ end
28
+ alias_method :method_missing, :[]
61
29
 
62
- def initialize(symbol, klass, metadata = {})
63
- @symbol, @klass = symbol, klass
64
- @metadata = metadata
30
+ def []=(key, val)
31
+ @hash[key] = val
65
32
  end
66
33
 
67
- def ==(other)
68
- return @symbol == other.symbol
34
+ def <=>(other)
35
+ @hash[:symbol] <=> other.hash[:symbol]
69
36
  end
70
37
 
71
38
  def to_s
72
- return @symbol.to_s
73
- end
39
+ @hash[:symbol].to_s
40
+ end
74
41
  end
75
42
 
76
- # A collection of Property related utility methods.
77
-
78
- module PropertyUtils
43
+ # Property related utils and helpers.
79
44
 
80
- # Add accessors to the properties to the given target
81
- # (Module or Class). For simplicity also create the
82
- # meta accessors.
45
+ class Property
46
+ class << self
47
+
48
+ # Populate an object from a hash of values.
49
+ # This is a truly dangerous method.
83
50
  #
84
- # [+target+]
85
- # The target class or module
86
- #--
87
- # gmosx: Perhaps we 'll optimize this in the future.
88
- #++
89
-
90
- def self.enchant(target, force = false)
91
- unless target.instance_variables.include?('@__props')
92
- # FIXME: should be thread safe here!
93
- target.instance_variable_set('@__meta', Flexob.new)
94
- target.instance_variable_set('@__props', SyncArray.new)
95
-
96
- # gmosx: Ruby surprises and amazes me! We are in the Metaclass
97
- # when defining methods and attributes so @__props is really
98
- # a class scoped variable that unlike @@__props is not shared
99
- # through the hierarchy.
51
+ # === Options:
52
+ # * name
53
+ # * force_boolean
54
+
55
+ def populate_object(obj, values, options = {})
56
+ # If a class is passed create an instance.
57
+ obj = obj.new if obj.is_a?(Class)
58
+
59
+ for prop in obj.class.properties.values
60
+ unless options[:all]
61
+ next if :oid == prop.symbol or prop.editor == :none # TODO check for real key.
62
+ end
63
+ prop_name = prop.symbol.to_s
100
64
 
101
- target.module_eval %{
102
- def self.__props
103
- @__props
104
- end
105
-
106
- def self.properties
107
- @__props
108
- end
109
-
110
- def self.__props=(props)
111
- @__props = props
112
- end
65
+ # See if there is an incoming request param for this prop.
66
+
67
+ if values.has_key?(prop_name)
113
68
 
114
- def self.__meta
115
- @__meta
116
- end
117
-
118
- def self.__meta=(meta)
119
- @__meta = meta
120
- end
69
+ # if incoming file then read contents into a string.
121
70
 
122
- def self.metadata
123
- @__meta
124
- end
125
- }
126
-
127
- if target.is_a?(Class)
128
-
129
- # Add some extra code to append features to
130
- # subclasses.
131
-
132
- target.module_eval %{
133
- def self.inherited(child)
134
- Glue::PropertyUtils.enchant(child)
135
- Glue::PropertyUtils.copy_props(self, child)
136
- # gmosx: We have to define @@__props first to avoid
137
- # reusing the hash from the module. super must stay
138
- # at the end.
139
- super
140
- end
141
- }
142
-
71
+ prop_value = values[prop_name]
72
+ prop_value.read if prop_value.respond_to?(:read)
73
+ prop_value = prop_value.to_s unless prop_value.is_a?(Hash) or prop_value.is_a?(Array)
74
+
75
+ # If property is a Blob dont overwrite current property's data if "".
76
+
77
+ break if prop.klass == Og::Blob and prop_value.empty?
78
+
79
+ # Pass to property force.
80
+
81
+ obj.send("__force_#{prop_name}", prop_value)
82
+ elsif options[:force_boolean] and (prop.klass == TrueClass or prop.klass == FalseClass)
83
+ # Set a boolean property to false if it is not in the request.
84
+ # Only enabled if force_boolean == true.
85
+
86
+ obj.send("__force_#{prop_name}", 0)
143
87
  else
144
-
145
- # Add some extra code for modules to append
146
- # their features to classes that include it.
88
+ # check if there is a hashed version of the property (in the form values[symbol.part])
147
89
 
148
- target.module_eval %{
149
- def self.append_features(base)
150
- # gmosx: We have to define @@__props first to avoid
151
- # reusing the hash from the module. super must stay
152
- # at the end.
153
- Glue::PropertyUtils.copy_features(self, base)
154
- super
90
+ property_parts = Hash.new
91
+ values.each do |k,v|
92
+ if k =~ /^#{prop_name}\./
93
+ part_key = k.sub("#{prop_name}.", "")
94
+ property_parts[part_key.to_sym] = values[k].to_s
155
95
  end
156
- }
157
-
96
+ end
97
+
98
+ # pass the hashed version to __force_hash_ for processing.
99
+
100
+ obj.send("__force_hash_#{prop_name}", property_parts) unless property_parts.empty?
158
101
  end
159
102
  end
160
- end
161
103
 
162
- # Copy properties from src (Module or Class) to dest.
163
-
164
- def self.copy_props(src, dest)
165
- src.__props.each do |p|
166
- add_prop(dest, p)
167
- end
168
-
169
- # copy the metadata.
170
- src.__meta.each do |k, val|
171
- if val.is_a?(TrueClass)
172
- dest.__meta[k] = val
173
- else
174
- dest.__meta[k] = val.dup
175
- end
176
- # val.each { |v| dest.meta(k, v) } if val
177
- end
178
- end
179
-
180
- # Add the property to the target (Class or Module)
181
-
182
- def self.add_prop(target, prop)
183
- if idx = target.__props.index(prop)
184
- # override in case of duplicates. Keep the order of the props.
185
- target.__props[idx] = prop
186
- else
187
- target.__props << prop
188
- end
189
-
190
- # Store the property in the :props_and_relations
191
- # metadata array.
192
-
193
- target.meta :props_and_relations, prop
194
-
195
- # Precompile the property read/write methods
196
-
197
- s, klass = prop.symbol, prop.klass
198
-
199
- if prop.meta[:reader]
200
- target.module_eval %{
201
- def #{s}
202
- return @#{s}
104
+ if options[:assign_relations]
105
+ for rel in obj.class.relations
106
+ unless options[:all]
107
+ next if rel.options[:editor] == :none
203
108
  end
204
- }
205
- end
206
-
207
- # gmosx: __force_xxx reuses xxx= to allow for easier
208
- # overrides.
209
-
210
- if prop.meta[:writer]
211
- code = %{
212
- #{prop_setter(prop)}
213
109
 
214
- def __force_#{s}(val)
215
- self.#{s}=(} + case klass.name
216
- when Fixnum.name
217
- "val.to_i()"
218
- when String.name
219
- "val.to_s()"
220
- when Float.name
221
- "val.to_f()"
222
- when Time.name
223
- "Time.parse(val.to_s())"
224
- when TrueClass.name, FalseClass.name
225
- "val.to_i() > 0"
226
- else
227
- "val"
228
- end + %{)
229
- end
110
+ rel_name = rel.name.to_s
230
111
 
231
- def __force_hash_#{s}(hash)
232
- }
112
+ # Renew the relations from values
113
+ # TODO, farms: custom callbacks for processing relations?
233
114
 
234
- case klass.name
235
- when Time.name
236
- code << %{
237
- ihash = {}
238
- hash = hash.map { |k, v| ihash[k.intern] = v.to_i }
239
- @#{s} = @#{s}.change(ihash)
240
- }
115
+ if rel.kind_of? Og::RefersTo
116
+ if foreign_oid = values[rel_name]
117
+ foreign_oid = nil if foreign_oid == 'nil' or foreign_oid == 'none'
118
+ end
119
+ obj.send("__force_#{rel.foreign_key}", foreign_oid)
120
+ elsif rel.kind_of? Og::JoinsMany or rel.kind_of? Og::HasMany
121
+ # Empty current relationships.
122
+
123
+ collection = obj.send(rel_name)
124
+ collection.remove_all
125
+
126
+ # Set new.
127
+
128
+ if values.has_key?(rel_name)
129
+ values[rel_name].each do |v|
130
+ child = rel.target_class[v.to_s.to_i]
131
+ collection << child
132
+ end
133
+ end
134
+ end
241
135
  end
242
-
243
- code << %{
244
- end
245
- }
246
-
247
- target.module_eval(code)
248
136
  end
249
- end
250
137
 
251
- # Generates the property setter code. Can be overriden
252
- # to support extra functionality (example: markup)
138
+ return obj
139
+ end
140
+
141
+ def eval_helpers(writer, m, sym, klass)
142
+ return unless writer
253
143
 
254
- def self.prop_setter(prop)
255
- s = prop.symbol
256
-
257
144
  code = %{
258
- def #{s}=(val)
145
+ def __force_#{sym}(val)
146
+ self.#{sym}=(} + case klass.name
147
+ when 'Fixnum': 'val.nil? ? nil : val.to_i'
148
+ when 'String': 'val.to_s'
149
+ when 'Float': 'val.nil? ? nil : val.to_f'
150
+ when 'Time': 'Time.parse(val.to_s)'
151
+ when 'TrueClass', 'FalseClass': '(val == "on" or val == "true") ? true : (val.to_i > 0)'
152
+ when 'Og::Blob': 'val'
153
+ else
154
+ 'val'
155
+ end + %{)
156
+ end
157
+
158
+ def __force_hash_#{sym}(hash)
259
159
  }
260
-
261
- if Glue::Property.type_checking
262
- code << %{
263
- unless #{prop.klass} == val.class
264
- raise "Invalid type, expected '#{prop.klass}', is '\#\{val.class\}'."
265
- end
266
- }
160
+
161
+ case klass.name
162
+ when Time.name
163
+ code << %{
164
+ self.#{sym} = Time.local(hash[:year],hash[:month],hash[:day],hash[:hour],hash[:min])
165
+ }
267
166
  end
268
167
 
269
168
  code << %{
270
- @#{s} = val
271
- end
169
+ end
272
170
  }
273
171
 
274
- return code
275
- end
276
-
277
- # Get the property metadata for the given symbol.
278
-
279
- def self.get_prop(klass, sym)
280
- return klass.__props.find { |p| p.symbol == sym }
281
- end
172
+ m.module_eval(code)
173
+ end
282
174
 
283
- # Include meta-language mixins
284
-
285
- def self.include_meta_mixins(target)
286
- target.module_eval %{ include Glue::Validation } if defined?(Glue::Validation)
287
- # gmosx: TODO, make Og::MetaLanguage equivalent to Validation.
288
- # target.module_eval %{ extend Og::MetaLanguage } if defined?(Og::MetaLanguage)
289
- target.module_eval %{ include Glue::Aspects } if defined?(Glue::Aspects)
290
- target.send(:include, Og::EntityMixin) if defined?(Og::EntityMixin)
291
175
  end
292
-
293
- def self.copy_features(this, other)
294
- Glue::PropertyUtils.enchant(other)
295
- Glue::PropertyUtils.copy_props(this, other)
296
- Glue::PropertyUtils.include_meta_mixins(other)
297
- end
298
-
299
- # Resolves the parameters passed to the propxxx macros
300
- # to generate the meta, klass and symbols variables. This
301
- # way the common functionality is factored out.
302
- #
303
- # [+params+]
304
- # The params to resolve.
305
- # [+one_symbol+]
306
- # If true, only resolves one symbol (used in prop).
307
-
308
- def self.resolve_prop_params(*params)
309
- meta = {}
310
- klass = Object
311
- symbols = []
312
-
313
- for param in params.flatten
314
- if param.is_a?(Class)
315
- klass = param
316
- elsif param.is_a?(Symbol)
317
- symbols << param
318
- elsif param.is_a?(TrueClass) or param.is_a?(TrueClass)
319
- writer = param
320
- elsif param.is_a?(Hash)
321
- # the meta hash.
322
- meta.update(param) { |k, a, b| [a,b].join(' ') }
323
- else
324
- raise 'Error when defining property!'
325
- end
326
- end
327
-
328
- raise 'No symbols provided!' if symbols.empty?
329
-
330
- return meta, klass, symbols
331
- end
332
-
333
176
  end
334
177
 
335
- end
178
+ #--
179
+ # Extend the default Module.
180
+ #
181
+ # The properties hash, keeps the property metadata as a hash to avoid
182
+ # the null issue.
183
+ #++
336
184
 
337
185
  class Module
186
+ [ nil, :_reader, :_writer, :_accessor].each do |m|
187
+ writer = (m != :reader)
188
+ code = %{
189
+ def prop#{m} (*args)
190
+ inheritor(:properties, {}, :merge) unless @properties
338
191
 
339
- # Define a property (== typed attribute)
340
- # This works like Ruby's standard attr method, ie creates
341
- # only one property.
342
- #
343
- # Use the prop_reader, prop_writer, prop_accessor methods
344
- # for multiple properties.
345
- #
346
- # === Examples
347
- #
348
- # prop String, :name, :sql => "char(32), :sql_index => "name(32)"
349
- # --> creates only writer.
350
- # prop Fixnum, :oid, writer = true, :sql => "integer PRIMARY KEY"
351
- # --> creates reader and writer.
352
-
353
- def prop(*params)
354
- meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params)
355
- symbol = symbols.first
356
-
357
- Glue::PropertyUtils.enchant(self)
358
-
359
- property = Glue::Property.new(symbol, klass, meta)
360
-
361
- reader = meta[:reader] || true
362
- writer = writer || meta[:writer] || false
363
-
364
- meta[:reader] = true if meta[:reader].nil?
365
- if defined?(writer)
366
- meta[:writer] = writer
367
- else
368
- meta[:writer] = true if meta[:writer].nil?
369
- end
370
-
371
- Glue::PropertyUtils.add_prop(self, property)
372
-
373
- # gmosx: should be placed AFTER enchant!
374
-
375
- Glue::PropertyUtils.include_meta_mixins(self)
376
- end
377
-
378
- # Helper method. Accepts a collection of symbols and generates
379
- # properties. Only generates reader.
380
- #
381
- # Example:
382
- # prop_reader String, :name, :title, :body, :sql => "char(32)"
383
-
384
- def prop_reader(*params)
385
- meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params)
386
-
387
- meta[:reader] = true
388
- meta[:writer] = false
389
-
390
- for symbol in symbols
391
- prop(klass, symbol, meta)
392
- end
393
- end
192
+ args = args.flatten
193
+ harg = {}
194
+ while args.last.is_a?(Hash)
195
+ harg.update(args.pop)
196
+ end
394
197
 
395
- # Helper method. Accepts a collection of symbols and generates
396
- # properties. Only generates writer.
397
- #
398
- # Example:
399
- # prop_writer String, :name, :title, :body, :sql => "char(32)"
400
-
401
- def prop_writer(*params)
402
- meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params)
198
+ harg[:klass] = args.pop if args.last.is_a?(Class)
199
+ harg[:klass] ||= String
200
+
201
+ raise if args.empty? and harg.empty?
403
202
 
404
- meta[:reader] = false
405
- meta[:writer] = true
203
+ if !args.empty?
204
+ undef_keys = args.select{ |a| !method_defined?(a) }
205
+ unless undef_keys.empty?
206
+ attr#{m} *undef_keys
207
+ end
208
+ args.each { |a|
209
+ a = a.to_sym
210
+ an = define_annotation(a, harg)
211
+ ah = an.to_h.merge(:symbol => a)
212
+ # gmosx: allow for duplicate declarations.
213
+ properties![a] = Property.new(ah)
214
+ Property.eval_helpers(#{writer}, self, a, harg[:klass])
215
+ }
216
+ else
217
+ undef_keys = harg.keys.select{ |a| !method_defined?(a) }
218
+ attribute#{m} *undef_keys
219
+ harg.each { |a,h|
220
+ a = a.to_sym
221
+ an = define_annotation(a, h)
222
+ ah = an.to_h.merge(:symbol => a)
223
+ # gmosx: allow for property redefinitions.
224
+ properties![a] = Property.new(ah)
225
+ Property.eval_helpers(#{writer}, self, a, harg[:klass])
226
+ }
227
+ end
228
+
229
+ Module.__add_prop_hook__(self)
230
+ end
231
+ }
406
232
 
407
- for symbol in symbols
408
- prop(klass, symbol, meta)
409
- end
233
+ module_eval(code)
410
234
  end
235
+ alias :property :prop_accessor
236
+
237
+ # NITRO specific!! leave blank in nano/mega.
238
+ # TODO: factor in eval_helpers into the hook!
411
239
 
412
- # Helper method. Accepts a collection of symbols and generates
413
- # properties. Generates reader and writer.
414
- #
415
- # Example:
416
- # prop_accessor String, :name, :title, :body, :sql => "char(32)"
417
-
418
- def prop_accessor(*params)
419
- meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params)
420
-
421
- meta[:reader] = true
422
- meta[:writer] = true
423
-
424
- for symbol in symbols
425
- prop(klass, symbol, meta)
426
- end
240
+ def self.__add_prop_hook__(m)
241
+ m.send(:include, Og::EntityMixin) unless m.ancestors.include?(Og::EntityMixin)
242
+ m.send(:include, Glue::Validation) unless m.ancestors.include?(Glue::Validation)
243
+ m.send(:include, Glue::Aspects) unless m.ancestors.include?(Glue::Aspects)
427
244
  end
428
- alias_method :property, :prop_accessor
429
245
 
430
- # Attach metadata.
431
- # Guard against duplicates, no need to keep order.
432
- # This method uses closures :)
433
- #--
434
- # gmosx: crappy implementation, recode.
435
- #++
436
-
437
- def meta(key, *val)
438
- Glue::PropertyUtils.enchant(self)
439
-
440
- if val.empty?
441
- self.module_eval %{
442
- @__meta[key] = true
443
- }
444
- else
445
- val = val.first if val.size == 1
446
-
447
- self.module_eval %{
448
- @__meta[key] ||= []
449
- @__meta[key].delete_if { |v| val == v }
450
- @__meta[key] << val
451
- }
452
- end
453
- end
454
-
455
246
  end
456
247
 
457
248
  # * George Moschovitis <gm@navel.gr>
249
+ # * Tom Sawyer <transfire@gmail.com>
250
+ # * Chris Farmiloe <chris.farmiloe@farmiloe.com>