glue 0.0.1 → 0.13.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.
- data/CHANGELOG +19 -0
- data/INSTALL +56 -0
- data/README +3 -0
- data/Rakefile +73 -10
- data/doc/AUTHORS +16 -0
- data/doc/LICENSE +33 -0
- data/doc/RELEASES +5 -0
- data/install.rb +47 -0
- data/lib/glue.rb +57 -59
- data/lib/glue/array.rb +61 -0
- data/lib/glue/attribute.rb +83 -0
- data/lib/glue/cache.rb +138 -0
- data/lib/glue/flexob.rb +12 -0
- data/lib/glue/hash.rb +122 -0
- data/lib/glue/inflector.rb +91 -0
- data/lib/glue/logger.rb +147 -0
- data/lib/glue/misc.rb +14 -0
- data/lib/glue/mixins.rb +36 -0
- data/lib/glue/number.rb +24 -0
- data/lib/glue/object.rb +32 -0
- data/lib/glue/pool.rb +60 -0
- data/lib/glue/property.rb +408 -0
- data/lib/glue/string.rb +162 -0
- data/lib/glue/time.rb +85 -0
- data/lib/glue/validation.rb +461 -0
- data/test/glue/tc_attribute.rb +22 -0
- data/test/glue/tc_cache.rb +45 -0
- data/test/glue/tc_hash.rb +38 -0
- data/test/glue/tc_logger.rb +39 -0
- data/test/glue/tc_numbers.rb +20 -0
- data/test/glue/tc_property.rb +91 -0
- data/test/glue/tc_property_mixins.rb +93 -0
- data/test/glue/tc_property_type_checking.rb +35 -0
- data/test/glue/tc_strings.rb +103 -0
- data/test/glue/tc_validation.rb +214 -0
- metadata +95 -89
- data/History.txt +0 -6
- data/Manifest.txt +0 -7
- data/README.txt +0 -127
- data/bin/glue +0 -1
- data/test/test_glue.rb +0 -218
data/lib/glue/logger.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: logger.rb 282 2005-03-10 12:24:53Z gmosx $
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
# A simple wrapper arround the Ruby logger. Mainly for
|
8
|
+
# compatibility purposes.
|
9
|
+
|
10
|
+
class Logger
|
11
|
+
alias_method :devel, :debug
|
12
|
+
alias_method :fine, :debug
|
13
|
+
|
14
|
+
# Prints a trace message to DEBUGLOG (at debug level).
|
15
|
+
# Useful for emitting the value of variables, etc. Use
|
16
|
+
# like this:
|
17
|
+
#
|
18
|
+
# x = y = 5
|
19
|
+
# trace 'x' # -> 'x = 5'
|
20
|
+
# trace 'x ** y' # -> 'x ** y = 3125'
|
21
|
+
#
|
22
|
+
# If you have a more complicated value, like an array of
|
23
|
+
# hashes, then you'll probably want to use an alternative
|
24
|
+
# output format. For instance:
|
25
|
+
#
|
26
|
+
# trace 'value', :yaml
|
27
|
+
#
|
28
|
+
# Valid output format values (the _style_ parameter) are:
|
29
|
+
#
|
30
|
+
# :p :inspect
|
31
|
+
# :pp (pretty-print, using 'pp' library)
|
32
|
+
# :s :to_s
|
33
|
+
# :y :yaml :to_yaml (using the 'yaml' library')
|
34
|
+
#
|
35
|
+
# The default is <tt>:p</tt>.
|
36
|
+
#
|
37
|
+
# CREDITS:
|
38
|
+
#
|
39
|
+
# This code comes straight from the dev-utils Gem.
|
40
|
+
# Author: Gavin Sinclair <gsinclair@soyabean.com.au>
|
41
|
+
|
42
|
+
def trace(expr, style=:p)
|
43
|
+
unless expr.respond_to? :to_str
|
44
|
+
warn "trace: Can't evaluate the given value: #{caller.first}"
|
45
|
+
else
|
46
|
+
require 'extensions/binding'
|
47
|
+
|
48
|
+
Binding.of_caller do |b|
|
49
|
+
value = b.eval(expr.to_str)
|
50
|
+
formatter = TRACE_STYLES[style] || :inspect
|
51
|
+
case formatter
|
52
|
+
when :pp then require 'pp'
|
53
|
+
when :y, :yaml, :to_yaml then require 'yaml'
|
54
|
+
end
|
55
|
+
value_s = value.send(formatter)
|
56
|
+
message = "#{expr} = #{value_s}"
|
57
|
+
lines = message.split(/\n/)
|
58
|
+
indent = " "
|
59
|
+
debug(lines.shift)
|
60
|
+
lines.each do |line|
|
61
|
+
debug(indent + line)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
TRACE_STYLES = {} # :nodoc:
|
67
|
+
TRACE_STYLES.update( :pp => :pp_s, :s => :to_s, :p => :inspect, :y => :to_yaml,
|
68
|
+
:yaml => :to_yaml, :inspect => :inspect, :to_yaml => :to_yaml )
|
69
|
+
|
70
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
71
|
+
# Global logger interface.
|
72
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
73
|
+
|
74
|
+
# Override the logger with your custom version.
|
75
|
+
#
|
76
|
+
@@global_logger = Logger.new(STDERR)
|
77
|
+
|
78
|
+
def self.set(logger)
|
79
|
+
@@global_logger = logger
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.get
|
83
|
+
@@global_logger
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.warn(str)
|
87
|
+
@@global_logger.warn(str)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.info(str)
|
91
|
+
@@global_logger.info(str)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.debug(str)
|
95
|
+
@@global_logger.debug(str)
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.error(str)
|
99
|
+
@@global_logger.error(str)
|
100
|
+
end
|
101
|
+
|
102
|
+
#--
|
103
|
+
# Saddly have to duplicate the code to make
|
104
|
+
# Binding.of_caller work.
|
105
|
+
#++
|
106
|
+
def self.trace(expr, style=:p)
|
107
|
+
unless expr.respond_to? :to_str
|
108
|
+
warn "trace: Can't evaluate the given value: #{caller.first}"
|
109
|
+
else
|
110
|
+
require 'extensions/binding'
|
111
|
+
|
112
|
+
Binding.of_caller do |b|
|
113
|
+
value = b.eval(expr.to_str)
|
114
|
+
formatter = TRACE_STYLES[style] || :inspect
|
115
|
+
case formatter
|
116
|
+
when :pp then require 'pp'
|
117
|
+
when :y, :yaml, :to_yaml then require 'yaml'
|
118
|
+
end
|
119
|
+
value_s = value.send(formatter)
|
120
|
+
message = "#{expr} = #{value_s}"
|
121
|
+
lines = message.split(/\n/)
|
122
|
+
indent = " "
|
123
|
+
debug(lines.shift)
|
124
|
+
lines.each do |line|
|
125
|
+
debug(indent + line)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
# the default Ruby logger has a hardwired silly format.
|
136
|
+
# we use some Ruby magic to fix it!
|
137
|
+
|
138
|
+
remove_const "Format"
|
139
|
+
|
140
|
+
Format = "%5s: %s\n"
|
141
|
+
|
142
|
+
def format_message(severity, timestamp, msg, progname)
|
143
|
+
Format % [severity, msg]
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
data/lib/glue/misc.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id$
|
4
|
+
|
5
|
+
# Executes a Ruby block without warnings.
|
6
|
+
|
7
|
+
def silence_warnings
|
8
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
9
|
+
begin
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
$VERBOSE = old_verbose
|
13
|
+
end
|
14
|
+
end
|
data/lib/glue/mixins.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id$
|
4
|
+
|
5
|
+
module N
|
6
|
+
|
7
|
+
# Generic expiring functionality mixin.
|
8
|
+
|
9
|
+
module Expirable
|
10
|
+
attr_accessor :expires
|
11
|
+
|
12
|
+
# Set the expires timeout for this entry.
|
13
|
+
|
14
|
+
def expires_after(timeout = (60*60*24))
|
15
|
+
@expires = Time.now + timeout
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set the expire timeout for this entry. The timeout happens
|
19
|
+
# after (base + rand(spread)) seconds.
|
20
|
+
|
21
|
+
def expires_spread(base, spread)
|
22
|
+
@expires = Time.now + base + rand(spread)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Is this entry expired?
|
26
|
+
|
27
|
+
def expired?
|
28
|
+
if @expires.nil? or (Time.now > @expires)
|
29
|
+
return true
|
30
|
+
else
|
31
|
+
return false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/glue/number.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: number.rb 282 2005-03-10 12:24:53Z gmosx $
|
4
|
+
|
5
|
+
module N;
|
6
|
+
|
7
|
+
# Implement as a module to avoid class polution. You can
|
8
|
+
# still use Ruby's advanced features to include the module in your
|
9
|
+
# class. Passing the object to act upon allows to check for nil,
|
10
|
+
# which isn't possible if you use self.
|
11
|
+
|
12
|
+
module NumberUtils
|
13
|
+
|
14
|
+
# Returns the multiple ceil of a number
|
15
|
+
#
|
16
|
+
def self.ceil_multiple(num, multiple)
|
17
|
+
# gmosx: to_f is needed!s
|
18
|
+
# gmosx: IS THERE a more optimized way to do this?
|
19
|
+
return ((num.to_f/multiple).ceil*multiple)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/glue/object.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: object.rb 282 2005-03-10 12:24:53Z gmosx $
|
4
|
+
|
5
|
+
# Code from RubyOnRails (http://www.rubyonrails.com)
|
6
|
+
|
7
|
+
class Object #:nodoc:
|
8
|
+
def remove_subclasses_of(superclass)
|
9
|
+
subclasses_of(superclass).each { |subclass| Object.send(:remove_const, subclass) rescue nil }
|
10
|
+
end
|
11
|
+
|
12
|
+
def subclasses_of(superclass)
|
13
|
+
subclasses = []
|
14
|
+
ObjectSpace.each_object(Class) do |k|
|
15
|
+
next if !k.ancestors.include?(superclass) || superclass == k || k.to_s.include?("::") || subclasses.include?(k.to_s)
|
16
|
+
subclasses << k.to_s
|
17
|
+
end
|
18
|
+
subclasses
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Code from RubyOnRails (http://www.rubyonrails.com)
|
23
|
+
|
24
|
+
class Class #:nodoc:
|
25
|
+
def remove_subclasses
|
26
|
+
Object.remove_subclasses_of(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def subclasses
|
30
|
+
Object.subclasses_of(self)
|
31
|
+
end
|
32
|
+
end
|
data/lib/glue/pool.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: pool.rb 282 2005-03-10 12:24:53Z gmosx $
|
4
|
+
|
5
|
+
require 'thread'
|
6
|
+
require 'monitor'
|
7
|
+
|
8
|
+
module N
|
9
|
+
|
10
|
+
# Generalized object pool implementation. Implemented as a thread
|
11
|
+
# safe stack. Exclusive locking is needed both for push and pop.
|
12
|
+
#
|
13
|
+
# INVESTIGATE: Could use the SizedQueue/Queue.
|
14
|
+
|
15
|
+
class Pool < Array
|
16
|
+
include MonitorMixin
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
super
|
20
|
+
@cv = new_cond()
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add, restore an object to the pool.
|
24
|
+
|
25
|
+
def push(obj)
|
26
|
+
synchronize do
|
27
|
+
super
|
28
|
+
@cv.signal()
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Obtain an object from the pool.
|
33
|
+
|
34
|
+
def pop
|
35
|
+
synchronize do
|
36
|
+
@cv.wait_while { empty? }
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Obtains an object, passes it to a block for processing
|
42
|
+
# and restores it to the pool.
|
43
|
+
|
44
|
+
def obtain
|
45
|
+
result = nil
|
46
|
+
|
47
|
+
begin
|
48
|
+
obj = pop()
|
49
|
+
|
50
|
+
result = yield(obj)
|
51
|
+
ensure
|
52
|
+
push(obj)
|
53
|
+
end
|
54
|
+
|
55
|
+
return result
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,408 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# * Michael Neumann <mneumann@ntecs.de>
|
3
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
4
|
+
# $Id: property.rb 282 2005-03-10 12:24:53Z gmosx $
|
5
|
+
|
6
|
+
require 'glue/attribute'
|
7
|
+
require 'glue/array'
|
8
|
+
require 'glue/hash'
|
9
|
+
|
10
|
+
module N
|
11
|
+
|
12
|
+
# Ruby attributes are typeless and generally this is good.
|
13
|
+
# Some times we need extra metadata though, for example in
|
14
|
+
# relational mapping, or web form population.
|
15
|
+
#
|
16
|
+
# Only Fixnums, Strings, Floats, Times, Booleans are
|
17
|
+
# converted.
|
18
|
+
#
|
19
|
+
# The default = methods do not force the types. A special
|
20
|
+
# __force_set method should be used instead.
|
21
|
+
#--
|
22
|
+
# TODO:
|
23
|
+
# Perhaps a sync is needed in evals (!!!!)
|
24
|
+
#++
|
25
|
+
|
26
|
+
class Property
|
27
|
+
|
28
|
+
# If set to true, perform type checking on property set.
|
29
|
+
# Useful when debugging.
|
30
|
+
|
31
|
+
cattr_accessor :type_checking, false
|
32
|
+
|
33
|
+
# The symbol of the property.
|
34
|
+
|
35
|
+
attr_accessor :symbol
|
36
|
+
|
37
|
+
# The string representation of the symbol.
|
38
|
+
|
39
|
+
attr_accessor :name
|
40
|
+
|
41
|
+
# The class of the property.
|
42
|
+
|
43
|
+
attr_accessor :klass
|
44
|
+
|
45
|
+
# Additional metadata (like sql declaration, sql index, etc)
|
46
|
+
# Here is a list of predefined metadata:
|
47
|
+
#
|
48
|
+
# [+:reader+]
|
49
|
+
# create reader?
|
50
|
+
#
|
51
|
+
# [+:writer+]
|
52
|
+
# create writer?
|
53
|
+
#
|
54
|
+
# [+:sql_index+]
|
55
|
+
# create an sql index for the column this poperty maps to?
|
56
|
+
#
|
57
|
+
# You can use this mechanism to add your own, custom,
|
58
|
+
# metadata.
|
59
|
+
|
60
|
+
attr_accessor :meta
|
61
|
+
|
62
|
+
def initialize(symbol, klass, meta = {})
|
63
|
+
@symbol, @klass = symbol, klass
|
64
|
+
@meta = meta
|
65
|
+
@name = @symbol.to_s()
|
66
|
+
end
|
67
|
+
|
68
|
+
def ==(other)
|
69
|
+
return @symbol == other.symbol
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
return name
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
# A collection of Property related utility methods.
|
79
|
+
|
80
|
+
module PropertyUtils
|
81
|
+
|
82
|
+
# Add accessors to the properties to the given target
|
83
|
+
# (Module or Class). For simplicity also create the
|
84
|
+
# meta accessors.
|
85
|
+
#
|
86
|
+
# [+target+]
|
87
|
+
# The target class or module
|
88
|
+
#--
|
89
|
+
# gmosx: Perhaps we 'll optimize this in the future.
|
90
|
+
#++
|
91
|
+
|
92
|
+
def self.enchant(target, force = false)
|
93
|
+
unless target.singleton_methods.include?('__props')
|
94
|
+
target.module_eval %{
|
95
|
+
@@__meta = N::SafeHash.new
|
96
|
+
@@__props = N::SafeArray.new
|
97
|
+
|
98
|
+
def self.__props
|
99
|
+
@@__props
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.__props=(props)
|
103
|
+
@@__props = props
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.__meta
|
107
|
+
@@__meta
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.__meta=(meta)
|
111
|
+
@@__meta = meta
|
112
|
+
end
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Copy properties from src (Module or Class) to dest.
|
118
|
+
|
119
|
+
def self.copy_props(src, dest)
|
120
|
+
src.__props.each do |p|
|
121
|
+
add_prop(dest, p)
|
122
|
+
end
|
123
|
+
|
124
|
+
# copy the metadata.
|
125
|
+
src.__meta.each do |k, val|
|
126
|
+
dest.__meta[k] = val.dup
|
127
|
+
# val.each { |v| dest.meta(k, v) } if val
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Add the property to the target (Class or Module)
|
132
|
+
|
133
|
+
def self.add_prop(target, prop)
|
134
|
+
if idx = target.__props.index(prop)
|
135
|
+
# override in case of duplicates. Keep the order of the props.
|
136
|
+
target.__props[idx] = prop
|
137
|
+
else
|
138
|
+
target.__props << prop
|
139
|
+
end
|
140
|
+
|
141
|
+
# Store the property in the :props_and_relations
|
142
|
+
# metadata array.
|
143
|
+
|
144
|
+
target.meta :props_and_relations, prop
|
145
|
+
|
146
|
+
# Precompile the property read/write methods
|
147
|
+
|
148
|
+
s, klass = prop.symbol, prop.klass
|
149
|
+
|
150
|
+
if prop.meta[:reader]
|
151
|
+
target.module_eval %{
|
152
|
+
def #{s}
|
153
|
+
return @#{s}
|
154
|
+
end
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
# gmosx: __force_xxx reuses xxx= to allow for easier
|
159
|
+
# overrides.
|
160
|
+
|
161
|
+
if prop.meta[:writer]
|
162
|
+
target.module_eval %{
|
163
|
+
#{prop_setter(prop)}
|
164
|
+
|
165
|
+
def __force_#{s}(val)
|
166
|
+
self.#{s}=(} + case klass.name
|
167
|
+
when Fixnum.name
|
168
|
+
"val.to_i()"
|
169
|
+
when String.name
|
170
|
+
"val.to_s()"
|
171
|
+
when Float.name
|
172
|
+
"val.to_f()"
|
173
|
+
when Time.name
|
174
|
+
"Time.parse(val.to_s())"
|
175
|
+
when TrueClass.name, FalseClass.name
|
176
|
+
"val.to_i() > 0"
|
177
|
+
else
|
178
|
+
"val"
|
179
|
+
end + %{)
|
180
|
+
end
|
181
|
+
}
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Generates the property setter code. Can be overriden
|
186
|
+
# to support extra functionality (example: markup)
|
187
|
+
|
188
|
+
def self.prop_setter(prop)
|
189
|
+
s = prop.symbol
|
190
|
+
|
191
|
+
code = %{
|
192
|
+
def #{s}=(val)
|
193
|
+
}
|
194
|
+
|
195
|
+
if N::Property.type_checking
|
196
|
+
code << %{
|
197
|
+
unless #{prop.klass} == val.class
|
198
|
+
raise "Invalid type, expected '#{prop.klass}', is '\#\{val.class\}'."
|
199
|
+
end
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
code << %{
|
204
|
+
@#{s} = val
|
205
|
+
end
|
206
|
+
}
|
207
|
+
|
208
|
+
return code
|
209
|
+
end
|
210
|
+
|
211
|
+
# Get the property metadata for the given symbol.
|
212
|
+
|
213
|
+
def self.get_prop(klass, sym)
|
214
|
+
return klass.__props.find { |p| p.symbol == sym }
|
215
|
+
end
|
216
|
+
|
217
|
+
# Include meta-language mixins
|
218
|
+
|
219
|
+
def self.include_meta_mixins(target)
|
220
|
+
target.module_eval %{ include N::Validation } if defined?(N::Validation)
|
221
|
+
# gmosx: TODO, make Og::MetaLanguage equivalent to Validation.
|
222
|
+
target.module_eval %{ extend Og::MetaLanguage } if defined?(Og::MetaLanguage)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Resolves the parameters passed to the propxxx macros
|
226
|
+
# to generate the meta, klass and symbols variables. This
|
227
|
+
# way the common functionality is factored out.
|
228
|
+
#
|
229
|
+
# [+params+]
|
230
|
+
# The params to resolve.
|
231
|
+
# [+one_symbol+]
|
232
|
+
# If true, only resolves one symbol (used in prop).
|
233
|
+
|
234
|
+
def self.resolve_prop_params(*params)
|
235
|
+
meta = {}
|
236
|
+
klass = Object
|
237
|
+
symbols = []
|
238
|
+
|
239
|
+
for param in params.flatten
|
240
|
+
if param.is_a?(Class)
|
241
|
+
klass = param
|
242
|
+
elsif param.is_a?(Symbol)
|
243
|
+
symbols << param
|
244
|
+
elsif param.is_a?(TrueClass) or param.is_a?(TrueClass)
|
245
|
+
writer = param
|
246
|
+
elsif param.is_a?(Hash)
|
247
|
+
# the meta hash.
|
248
|
+
meta.update(param) { |k, a, b| [a,b].join(' ') }
|
249
|
+
else
|
250
|
+
raise 'Error when defining property!'
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
raise 'No symbols provided!' if symbols.empty?
|
255
|
+
|
256
|
+
return meta, klass, symbols
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
end # module
|
262
|
+
|
263
|
+
class Module
|
264
|
+
|
265
|
+
# Define a property (== typed attribute)
|
266
|
+
# This works like Ruby's standard attr method, ie creates
|
267
|
+
# only one property.
|
268
|
+
#
|
269
|
+
# Use the prop_reader, prop_writer, prop_accessor methods
|
270
|
+
# for multiple properties.
|
271
|
+
#
|
272
|
+
# Examples:
|
273
|
+
# prop String, :name, :sql => "char(32), :sql_index => "name(32)"
|
274
|
+
# --> creates only writer.
|
275
|
+
# prop Fixnum, :oid, writer = true, :sql => "integer PRIMARY KEY"
|
276
|
+
# --> creates reader and writer.
|
277
|
+
|
278
|
+
def prop(*params)
|
279
|
+
meta, klass, symbols = N::PropertyUtils.resolve_prop_params(params)
|
280
|
+
symbol = symbols.first
|
281
|
+
|
282
|
+
N::PropertyUtils.enchant(self)
|
283
|
+
|
284
|
+
if self.is_a?(Class)
|
285
|
+
|
286
|
+
# Add some extra code to append features to
|
287
|
+
# subclasses.
|
288
|
+
|
289
|
+
self.module_eval %{
|
290
|
+
def self.inherited(sub)
|
291
|
+
N::PropertyUtils.enchant(sub)
|
292
|
+
N::PropertyUtils.copy_props(self, sub)
|
293
|
+
# gmosx: We have to define @@__props first to avoid reusing
|
294
|
+
# the hash from the module. super must stay at the end.
|
295
|
+
super
|
296
|
+
end
|
297
|
+
}
|
298
|
+
else
|
299
|
+
|
300
|
+
# Add some extra code for modules to append
|
301
|
+
# their features to classes that include it.
|
302
|
+
|
303
|
+
self.module_eval %{
|
304
|
+
def self.append_features(base)
|
305
|
+
N::PropertyUtils.enchant(base)
|
306
|
+
N::PropertyUtils.copy_props(self, base)
|
307
|
+
|
308
|
+
# gmosx: We have to define @@__props first to avoid reusing
|
309
|
+
# the hash from the module. super must stay at the end.
|
310
|
+
|
311
|
+
N::PropertyUtils.include_meta_mixins(base)
|
312
|
+
|
313
|
+
super
|
314
|
+
end
|
315
|
+
}
|
316
|
+
end
|
317
|
+
|
318
|
+
property = N::Property.new(symbol, klass, meta)
|
319
|
+
|
320
|
+
reader = meta[:reader] || true
|
321
|
+
writer = writer || meta[:writer] || false
|
322
|
+
|
323
|
+
meta[:reader] = true if meta[:reader].nil?
|
324
|
+
if defined?(writer)
|
325
|
+
meta[:writer] = writer
|
326
|
+
else
|
327
|
+
meta[:writer] = true if meta[:writer].nil?
|
328
|
+
end
|
329
|
+
|
330
|
+
N::PropertyUtils.add_prop(self, property)
|
331
|
+
|
332
|
+
# gmosx: should be placed AFTER enchant!
|
333
|
+
|
334
|
+
N::PropertyUtils.include_meta_mixins(self)
|
335
|
+
end
|
336
|
+
|
337
|
+
# Helper method. Accepts a collection of symbols and generates
|
338
|
+
# properties. Only generates reader.
|
339
|
+
#
|
340
|
+
# Example:
|
341
|
+
# prop_reader String, :name, :title, :body, :sql => "char(32)"
|
342
|
+
|
343
|
+
def prop_reader(*params)
|
344
|
+
meta, klass, symbols = N::PropertyUtils.resolve_prop_params(params)
|
345
|
+
|
346
|
+
meta[:reader] = true
|
347
|
+
meta[:writer] = false
|
348
|
+
|
349
|
+
for symbol in symbols
|
350
|
+
prop(klass, symbol, meta)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Helper method. Accepts a collection of symbols and generates
|
355
|
+
# properties. Only generates writer.
|
356
|
+
#
|
357
|
+
# Example:
|
358
|
+
# prop_writer String, :name, :title, :body, :sql => "char(32)"
|
359
|
+
|
360
|
+
def prop_writer(*params)
|
361
|
+
meta, klass, symbols = N::PropertyUtils.resolve_prop_params(params)
|
362
|
+
|
363
|
+
meta[:reader] = false
|
364
|
+
meta[:writer] = true
|
365
|
+
|
366
|
+
for symbol in symbols
|
367
|
+
prop(klass, symbol, meta)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Helper method. Accepts a collection of symbols and generates
|
372
|
+
# properties. Generates reader and writer.
|
373
|
+
#
|
374
|
+
# Example:
|
375
|
+
# prop_accessor String, :name, :title, :body, :sql => "char(32)"
|
376
|
+
|
377
|
+
def prop_accessor(*params)
|
378
|
+
meta, klass, symbols = N::PropertyUtils.resolve_prop_params(params)
|
379
|
+
|
380
|
+
meta[:reader] = true
|
381
|
+
meta[:writer] = true
|
382
|
+
|
383
|
+
for symbol in symbols
|
384
|
+
prop(klass, symbol, meta)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
alias_method :property, :prop_accessor
|
388
|
+
|
389
|
+
# Attach metadata.
|
390
|
+
# Guard against duplicates, no need to keep order.
|
391
|
+
# This method uses closures :)
|
392
|
+
#--
|
393
|
+
# gmosx: crappy implementation, recode.
|
394
|
+
#++
|
395
|
+
|
396
|
+
def meta(key, *val)
|
397
|
+
val = val.first if val.size == 1
|
398
|
+
|
399
|
+
N::PropertyUtils.enchant(self)
|
400
|
+
|
401
|
+
self.module_eval %{
|
402
|
+
@@__meta[key] ||= []
|
403
|
+
@@__meta[key].delete_if { |v| val == v }
|
404
|
+
@@__meta[key] << val
|
405
|
+
}
|
406
|
+
end
|
407
|
+
|
408
|
+
end
|