welo 0.0.6

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/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
+