welo 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,122 @@
1
+
2
+ = Welo
3
+
4
+ Welo is a library to work with resources in a general framework.
5
+ It deals with the following concerns:
6
+ - relationships between resources
7
+ - structuring and access control with perspectives: one may or may not be able to access all fields of a resource
8
+ - identifiers and epithets
9
+ - serialization of resources
10
+
11
+ == Perspectives
12
+
13
+ When you look at a house, you usually can see walls, windows, a door, and a
14
+ roof. If you can enter the house, then you will see rooms, furniture,
15
+ inhabitants and so on. Depending on the room where you are, you will not see
16
+ the roof. The concept of perspective is basically that: depending on where you
17
+ look from (or who you are), you cannot observe the same parts of the same
18
+ object. Perspectives have a name, and a set of symbols which corresponds to
19
+ what someone can observe through this perspective.
20
+
21
+ A piece of code for the above example could be:
22
+
23
+ class House
24
+ include Welo::Resource
25
+ perspective :outside, [:walls, :windows, :roof, :door]
26
+ perspective :inside, [:rooms, :furniture, :inhabitants]
27
+ end
28
+
29
+ == Identifiers
30
+
31
+ Say you have a lots of shirts, and picked one for a party. A close friend calls
32
+ you and ask you which clothes you're taking. Then, you'll describe your shirt
33
+ like "the black shirt with vertical stripes". You've just identified your
34
+ shirt, by, giving enough information to uniquelly identify a shirt. Then,
35
+ depending on the situation, you may identify a shirt differently. As an
36
+ example, the shop where you've bought your shirt has a reference number, and
37
+ maybe a barcode to identify it. By default, Welo will add a 'uuid' method
38
+ (which is aliased to the object_id) for identifying a resource. You can always
39
+ override that.
40
+
41
+ class Shirt
42
+ include Welo::Resource
43
+ # identify :default, :uuid #what Welo does for you
44
+ identify :friend, :colors, :stripes, :length
45
+ identify :shop, :refnum
46
+ end
47
+
48
+ == Relationships
49
+
50
+ Often, two things are related whith each other. With respect to the state of
51
+ the art. So far, a person may have many shirts but has only two biological
52
+ parents: a father and a mother. Currently, Welo has relationships of :one and
53
+ :many kinds of relationships. This allow you to define some elaborate structures:
54
+
55
+ class Person
56
+ include Welo::Resource
57
+ relationship :shirts, :Shirt, :many
58
+ relationship :father, :Person, :one
59
+ relationship :mother, :Person, :one
60
+ end
61
+
62
+ Someone used to relational system may notice that we only wrote one part of the
63
+ relationships: if a person has another Person as a parent, therefore, the
64
+ parents should have a sort of symmetrical relationship. For simplicity, Welo does not force you to write this symmetry relationship if you will not be using it in your program.
65
+ Similarly, for now, there are no complex associations, like through
66
+ associations. Welo's goal is not to build an ORM which optimizes SQL queries.
67
+
68
+ Finally, there are two ways to qualify relationships: Nestings and Epithets.
69
+ These two qualifiers are related to the various ways of identifying related
70
+ resources.
71
+
72
+ === Nesting
73
+ Nestings, let you use an external way of identifying a resource. In that case,
74
+ the resource identification's scheme depends on the resources, but the value of
75
+ the identification is within the nested resource itself. As an example, for a
76
+ company, an employee will be identified upon its social security number,
77
+ whereas, in a pub, friends will use his name.
78
+ Nesting, just mark a preferred way of identifying related resources.
79
+
80
+ class Person
81
+ include Welo::Resource
82
+ identify :friend, :name
83
+ identify :company, :ssn
84
+ relationship :friends, :Person, :many
85
+ nesting :friends, :friend
86
+ end
87
+
88
+ class Company
89
+ include Welo::Resource
90
+ relationship :employees, :Person, :many
91
+ nesting :employees, :company
92
+ end
93
+
94
+ === Epithets
95
+ Epithets are similar to nestings, but the way of identifying a resource is only
96
+ related to the pointing resource. As an example, a person may have three
97
+ preferred movies. This naming scheme is only personal to the person, but not to
98
+ the movies. So, if you know that an object has preferred movies, you don't need
99
+ to know anything about the movie to identify it within the context of a person.
100
+ For now, declaring requires you must first specify that the matching
101
+ relationship is a mere alias, then declare the epithet's method for identifying
102
+ the related resource.
103
+
104
+ class Movie
105
+ include Welo::Resource
106
+ end
107
+
108
+ class Person
109
+ include Welo::Resource
110
+ relationship :preferred_movies, :Movie, :many, :alias
111
+ epithet :preferred_movies, :rank_for_preffered_movie
112
+
113
+ def rank_for_preffered_movie(movie)
114
+ @preferred_movies.index(movie)
115
+ end
116
+ end
117
+
118
+ == Coming later
119
+
120
+ * Embedding (similar to mongo's embedded documents).
121
+ * ResourceProxys to manipulate distant resources, or resources observed from one perspective, with missing fields.
122
+
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+
2
+ require 'rubygems'
3
+ require 'rake/gempackagetask'
4
+
5
+ $LOAD_PATH.unshift('lib')
6
+ require 'welo'
7
+
8
+ spec = Gem::Specification.new do |s|
9
+ s.name = 'welo'
10
+ s.rubyforge_project = 'welo'
11
+ s.version = Welo::VERSION
12
+ s.author = Welo::AUTHORS.first
13
+ s.homepage = Welo::WEBSITE
14
+ s.summary = "A light resource model for Ruby"
15
+ s.email = "crapooze@gmail.com"
16
+ s.platform = Gem::Platform::RUBY
17
+
18
+ s.files = [
19
+ 'Rakefile',
20
+ 'TODO',
21
+ 'README',
22
+ 'lib/welo.rb',
23
+ 'lib/welo/core/resource.rb',
24
+ 'lib/welo/core/relationship.rb',
25
+ 'lib/welo/core/link.rb',
26
+ 'lib/welo/core/matcher.rb',
27
+ 'lib/welo/core/nesting.rb',
28
+ 'lib/welo/core/perspective.rb',
29
+ 'lib/welo/base/resource.rb',
30
+ ]
31
+
32
+ s.require_path = 'lib'
33
+ s.bindir = 'bin'
34
+ s.executables = []
35
+ s.has_rdoc = true
36
+ end
37
+
38
+ Rake::GemPackageTask.new(spec) do |pkg|
39
+ pkg.need_tar = true
40
+ end
41
+
42
+ task :gem => ["pkg/#{spec.name}-#{spec.version}.gem"] do
43
+ puts "generated #{spec.version}"
44
+ end
45
+
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ * Relationships
2
+ - we have :one, :many, we should be able to have, exactly_four, or one_per_foo
3
+ * Epitheting vs. Nesting vs. Identifying
4
+ - remove alias from Relationship and create a similar Epiteth object?
5
+ * Embeddings
6
+ - recursively embedded datastructures?
7
+ - how to work with perspectives?
8
+ * ResourceProxy
data/lib/welo.rb ADDED
@@ -0,0 +1,8 @@
1
+
2
+ module Welo
3
+ VERSION = "0.0.6"
4
+ AUTHORS = ['crapooze']
5
+ WEBSITE = "http://github.com/crapooze/welo"
6
+ LICENCE = "MIT"
7
+ autoload :Resource, 'welo/base/resource'
8
+ end
@@ -0,0 +1,69 @@
1
+
2
+ require 'welo/core/resource'
3
+ module Welo
4
+ module Resource
5
+
6
+ # returns a text representation of the resource, under
7
+ # the persp perspective,
8
+ # the format is not standardized, suitable for dev cycles
9
+ def to_text(persp)
10
+ ret = ''
11
+ structure_pairs(persp).each do |k,val|
12
+ ret << "##{k}\n"
13
+ if val.respond_to? :map
14
+ ret << val.map{|v| v.to_s}.join("\n")
15
+ else
16
+ ret << val.to_s
17
+ end
18
+ ret << "\n"
19
+ end
20
+ ret
21
+ end
22
+
23
+ # returns an array of pairs of fields name and fields values
24
+ # for the perspective.
25
+ #
26
+ # the order of the pairs is the same as the fields in the perspective
27
+ #
28
+ # Links to other resources are represented as urls by sending
29
+ # them the :to_s method.
30
+ # the name of this method comes from the fact that Links are serialized to
31
+ # strings
32
+ def serialized_pairs(persp)
33
+ ary = structure_pairs(persp).map do |sym, val|
34
+ i1 = case val
35
+ when Link
36
+ val.to_s
37
+ when LinksEnumerator
38
+ val.map{|v| v.to_s}
39
+ else
40
+ val
41
+ end
42
+ [sym, i1]
43
+ end
44
+ end
45
+
46
+ # similar to to_rb_hash, but with the urls serialized
47
+ def to_serialized_hash(persp)
48
+ Hash[serialized_pairs(persp)]
49
+ end
50
+
51
+ # returns a JSON representation of the resource, under
52
+ # the persp perspective
53
+ def to_json(persp)
54
+ to_serialized_hash(persp).to_json
55
+ end
56
+
57
+ # returns a YAML representation of the resource, under
58
+ # the persp perspective
59
+ def to_YAML(persp)
60
+ YAML.dump to_serialized_hash(persp)
61
+ end
62
+
63
+ # shortcut to call to_#{ext}
64
+ def to_ext(ext, persp=:default)
65
+ meth = ext.sub(/^\./, 'to_')
66
+ send(meth, persp)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,123 @@
1
+
2
+ module Welo
3
+ module CanBeLazy
4
+ # A flag to say if this object is lazy or not
5
+ attr_accessor :lazy
6
+
7
+ # A hash of blocks between methods name and lazy evaluations
8
+ attr_accessor :lazy_blocks
9
+
10
+ def lazy_blocks
11
+ @lazy_blocks ||= {}
12
+ end
13
+
14
+ # true if lazy
15
+ def lazy?
16
+ (@lazy && true) or @lazy.nil?
17
+ end
18
+ end
19
+
20
+ class Link
21
+ include CanBeLazy
22
+
23
+ # The pointing resource
24
+ attr_accessor :from
25
+
26
+ # The pointed resource
27
+ attr_accessor :to
28
+
29
+ # The labeling for this link
30
+ attr_accessor :label
31
+
32
+ # The locality for this link
33
+ attr_accessor :local
34
+
35
+ # Creates a link to the pointed resource to,
36
+ # it must respond to :path
37
+ def initialize(from, to=nil, params={}, &blk)
38
+ @from = from
39
+ @to = to
40
+ @lazy = params.has_key?(:lazy) ? params[:lazy] : true
41
+ @label = params[:label]
42
+ @local = params[:local]
43
+ lazy_blocks[:to] = blk if block_given?
44
+ end
45
+
46
+ # If the link is lazy and there is no value to @to,
47
+ # will evaluate the :to lazy_block and set to to this value.
48
+ # If the link is not lazy, will just return @to
49
+ def to
50
+ if @lazy
51
+ @to ||= lazy_blocks[:to].call
52
+ else
53
+ @to
54
+ end
55
+ end
56
+
57
+ # Gets the nesting of the from resource of this link.
58
+ def nesting
59
+ @nesting ||= from.nesting(label)
60
+ end
61
+
62
+ # Gets the identifying scheme corresponding to this link.
63
+ # For now the identification scheme is written in the to resource.
64
+ # Later, we'll try to be able to identify pointed resources with
65
+ # an identifying scheme local to the from resource.
66
+ def identify
67
+ identify = if nesting
68
+ nesting.identifier_sym
69
+ else
70
+ :default
71
+ end
72
+ end
73
+
74
+ # Returns an absolute version of the path.
75
+ # Meaning that we use the default identifying scheme.
76
+ def to_absolute_path
77
+ File.join('', to.path(:default).to_s)
78
+ end
79
+
80
+ # Returns a relative version of the path.
81
+ # We can use a different identification scheme of the pointed resource.
82
+ # Later, we'll try to be able to identify pointed resources with
83
+ # a identifying scheme local to the from resource.
84
+ def to_relative_path
85
+ File.join('.', to.path(identify).to_s)
86
+ end
87
+
88
+ def to_local_path
89
+ File.join('.', from.epithet_path_to(to, label).to_s)
90
+ end
91
+
92
+ # Returns a string representation of this link
93
+ # (i.e., an path)
94
+ def to_s
95
+ if nesting
96
+ to_relative_path
97
+ elsif local
98
+ to_local_path
99
+ else
100
+ to_absolute_path
101
+ end
102
+ end
103
+ end
104
+
105
+ class LinksEnumerator
106
+ include Enumerable
107
+
108
+ # the enumerating object
109
+ attr_reader :enum
110
+
111
+ def initialize(enum=nil, params={}, &blk)
112
+ @enum = enum || blk
113
+ end
114
+
115
+ def each
116
+ if enum.respond_to? :call
117
+ enum.call{|i| yield(i)}
118
+ else
119
+ enum.each{|i| yield(i)}
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,101 @@
1
+
2
+ module Welo
3
+ class Matcher
4
+ attr_reader :params, :prefix
5
+
6
+ # Initializes a new matcher for the set of params (a hash) a prefix may be
7
+ # given, in which case, only the keys prefixed by the prefix
8
+ # will be selected.
9
+ def initialize(params, prefix='')
10
+ @prefix = prefix.dup.freeze
11
+ @params = if prefix.empty?
12
+ params.dup.freeze
13
+ else
14
+ h = {}
15
+ keys = params.keys.select do |k|
16
+ k.start_with?(prefix)
17
+ end
18
+ keys.each do |k|
19
+ h[k] = params[k]
20
+ end
21
+ h.freeze
22
+ end
23
+ end
24
+
25
+ def missing_params?(*args)
26
+ missing_params(*args).any?
27
+ end
28
+
29
+ def too_many_params?(*args)
30
+ extra_params(*args).any?
31
+ end
32
+
33
+ def wrong_params_set?(*args)
34
+ missing_params?(*args) or
35
+ too_many_params?(*args)
36
+ end
37
+
38
+ end
39
+
40
+ class IdentifyingMatcher < Matcher
41
+ def identifiers_for_params_matching(resource, ident=:default)
42
+ resource.identifiers(ident, prefix).map{|id| id.sub(/^:/,'')}
43
+ end
44
+
45
+ def missing_params(resource, ident=:default)
46
+ (identifiers_for_params_matching(resource, ident) - params.keys)
47
+ end
48
+
49
+ def extra_params(resource, ident=:default)
50
+ (params.keys - identifiers_for_params_matching(resource, ident))
51
+ end
52
+
53
+ # Returns true if the params hash qualifies for identifying this resource.
54
+ # prefixing is possible in case the hash's keys has prefixes
55
+ # e.g., Foo = Struct.new(:a, :b) do
56
+ # include Resource
57
+ # identify :default, :a, :b
58
+ # end
59
+ # foo = Foo.new(1,2)
60
+ # params = { 'bla.a' => 1, 'bla.b' => 2 }
61
+ # Matcher.new(params, 'bla.').match?(foo, :default)
62
+ # # => true
63
+ # see Resource#identifiers and identifiers_for_params_matching for more on prefix.
64
+ def match?(resource, ident=:default)
65
+ return false if wrong_params_set?(resource, ident)
66
+ pairs = [resource.identify(ident), identifiers_for_params_matching(resource, ident)].transpose
67
+ pairs.inject(true) do |bool, pair|
68
+ sym, param_name = *pair
69
+ bool and resource.send(sym) == params[param_name]
70
+ end
71
+ end
72
+
73
+ alias :=~ :match?
74
+ end
75
+
76
+ class EpithetMatcher < Matcher
77
+ def epithets_for_params_matching(resource, epithet_resource, label)
78
+ resource.epithets(label, prefix).map{|id| id.sub(/^:/,'')}
79
+ end
80
+
81
+ def missing_params(resource, epithet_resource, label)
82
+ (epithets_for_params_matching(resource, epithet_resource, label) - params.keys)
83
+ end
84
+
85
+ def extra_params(resource, epithet_resource, label)
86
+ (params.keys - epithets_for_params_matching(resource, epithet_resource, label))
87
+ end
88
+
89
+ def match?(resource, epithet_resource, label)
90
+ return false if wrong_params_set?(resource, epithet_resource, label)
91
+ pairs = [resource.epithet(label),
92
+ epithets_for_params_matching(resource, epithet_resource, label)].transpose
93
+ pairs.inject(true) do |bool, pair|
94
+ sym, param_name = *pair
95
+ bool and resource.send(sym, epithet_resource) == params[param_name]
96
+ end
97
+ end
98
+
99
+ alias :=~ :match?
100
+ end
101
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module Welo
3
+ # Nesting are not embeddings they just points to other resources with local
4
+ # identification namings scheme
5
+ class Nesting
6
+ # The symbol for the method to call to get the
7
+ # nested resource
8
+ attr_accessor :resource_sym
9
+
10
+ # The symbol for the method to identify the
11
+ # nested resource
12
+ attr_accessor :identifier_sym
13
+
14
+ def initialize(sym1, sym2)
15
+ @resource_sym = sym1
16
+ @identifier_sym = sym2
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module Welo
3
+ class Perspective
4
+ attr_reader :name
5
+ attr_reader :fields #symbols or relationships?
6
+
7
+ def initialize(name, fields)
8
+ @name = name
9
+ @fields = fields
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,44 @@
1
+
2
+ module Welo
3
+ class Relationship
4
+ # The symbol for the method to call to get the
5
+ # related resource
6
+ attr_accessor :sym
7
+
8
+ # The kinds of relationship
9
+ attr_accessor :kinds
10
+
11
+ # The klass of the related resource
12
+ attr_accessor :klass
13
+
14
+ # Returns true if the kinds are incompatible
15
+ # current incompatibilities:
16
+ # - :one and :many at the same time
17
+ def self.incompatible_kinds?(kinds)
18
+ (kinds & [:one, :many]).size == 2
19
+ end
20
+
21
+ # creates a new relationship for sym and klass, with all the given kinds
22
+ # providing incompatibles kinds lead to an ArgumentError
23
+ def initialize(sym, klass, kinds)
24
+ raise ArgumentError, "incompatible kinds" if self.class.incompatible_kinds?(kinds)
25
+ @sym = sym
26
+ @klass = klass
27
+ @kinds = kinds
28
+ end
29
+
30
+ # true if at least one of the kinds is :one
31
+ def one?
32
+ kinds.include?(:one)
33
+ end
34
+
35
+ # true if at least one of the kinds is :many
36
+ def many?
37
+ kinds.include?(:many)
38
+ end
39
+
40
+ def alias?
41
+ kinds.include?(:alias)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,319 @@
1
+
2
+ module Welo
3
+ autoload :Link, 'welo/core/link'
4
+ autoload :LinksEnumerator, 'welo/core/link'
5
+ autoload :Relationship, 'welo/core/relationship'
6
+ autoload :Perspective, 'welo/core/perspective'
7
+ autoload :Nesting, 'welo/core/nesting'
8
+ autoload :IdentifyingMatcher, 'welo/core/matcher'
9
+ autoload :EpithetMatcher, 'welo/core/matcher'
10
+
11
+ module Resource
12
+
13
+ # A simple hook to extend the ClassMethods
14
+ def self.included(mod)
15
+ mod.extend ClassMethods
16
+ end
17
+
18
+ module ClassMethods
19
+ # A hook to duplicates
20
+ # * relationships
21
+ # * perspectives
22
+ # * identification
23
+ # * base_path
24
+ # Such that the subclass looks like the parent one
25
+ def inherited(klass)
26
+ relationships.each_pair do |k,v|
27
+ klass.relationships[k] = v
28
+ end
29
+ nestings.each_pair do |k,v|
30
+ klass.nestings[k] = v
31
+ end
32
+ perspectives.each_pair do |k,v|
33
+ klass.perspectives[k] = v
34
+ end
35
+ identifiers_hash.each_pair do |k,v|
36
+ klass.identifiers_hash[k] = v
37
+ end
38
+ klass.base_path base_path
39
+ end
40
+
41
+ # The hash of relationships with other resources
42
+ def relationships
43
+ @relationships ||= {}
44
+ end
45
+
46
+ # The hash of nestings of other resources
47
+ def nestings
48
+ @nestings ||= {}
49
+ end
50
+
51
+ # If one argument, returns the relationship with the given name
52
+ # If more than one argument, registers a new relationship,
53
+ # possibly overwriting it.
54
+ def relationship(sym, klass=nil, kinds=[])
55
+ if klass
56
+ relationships[sym] = Relationship.new(sym, klass, kinds)
57
+ else
58
+ relationships[sym]
59
+ end
60
+ end
61
+
62
+ # The hash of perspectives for this resource.
63
+ def perspectives
64
+ @perspectives ||= {}
65
+ end
66
+
67
+ # If one argument, returns the perspective for the given name,
68
+ # or the :default one if it exists
69
+ # If more than one argument, registers a new perspective
70
+ def perspective(name, syms=nil)
71
+ if syms.nil?
72
+ perspectives[name] || perspectives[:default]
73
+ else
74
+ perspectives[name] = Perspective.new(name, syms)
75
+ end
76
+ end
77
+
78
+ # If one argument, returns the nesting for the given resource_sym
79
+ # If two arguments: creates a nesting of a resource in this resource
80
+ def nesting(resource_sym, identifier_sym=nil)
81
+ if identifier_sym.nil?
82
+ nestings[resource_sym]
83
+ else
84
+ nestings[resource_sym] = Nesting.new(resource_sym, identifier_sym)
85
+ end
86
+ end
87
+
88
+ # Returns the downcased last part of the ruby name
89
+ def default_base_path_name
90
+ self.name.split('::').last.downcase
91
+ end
92
+
93
+ # If there is one argument, sets the base_path,
94
+ # otherwise returns the base_path, if not set, will fallback to
95
+ # the default_base_path_name
96
+ def base_path(val=nil)
97
+ if val
98
+ @base_path = val
99
+ else
100
+ @base_path || default_base_path_name
101
+ end
102
+ end
103
+
104
+ # An hash to log the identifiers, by default, uses [:uuid]
105
+ def identifiers_hash
106
+ @identifiers_hash ||= {:default => [:uuid]}
107
+ end
108
+
109
+ # If there is no argument, returns the identification fields
110
+ # (defaults to [:uuid]).
111
+ # Otherwise, sets the identification fields.
112
+ # Fields are symbols of methods that will be sent to the instances.
113
+ # Since they may be used to build an URL, identifying fields
114
+ # must respond to :to_s.
115
+ # See Resource#identifying_path_part
116
+ def identify(sym, vals=nil)
117
+ if vals.nil?
118
+ identifiers_hash[sym]
119
+ else
120
+ identifiers_hash[sym] = vals
121
+ end
122
+ end
123
+
124
+ # Returns array of formatted strings from the identification fields.
125
+ # a prefix may be appended before the identification field
126
+ # e.g.
127
+ # 'foo' (no prefix) => 'foo'
128
+ # 'foo' (prefix:'bla.') => 'bla.foo'
129
+ def identifiers(ident, prefix='')
130
+ identify(ident).map{|sym| "#{prefix}#{sym}"}
131
+ end
132
+
133
+ def epithets_hash
134
+ @epithets_hash ||= {}
135
+ end
136
+
137
+ def epithet(sym, vals=nil)
138
+ if vals.nil?
139
+ epithets_hash[sym]
140
+ else
141
+ epithets_hash[sym] = vals
142
+ end
143
+ end
144
+
145
+ # Returns array of formatted strings from the epithets fields.
146
+ # a prefix may be appended before the epithets field
147
+ # e.g.
148
+ # 'foo' (no prefix) => 'foo'
149
+ # 'foo' (prefix:'bla.') => 'bla.foo'
150
+ def epithets(label, prefix='')
151
+ epithet(label).map{|sym| "#{prefix}#{sym}"}
152
+ end
153
+
154
+ # Returns the path model for items of this resource,
155
+ # including an optional prefix
156
+ # e.g. class Foo
157
+ # base_path 'foo'
158
+ # identify :default, :bar
159
+ # end
160
+ #
161
+ # Foo.path_model(ident, 'pfx.')
162
+ # #=> 'foo/:pfx.bar'
163
+ def path_model(ident, prefix='')
164
+ File.join(base_path, identifiers(ident, ':' + prefix))
165
+ end
166
+
167
+ # Returns the structure associated to the selected perspective
168
+ # the structure replace the perspective's fields by relationships whenever
169
+ # it's possible
170
+ def structure(name)
171
+ persp = perspective(name)
172
+ raise ArgumentError.new("no such perspective: #{name} for #{self}") unless persp
173
+ fields = persp.fields
174
+ fields.map do |field|
175
+ rel = relationship(field)
176
+ (rel || field)
177
+ end
178
+ end
179
+ end # ClassMethods
180
+
181
+ # Shorthand for class' base_path
182
+ def base_path
183
+ self.class.base_path
184
+ end
185
+
186
+ # Shorthand for class' path_model
187
+ def path_model(ident=:default, prefix='')
188
+ self.class.path_model(ident, prefix)
189
+ end
190
+
191
+ # Shorthand for class' identify
192
+ def identify(ident=:default)
193
+ self.class.identify(ident)
194
+ end
195
+
196
+ # Shorthand for class' epithet
197
+ def epithet(name)
198
+ self.class.epithet(name)
199
+ end
200
+
201
+ # Shorthand for class' perspective
202
+ def perspective(sym)
203
+ self.class.perspective(sym)
204
+ end
205
+
206
+ # Shorthand for class' relationship
207
+ def relationship(sym)
208
+ self.class.relationship(sym)
209
+ end
210
+
211
+ # Shorthand for class' nesting
212
+ def nesting(resource_sym)
213
+ self.class.nesting(resource_sym)
214
+ end
215
+
216
+ # Returns the resource's path part mapping the various fields.
217
+ # The objects must respond to .to_s and return a value such that
218
+ # the URL part is valid (esp. no whitespace)
219
+ # This method does NOT check wether the return of .to_s builds a
220
+ # valid URL part
221
+ def identifying_path_part(ident=:default)
222
+ File.join identify(ident).map{|sym| send(sym).to_s}
223
+ end
224
+
225
+ # Returns the full URL of this object
226
+ def path(ident=:default)
227
+ tail = identifying_path_part(ident)
228
+ if tail.empty?
229
+ base_path
230
+ else
231
+ File.join base_path, tail
232
+ end
233
+ end
234
+
235
+ # Same as Resource.identifiers
236
+ def identifiers(ident=:default, prefix='')
237
+ identify(ident).map{|sym| ":#{prefix}#{sym}"}
238
+ end
239
+
240
+ # Shorthand for class' structure
241
+ def structure(name)
242
+ self.class.structure(name)
243
+ end
244
+
245
+ # Returns the structure as an array of pairs which first object is the
246
+ # field name, the second object being the value.
247
+ # When the class' structure mandates a Relationship, then the
248
+ # second object of the pair is a Link to the related resource,
249
+ # or an LinksEnumerator if it has many resources.
250
+ def structure_pairs(name)
251
+ structure(name).map do |rel|
252
+ sym = nil
253
+ ret = case rel
254
+ when Symbol
255
+ sym = rel
256
+ send(sym)
257
+ when Relationship
258
+ sym = rel.sym
259
+ link_for_rel(rel)
260
+ end #case rel
261
+ [sym, ret]
262
+ end
263
+ end
264
+
265
+ def link_for_rel(rel)
266
+ if rel.one?
267
+ single_link_for_rel(rel)
268
+ elsif rel.many?
269
+ links_enumerator_for_rel(rel)
270
+ else
271
+ raise ArgumentError, "unkown relationship kinds #{rel.kinds}"
272
+ end #case rel.kind
273
+ end
274
+
275
+ # returns a lazy link loader for the relationship
276
+ # i.e., the evaluation of the sym method may be delayed
277
+ def single_link_for_rel(rel)
278
+ sym = rel.sym
279
+ Link.new(self, nil, :label => sym, :local => rel.alias?) do
280
+ send(sym)
281
+ end
282
+ end
283
+
284
+ # returns a lazy link loader enumerator for the relationship
285
+ # it will yield a link
286
+ def links_enumerator_for_rel(rel)
287
+ sym = rel.sym
288
+ LinksEnumerator.new do |&blk|
289
+ send(sym).each do |i|
290
+ blk.call Link.new(self, i, :label => sym, :local => rel.alias?)
291
+ end
292
+ end
293
+ end
294
+
295
+ def match_params?(params, ident=:default, prefix='')
296
+ IdentifyingMatcher.new(params, prefix).match?(self, ident)
297
+ end
298
+
299
+ alias uuid object_id
300
+
301
+ def epitheting_path_part(resource, label)
302
+ File.join epithet(label).map{|sym| send(sym, resource).to_s}
303
+ end
304
+
305
+ def epithet_path_to(resource, label)
306
+ File.join label.to_s, epitheting_path_part(resource,label)
307
+ end
308
+
309
+ # Same as Resource.epithets
310
+ def epithets(label, prefix='')
311
+ epithet(label).map{|sym| ":#{prefix}#{sym}"}
312
+ end
313
+
314
+ def epithet_resource_match_params?(resource, params, label, prefix='')
315
+ EpithetMatcher.new(params, prefix).match?(self, resource, label)
316
+ end
317
+
318
+ end # Resource
319
+ end # Welo
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: welo
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 6
9
+ version: 0.0.6
10
+ platform: ruby
11
+ authors:
12
+ - crapooze
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-14 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: crapooze@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - Rakefile
31
+ - TODO
32
+ - README
33
+ - lib/welo.rb
34
+ - lib/welo/core/resource.rb
35
+ - lib/welo/core/relationship.rb
36
+ - lib/welo/core/link.rb
37
+ - lib/welo/core/matcher.rb
38
+ - lib/welo/core/nesting.rb
39
+ - lib/welo/core/perspective.rb
40
+ - lib/welo/base/resource.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/crapooze/welo
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project: welo
69
+ rubygems_version: 1.3.7
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: A light resource model for Ruby
73
+ test_files: []
74
+