card 1.18.4 → 1.18.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +263 -240
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/VERSION +1 -1
  6. data/card.gemspec +5 -2
  7. data/config/initializers/01_core_extensions/array.rb +9 -0
  8. data/config/initializers/01_core_extensions/hash.rb +57 -0
  9. data/config/initializers/01_core_extensions/module.rb +14 -0
  10. data/config/initializers/01_core_extensions/object.rb +43 -0
  11. data/config/initializers/02_extensions/kaminari.rb +38 -0
  12. data/config/initializers/core_extensions.rb +24 -0
  13. data/config/initializers/extensions.rb +3 -0
  14. data/lib/card.rb +15 -10
  15. data/lib/card/director_register.rb +7 -0
  16. data/lib/card/loader.rb +29 -39
  17. data/lib/card/migration.rb +14 -0
  18. data/lib/card/query.rb +17 -128
  19. data/lib/card/query/attributes.rb +2 -244
  20. data/lib/card/query/conjunctions.rb +36 -0
  21. data/lib/card/query/helpers.rb +72 -0
  22. data/lib/card/query/interpretation.rb +124 -0
  23. data/lib/card/query/relational_attributes.rb +95 -0
  24. data/lib/card/query/sorting.rb +52 -0
  25. data/lib/card/set.rb +73 -362
  26. data/lib/card/set/format.rb +122 -0
  27. data/lib/card/set/helpers.rb +100 -0
  28. data/lib/card/set/loader.rb +98 -0
  29. data/lib/card/set/trait.rb +5 -2
  30. data/lib/card/subcards.rb +38 -12
  31. data/lib/cardio.rb +1 -1
  32. data/lib/generators/card/migration/templates/card_migration.erb +1 -1
  33. data/mod/01_core/chunk/include.rb +13 -1
  34. data/mod/01_core/set/all/event.rb +14 -0
  35. data/mod/01_core/set/all/fetch.rb +21 -7
  36. data/mod/01_core/set/all/states.rb +3 -1
  37. data/mod/01_core/set/all/subcards.rb +4 -2
  38. data/mod/01_core/spec/set/all/fetch_spec.rb +14 -1
  39. data/mod/02_basic_types/set/abstract/code_file.rb +6 -0
  40. data/mod/04_settings/set/type/setting.rb +10 -0
  41. data/mod/05_standard/set/all/rich_html/wrapper.rb +1 -1
  42. data/mod/05_standard/set/self/codenames.rb +0 -0
  43. data/mod/05_standard/spec/chunk/include_spec.rb +10 -10
  44. data/spec/config/initializers/core_extensions_spec.rb +15 -0
  45. data/spec/lib/card/stage_director_spec.rb +25 -1
  46. metadata +25 -8
  47. data/config/initializers/01_init_ruby_extensions.rb +0 -3
  48. data/lib/card/core_ext.rb +0 -107
@@ -0,0 +1,95 @@
1
+ class Card
2
+ class Query
3
+ module RelationalAttributes
4
+
5
+ def type val
6
+ restrict :type_id, val
7
+ end
8
+
9
+ def part val
10
+ right_val = val.is_a?(Integer) ? val : val.clone
11
+ any(left: val, right: right_val)
12
+ end
13
+
14
+ def left val
15
+ restrict :left_id, val
16
+ end
17
+
18
+ def right val
19
+ restrict :right_id, val
20
+ end
21
+
22
+ def editor_of val
23
+ act_join = Join.new(
24
+ from: self,
25
+ to: ['card_acts', "a#{table_id true}", 'actor_id']
26
+ )
27
+ joins << act_join
28
+ action_join = Join.new(
29
+ from: act_join,
30
+ to: ['card_actions', "an#{table_id true}", 'card_act_id'],
31
+ superjoin: act_join
32
+ )
33
+ join_cards val, from: action_join, from_field: 'card_id'
34
+ end
35
+
36
+ def edited_by val
37
+ action_join = Join.new(
38
+ from: self,
39
+ to: ['card_actions', "an#{table_id true}", 'card_id']
40
+ )
41
+ joins << action_join
42
+ act_join = Join.new(
43
+ from: action_join,
44
+ from_field: 'card_act_id',
45
+ to: ['card_acts', "a#{table_id true}"]
46
+ )
47
+ join_cards val, from: act_join, from_field: 'actor_id'
48
+ end
49
+
50
+ def last_editor_of val
51
+ join_cards val, to_field: 'updater_id'
52
+ end
53
+
54
+ def last_edited_by val
55
+ restrict :updater_id, val
56
+ end
57
+
58
+ def creator_of val
59
+ join_cards val, to_field: 'creator_id'
60
+ end
61
+
62
+ def created_by val
63
+ restrict :creator_id, val
64
+ end
65
+
66
+ def member_of val
67
+ interpret right_plus: [RolesID, refer_to: val]
68
+ end
69
+
70
+ def member val
71
+ interpret referred_to_by: { left: val, right: RolesID }
72
+ end
73
+
74
+ # ~~~~~~ PLUS RELATIONAL
75
+
76
+ def left_plus val
77
+ junction val, :left, :right_id
78
+ end
79
+
80
+ def right_plus val
81
+ junction val, :right, :left_id
82
+ end
83
+
84
+ def plus val
85
+ any(left_plus: val, right_plus: val.deep_clone)
86
+ end
87
+
88
+ def junction val, side, to_field
89
+ part_clause, junction_clause = val.is_a?(Array) ? val : [val, {}]
90
+ junction_val = clause_to_hash(junction_clause).merge side => part_clause
91
+ join_cards junction_val, to_field: to_field
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,52 @@
1
+ class Card
2
+ class Query
3
+ module Sorting
4
+ SORT_JOIN_TO_ITEM_MAP = { left: 'left_id', right: 'right_id' }.freeze
5
+
6
+ def sort val
7
+ return nil if @superquery
8
+ sort_field = val[:return] || 'db_content'
9
+ item = val.delete(:item) || 'left'
10
+
11
+ if sort_field == 'count'
12
+ sort_by_count val, item
13
+ elsif (join_field = SORT_JOIN_TO_ITEM_MAP[item.to_sym])
14
+ sq = join_cards(val, to_field: join_field,
15
+ side: 'LEFT',
16
+ conditions_on_join: true)
17
+ @mods[:sort] ||= "#{sq.table_alias}.#{sort_field}"
18
+ else
19
+ raise BadQuery, "sort item: #{item} not yet implemented"
20
+ end
21
+ end
22
+
23
+ # EXPERIMENTAL!
24
+ def sort_by_count val, item
25
+ if item == 'referred_to'
26
+ @mods[:sort] = 'coalesce(count,0)' # needed for postgres
27
+ cs = Query.new(
28
+ return: 'coalesce(count(*), 0) as count',
29
+ group: 'sort_join_field',
30
+ superquery: self
31
+ )
32
+ subselect = Query.new val.merge(return: 'id', superquery: self)
33
+ cs.add_condition "referer_id in (#{subselect.sql})"
34
+ # FIXME: - SQL generated before SQL phase
35
+ cs.joins << Join.new(
36
+ from: cs,
37
+ to: %w(card_references wr referee_id)
38
+ )
39
+ cs.mods[:sort_join_field] = "#{cs.table_alias}.id as sort_join_field"
40
+ # HACK!
41
+
42
+ joins << Join.new(
43
+ from: self,
44
+ to: [cs, 'srtbl', 'sort_join_field']
45
+ )
46
+ else
47
+ raise BadQuery, "count with item: #{item} not yet implemented"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,374 +1,85 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
3
  class Card
4
+ #
5
+ # A 'Set' is a group of Cards to which 'Rules' may be applied.
6
+ # Sets can be as specific as a single card, as general as all cards, or
7
+ # anywhere in between.
8
+ #
9
+ # Rules take two main forms: card rules and code rules.
10
+ #
11
+ # 'Card rules' are defined in card content. These are generally configured
12
+ # via the web interface and are thus documented at http://wagn.org/rules.
13
+ #
14
+ # 'Code rules' can be defined in a 'set module'. Card::Set supports
15
+ # creating and organizing these set modules. It also provides an API for
16
+ # defining special methods within set modules.
17
+ #
18
+ # Set modules follow the following naming convention:
19
+ #
20
+ # MOD/set/PATTERN/ANCHOR[/FREENAME].rb
21
+ #
22
+ # For example, suppose you created a "mod" (a Card modification) for managing
23
+ # your contacts called "contactmanager", and you wanted to write code
24
+ # that would apply to all +address cards. You could add a file here:
25
+ #
26
+ # ./contactmanager/set/right/address.rb
27
+ #
28
+ # or here:
29
+ #
30
+ # ./contactmanager/set/right/address/countries.rb
31
+ #
32
+ # Then, whenever you fetch or instantiate a +address card, the card will
33
+ # automatically include code from that set module. In fact, it will include
34
+ # all the set modules associated with sets of which it is a member.
35
+ #
36
+ # For example, say you have a Plaintext card named 'Philipp+address', and
37
+ # you have set files for the following sets:
38
+ #
39
+ # * all cards
40
+ # * all Plaintext cards
41
+ # * all cards ending in +address
42
+ #
43
+ # When you run this:
44
+ #
45
+ # mycard = Card.fetch 'Philipp+address'
46
+ #
47
+ # ...then mycard will include the set modules associated with each of those
48
+ # sets in the above order.
49
+ #
50
+ # You can quickly create a new set module running
51
+ #
52
+ # `wagn generate set MOD PATTERN ANCHOR`
53
+ #
54
+ # In the current example, this would translate to:
55
+ #
56
+ # `wagn generate set contactmanager right address`.
57
+ #
58
+ # Note that the set module's filename connects it to the set, so both
59
+ # the set_pattern and the set_anchor must correspond to the
60
+ # codename of a card in the database to function correctly.
61
+ #
62
+ # A set module is "just ruby", but is generally quite concise because
63
+ # Card (a) uses its the set module's file location to autogenerate ruby
64
+ # module names and (b) then uses Card::Set module to provide API for the
65
+ # most common set methods.
66
+ #
4
67
  module Set
5
68
  include Event
6
69
  include Trait
7
- mattr_accessor :modules, :traits
8
- @@modules = { base: [], base_format: {}, nonbase: {}, nonbase_format: {},
9
- abstract: {}, abstract_format: {} }
10
-
11
- # A 'Set' is a group of Cards to which 'Rules' may be applied.
12
- # Sets can be as specific as a single card, as general as all cards, or
13
- # anywhere in between.
14
- #
15
- # Rules take two main forms: card rules and code rules.
16
- #
17
- # 'Card rules' are defined in card content. These are generally configured
18
- # via the web interface and are thus documented at http://wagn.org/rules.
19
- #
20
- # 'Code rules' can be defined in a 'set file' within any 'Mod' (short for
21
- # both 'module' and 'modification'). In accordance with Wagn's 'MoVE'
22
- # architecture, there are two main kinds of code rules you can create in
23
- # set file: Views, and Events.
24
- # Events are associated with the Card class, and Views are associated with
25
- # a Format class.
26
- # You can also use set files to add or override Card and/or Format methods
27
- # directly. The majority of Card code is contained in these files.
28
- #
29
- # (FIXME - define mod, add generator)
30
- #
31
- # Whenever you fetch or instantiate a card, it will automatically include
32
- # all the set modules defined in set files associated with sets of which it
33
- # is a member. This entails both simple model methods and 'events', which
34
- # are special methods explored in greater detail below.
35
- #
36
- # For example, say you have a Plaintext card named 'Philipp+address', and
37
- # you have set files for the following sets:
38
- #
39
- # * all cards
40
- # * all Plaintext cards
41
- # * all cards ending in +address
42
- #
43
- # When you run this:
44
- #
45
- # mycard = Card.fetch 'Philipp+address'
46
- #
47
- # ...then mycard will include the set modules associated with each of those
48
- # ets in the above order. (The order is determined by the set pattern;
49
- # ee lib/card/set_pattern.rb for more information about set_ptterns and
50
- # od/core/set/all/fetch.rb for more about fetching.)
51
- #
52
- # imilarly, whenever a Format object is instantiated for a card, it
53
- # ncludes all views associated with BOTH (a) sets of which the card is a
54
- # ember and (b) the current format or its ancestors. More on defining
55
- # iews below.
56
- #
57
- #
58
- # In order to have a set file associated with "all cards ending in
59
- # +address", you could create a file in
60
- # mywagn/mod/mymod/set/right/address.rb.
61
- # The recommended mechanism for doing so is running `wagn generate set
62
- # modname set_pattern set_anchor`. In the current example, this
63
- # would translate to `wagn generate set mymod right address`.
64
- # Note that both the set_pattern and the set_anchor must correspond to the
65
- # codename of a card in the database to function correctly but you can add
66
- # arbitrary subdirectories to organize your code rules. The rule above
67
- # for example could be saved in
68
- # mywagn/mod/mymod/set/right/address/america/north/canada.rb.
69
- #
70
- #
71
- # When a Card application loads, it uses these files to autogenerate a
72
- # tmp_file that uses this set file to create a Card::Set::Right::Address
73
- # module which itself is extended with Card::Set. A set file is 'just ruby'
74
- # but is generally quite concise because Card uses its file location to
75
- # autogenerate ruby module names and then uses Card::Set module to provide
76
- # additional API.
77
- #
78
- #
79
- # View definitions
80
- #
81
- # When you declare:
82
- # view :view_name do |args|
83
- # #...your code here
84
- # end
85
- #
86
- # Methods are defined on the format
87
- #
88
- # The external api with checks:
89
- # render(:viewname, args)
90
-
91
- module Format
92
- mattr_accessor :views
93
- @@views = {}
94
-
95
- def view view, *args, &block
96
- view = view.to_viewname.key.to_sym
97
- views[self] ||= {}
98
- view_block = views[self][view] =
99
- if block_given?
100
- Card::Format.extract_class_vars view, args[0]
101
- block
102
- else
103
- alias_block view, args
104
- end
105
- define_method "_view_#{view}", view_block
106
- end
107
-
108
- def alias_block view, args
109
- opts = args[0].is_a?(Hash) ? args.shift : { view: args.shift }
110
- opts[:mod] ||= self
111
- opts[:view] ||= view
112
- views[opts[:mod]][opts[:view]] || raise
113
- rescue
114
- raise "cannot find #{opts[:view]} view in #{opts[:mod]}; " \
115
- "failed to alias #{view} in #{self}"
116
- end
117
- end
118
-
119
- def format *format_names, &block
120
- if format_names.empty?
121
- format_names = [:base]
122
- elsif format_names.first == :all
123
- format_names =
124
- Card::Format.registered.reject { |f| Card::Format.aliases[f] }
125
- end
126
- format_names.each do |f|
127
- define_on_format f, &block
128
- end
129
- end
130
70
 
131
- def define_on_format format_name=:base, &block
132
- # format class name, eg. HtmlFormat
133
- klass = Card::Format.format_class_name format_name
71
+ include Set::Format
72
+ include Set::Helpers
134
73
 
135
- # called on current set module, eg Card::Set::Type::Pointer
136
- mod = const_get_or_set klass do
137
- # yielding set format module, eg Card::Set::Type::Pointer::HtmlFormat
138
- m = Module.new
139
- register_set_format Card.const_get(klass), m
140
- m.extend Card::Set::Format
141
- m
142
- end
143
- mod.class_eval &block
144
- end
74
+ extend Set::Loader
145
75
 
146
- def view *args, &block
147
- format do
148
- view *args, &block
149
- end
150
- end
151
-
152
- def stage_method method, opts={}, &block
153
- class_eval do
154
- define_method "_#{method}", &block
155
- define_method method do |*args|
156
- error =
157
- if !director.stage_ok? opts
158
- if !stage
159
- "phase method #{method} called outside of event phases"
160
- else
161
- "#{opts.inspect} method #{method} called in phase #{stage}"
162
- end
163
- elsif !on_condition_applies?(opts[:on])
164
- "on: #{opts[:on]} method #{method} called on #{@action}"
165
- end
166
- if error
167
- raise Card::Error, error
168
- else
169
- send "_#{method}", *args
170
- end
171
- end
172
- end
173
- end
174
-
175
- # include a set module and all its format modules
176
- # @param [Module] set
177
- # @param [Hash] opts choose the formats you want to include
178
- # @option opts [Symbol, Array<Symbol>] :only include only these formats
179
- # @option opts [Symbol, Array<Symbol>] :except don't include these formats
180
- # @example
181
- # include_set Type::Basic, except: :css
182
- def include_set set, opts={}
183
- set_type = set.abstract_set? ? :abstract : :nonbase
184
- @@modules[set_type][set.shortname].each do |set_mod|
185
- include set_mod
186
- end
187
- include_set_formats set, opts
188
- end
189
-
190
- def each_format set
191
- set_type = set.abstract_set? ? :abstract : :nonbase
192
- format_type = "#{set_type}_format".to_sym
193
- @@modules[format_type].each_pair do |format, set_format_mod_hash|
194
- next unless (format_mods = set_format_mod_hash[set.shortname])
195
- yield format, format_mods
196
- end
197
- end
198
-
199
- # include a format modules of a set
200
- # @param [Module] set
201
- # @param [Hash] opts choose the formats you want to include
202
- # @option opts [Symbol, Array<Symbol>] :only include only these formats
203
- # @option opts [Symbol, Array<Symbol>] :except don't include these formats
204
- # @example
205
- # include_set Type::Basic, except: :css
206
- def include_set_formats set, opts={}
207
- each_format set do |format, format_mods|
208
- match = format.to_s.match(/::(?<format>[^:]+)Format/)
209
- format_sym = match ? match[:format] : :base
210
- next if opts[:except] && Array(opts[:except]).include?(format_sym)
211
- next if opts[:only] && !Array(opts[:only]).include?(format_sym)
212
- format_mods.each do |format_mod|
213
- define_on_format format_sym do
214
- include format_mod
215
- end
216
- end
217
- end
218
- end
219
-
220
- def ensure_set &block
221
- set_module = yield
222
- rescue NameError => e
223
- if e.message =~ /uninitialized constant (?:Card::Set::)?(.+)$/
224
- Regexp.last_match(1).split('::').inject(Card::Set) do |set_mod, module_name|
225
- set_mod.const_get_or_set module_name do
226
- Module.new
227
- end
228
- end
229
- end
230
- # try again - there might be another submodule that doesn't exist
231
- ensure_set &block
232
- else
233
- set_module.extend Card::Set
234
- end
235
- # the set loading process has two main phases:
236
-
237
- # 1. Definition: interpret each set file, creating/defining set and
238
- # set_format modules
239
- # 2. Organization: have base classes include modules associated with the
240
- # 'all' set, and clean up the other modules
241
-
242
- class << self
243
- # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
244
- # Definition Phase
245
-
246
- # each set file calls `extend Card::Set` when loaded
247
- def extended mod
248
- register_set mod
249
- end
250
-
251
- # make the set available for use
252
- def register_set set_module
253
- if set_module.all_set?
254
- # automatically included in Card class
255
- modules[:base] << set_module
256
- else
257
- set_type = set_module.abstract_set? ? :abstract : :nonbase
258
- # made ready for dynamic loading via #include_set_modules
259
- modules[set_type][set_module.shortname] ||= []
260
- modules[set_type][set_module.shortname] << set_module
261
- end
262
- end
263
-
264
- def write_tmp_file from_file, to_file, rel_path
265
- name_parts = rel_path.gsub(/\.rb/, '').split(File::SEPARATOR)
266
- submodules = name_parts.map { |a| "module #{a.camelize};" } * ' '
267
- file_content = <<EOF
268
- # -*- encoding : utf-8 -*-
269
- class Card; module Set; #{submodules} extend Card::Set
270
- # ~~~~~~~~~~~ above autogenerated; below pulled from #{from_file} ~~~~~~~~~~~
271
- #{File.read from_file}
272
-
273
- # ~~~~~~~~~~~ below autogenerated; above pulled from #{from_file} ~~~~~~~~~~~
274
- end;end;#{'end;' * name_parts.size}
275
- EOF
276
-
277
- FileUtils.mkdir_p File.dirname(to_file)
278
- File.write to_file, file_content
279
- to_file
280
- end
281
-
282
- # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
283
- # Organization Phase
284
-
285
- # 'base modules' are modules that are permanently included on the Card or
286
- # Format class
287
- # 'nonbase modules' are included dynamically on singleton_classes
288
- def process_base_modules
289
- process_base_module_list modules[:base], Card
290
- modules[:base_format].each do |format_class, modules_list|
291
- process_base_module_list modules_list, format_class
292
- end
293
- modules.delete :base
294
- modules.delete :base_format
295
- end
296
-
297
- def process_base_module_list list, klass
298
- list.each do |mod|
299
- klass.send :include, mod if mod.instance_methods.any?
300
- if (class_methods = mod.const_get_if_defined(:ClassMethods))
301
- klass.send :extend, class_methods
302
- end
303
- end
304
- end
305
-
306
- def clean_empty_modules
307
- clean_empty_module_from_hash modules[:nonbase]
308
- modules[:nonbase_format].values.each do |hash|
309
- clean_empty_module_from_hash hash
310
- end
311
- end
312
-
313
- def clean_empty_module_from_hash hash
314
- hash.each do |mod_name, modlist|
315
- modlist.delete_if { |x| x.instance_methods.empty? }
316
- hash.delete mod_name if modlist.empty?
317
- end
318
- end
319
- end
320
-
321
- def register_set_format format_class, mod
322
- if all_set?
323
- # ready to include in base format classes
324
- modules[:base_format][format_class] ||= []
325
- modules[:base_format][format_class] << mod
326
- else
327
- format_type = abstract_set? ? :abstract_format : :nonbase_format
328
- # ready to include dynamically in set members' format singletons
329
- format_hash = modules[format_type][format_class] ||= {}
330
- format_hash[shortname] ||= []
331
- format_hash[shortname] << mod
332
- end
333
- end
334
-
335
- def shortname
336
- parts = name.split '::'
337
- first = 2 # shortname eliminates Card::Set
338
- pattern_name = parts[first].underscore
339
- last = if pattern_name == 'abstract'
340
- first + 1
341
- else
342
- set_class = Card::SetPattern.find pattern_name
343
- first + set_class.anchor_parts_count
344
- end
345
- parts[first..last].join '::'
346
- end
347
-
348
- def abstract_set?
349
- name =~ /^Card::Set::Abstract::/
350
- end
351
-
352
- def all_set?
353
- name =~ /^Card::Set::All::/
354
- end
355
-
356
- private
357
-
358
- def set_specific_attributes *args
359
- Card.set_specific_attributes ||= []
360
- Card.set_specific_attributes += args.map(&:to_s)
361
- end
76
+ mattr_accessor :modules, :traits
77
+ self.modules = { base: [], base_format: {}, nonbase: {}, nonbase_format: {},
78
+ abstract: {}, abstract_format: {} }
362
79
 
363
- def attachment name, args
364
- include Abstract::Attachment
365
- set_specific_attributes name,
366
- :load_from_mod,
367
- :action_id_of_cached_upload,
368
- :empty_ok,
369
- "remote_#{name}_url".to_sym
370
- uploader_class = args[:uploader] || FileUploader
371
- mount_uploader name, uploader_class
372
- end
80
+ # SET MODULE API
81
+ #
82
+ # The most important parts of the set module API are views (see
83
+ # Card::Set::Format) and events (see Card::Set::Event)
373
84
  end
374
85
  end