workflow 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6c18ece78f869cf0939fd801a99b63a93b99eafd
4
+ data.tar.gz: 949a3b5bff0e79ced88e7ba5a3050820f58efd40
5
+ SHA512:
6
+ metadata.gz: 801d65a4147f19643d429d38e794f9529ff760147cf63185ab19762ddf618deb8f8ff19deb0ec590b8a3cd7166c99bfa129fdf716dc8b261c7cb98478685a494
7
+ data.tar.gz: 5cc6722b3029ab9a96a2247215706315561735f6f7fa5950727f22b969040c53bba907532e3fada31d485c9c9ac6a39a66b130ed956f61b8ef68a2f566018332
@@ -2,8 +2,9 @@ before_install:
2
2
  - sudo apt-get install -qq graphviz
3
3
 
4
4
  rvm:
5
- - 1.9.3
6
5
  - 2.0.0
6
+ gemfile:
7
+ - gemfiles/Gemfile.rails-edge
7
8
 
8
9
  matrix:
9
10
  include:
@@ -12,3 +13,9 @@ matrix:
12
13
  gemfile: gemfiles/Gemfile.rails-2.3.x
13
14
  # running a smaller test set for old Rails and Ruby
14
15
  script: rake test_without_new_versions
16
+
17
+ - rvm: 1.9.3
18
+ gemfile: gemfiles/Gemfile.rails-3.x
19
+
20
+ - rvm: 2.0.0
21
+ gemfile: gemfiles/Gemfile.rails-3.x
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
-
@@ -1,5 +1,6 @@
1
1
  [![Build Status](https://travis-ci.org/geekq/workflow.png?branch=master)](https://travis-ci.org/geekq/workflow)
2
2
 
3
+
3
4
  What is workflow?
4
5
  -----------------
5
6
 
@@ -63,10 +64,10 @@ Let's create an article instance and check in which state it is:
63
64
  You can also access the whole `current_state` object including the list
64
65
  of possible events and other meta information:
65
66
 
66
- article.current_state
67
+ article.current_state
67
68
  => #<Workflow::State:0x7f1e3d6731f0 @events={
68
- :submit=>#<Workflow::Event:0x7f1e3d6730d8 @action=nil,
69
- @transitions_to=:awaiting_review, @name=:submit, @meta={}>},
69
+ :submit=>#<Workflow::Event:0x7f1e3d6730d8 @action=nil,
70
+ @transitions_to=:awaiting_review, @name=:submit, @meta={}>},
70
71
  name:new, meta{}
71
72
 
72
73
  On Ruby 1.9 and above, you can check whether a state comes before or
@@ -86,7 +87,7 @@ Now we can call the submit event, which transitions to the
86
87
 
87
88
  article.submit!
88
89
  article.awaiting_review? # => true
89
-
90
+
90
91
  Events are actually instance methods on a workflow, and depending on the
91
92
  state you're in, you'll have a different set of events used to
92
93
  transition to other states.
@@ -101,18 +102,20 @@ Installation
101
102
 
102
103
  gem install workflow
103
104
 
104
- Alternatively you can just download the lib/workflow.rb and put it in
105
- the lib folder of your Rails or Ruby application.
105
+ **Important**: If you're interested in graphing your workflow state machine, you will also need to
106
+ install the `active_support` and `ruby-graphviz` gems.
106
107
 
108
+ Versions up to and including 1.0.0 are also available as a single file download -
109
+ [lib/workflow.rb file](https://github.com/geekq/workflow/blob/v1.0.0/lib/workflow.rb).
107
110
 
108
111
  Ruby 1.9
109
112
  --------
110
113
 
111
- Workflow gem does not work with some (but very widespread) Ruby 1.9
112
- builds due to a known bug in Ruby 1.9. Either
114
+ Workflow gem does not work with some Ruby 1.9
115
+ builds due to a known bug in Ruby 1.9. Either
113
116
 
114
117
  * use newer ruby build, 1.9.2-p136 and -p180 tested to work
115
- * or compile your Ruby 1.9 from source
118
+ * or compile your Ruby 1.9 from source
116
119
  * or [comment out some lines in workflow](http://github.com/geekq/workflow/issues#issue/6)
117
120
  (reduces functionality).
118
121
 
@@ -148,6 +151,13 @@ be:
148
151
  (if integrated with ActiveRecord) and invoke this user defined reject
149
152
  method.
150
153
 
154
+ Note: on successful transition from one state to another the workflow
155
+ gem immediately persists the new workflow state with `update_column()`,
156
+ bypassing any ActiveRecord callbacks including `updated_at` update.
157
+ This way it is possible to deal with the validation and to save the
158
+ pending changes to a record at some later point instead of the moment
159
+ when transition occurs.
160
+
151
161
  You can also define event handler accepting/requiring additional
152
162
  arguments:
153
163
 
@@ -175,7 +185,7 @@ invoked for particular transitions leads to a bumpy and poorly readable code
175
185
  due to a deep nesting. We tried (and dismissed) lambdas for this. Eventually
176
186
  we decided to invoke an optional user defined callback method with the same
177
187
  name as the event (convention over configuration) as explained before.
178
-
188
+
179
189
 
180
190
  Integration with ActiveRecord
181
191
  -----------------------------
@@ -193,7 +203,7 @@ and include the workflow mixin in your model class as usual:
193
203
 
194
204
  On a database record loading all the state check methods e.g.
195
205
  `article.state`, `article.awaiting_review?` are immediately available.
196
- For new records or if the workflow_state field is not set the state
206
+ For new records or if the `workflow_state` field is not set the state
197
207
  defaults to the first state declared in the workflow specification. In
198
208
  our example it is `:new`, so `Article.new.new?` returns true and
199
209
  `Article.new.approved?` returns false.
@@ -204,6 +214,25 @@ new state is immediately saved in the database.
204
214
  You can change this behaviour by overriding `persist_workflow_state`
205
215
  method.
206
216
 
217
+ ### Scopes
218
+
219
+ Workflow library also adds automatically generated scopes with names based on
220
+ states names:
221
+
222
+ class Order < ActiveRecord::Base
223
+ include Workflow
224
+ workflow do
225
+ state :approved
226
+ state :pending
227
+ end
228
+ end
229
+
230
+ # returns all orders with `approved` state
231
+ Order.with_approved_state
232
+
233
+ # returns all orders with `pending` state
234
+ Order.with_pending_state
235
+
207
236
 
208
237
  ### Custom workflow database column
209
238
 
@@ -212,7 +241,7 @@ custom persistence column easily, e.g. for a legacy database schema:
212
241
 
213
242
  class LegacyOrder < ActiveRecord::Base
214
243
  include Workflow
215
-
244
+
216
245
  workflow_column :foo_bar # use this legacy database column for
217
246
  # persistence
218
247
  end
@@ -234,8 +263,8 @@ need to override `load_workflow_state` and
234
263
  `persist_workflow_state(new_value)` methods. Next section contains an example for
235
264
  using CouchDB, a document oriented database.
236
265
 
237
- [Tim Lossen](http://tim.lossen.de/) implemented support
238
- for [remodel](http://github.com/tlossen/remodel) / [redis](http://github.com/antirez/redis)
266
+ [Tim Lossen](http://tim.lossen.de/) implemented support
267
+ for [remodel](http://github.com/tlossen/remodel) / [redis](http://github.com/antirez/redis)
239
268
  key-value store.
240
269
 
241
270
  Integration with CouchDB
@@ -268,7 +297,7 @@ couchrest library.
268
297
  end
269
298
  end
270
299
 
271
- Please also have a look at
300
+ Please also have a look at
272
301
  [the full source code](http://github.com/geekq/workflow/blob/master/test/couchtiny_example.rb).
273
302
 
274
303
  Integration with Mongoid
@@ -310,7 +339,7 @@ state and every event:
310
339
 
311
340
  The workflow library itself uses this feature to tweak the graphical
312
341
  representation of the workflow. See below.
313
-
342
+
314
343
 
315
344
  Advanced transition hooks
316
345
  -------------------------
@@ -346,7 +375,7 @@ example][advanced_hooks_and_validation_test].
346
375
 
347
376
  ### on_error
348
377
 
349
- If you want to do custom exception handling internal to workflow, you can define an `on_error` hook in your workflow.
378
+ If you want to do custom exception handling internal to workflow, you can define an `on_error` hook in your workflow.
350
379
  For example:
351
380
 
352
381
  workflow do
@@ -356,12 +385,12 @@ For example:
356
385
  state :second
357
386
 
358
387
  on_error do |error, from, to, event, *args|
359
- Log.info "Exception(#error.class) on #{from} -> #{to}"
388
+ Log.info "Exception(#error.class) on #{from} -> #{to}"
360
389
  end
361
390
  end
362
391
 
363
- If forward! results in an exception, `on_error` is invoked and the workflow stays in a 'first' state. This capability
364
- is particularly useful if your errors are transient and you want to queue up a job to retry in the future without
392
+ If forward! results in an exception, `on_error` is invoked and the workflow stays in a 'first' state. This capability
393
+ is particularly useful if your errors are transient and you want to queue up a job to retry in the future without
365
394
  affecting the existing workflow state.
366
395
 
367
396
  ### Guards
@@ -400,7 +429,7 @@ Multiple Workflows
400
429
  ------------------
401
430
 
402
431
  I am frequently asked if it's possible to represent multiple "workflows"
403
- in an ActiveRecord class.
432
+ in an ActiveRecord class.
404
433
 
405
434
  The solution depends on your business logic and how you want to
406
435
  structure your implementation.
@@ -462,8 +491,17 @@ example][multiple_workflow_test]!
462
491
  Documenting with diagrams
463
492
  -------------------------
464
493
 
465
- You can generate a graphical representation of your workflow for
466
- documentation purposes. S. Workflow::create_workflow_diagram.
494
+ You can generate a graphical representation of the workflow for
495
+ a particular class for documentation purposes.
496
+ Use `Workflow::create_workflow_diagram(class)` in your rake task like:
497
+
498
+ namespace :doc do
499
+ desc "Generate a workflow graph for a model passed e.g. as 'MODEL=Order'."
500
+ task :workflow => :environment do
501
+ require 'workflow/draw'
502
+ Workflow::Draw::workflow_diagram(ENV['MODEL'].constantize)
503
+ end
504
+ end
467
505
 
468
506
 
469
507
  Earlier versions
@@ -472,7 +510,7 @@ Earlier versions
472
510
  The `workflow` library was originally written by Ryan Allen.
473
511
 
474
512
  The version 0.3 was almost completely (including ActiveRecord
475
- integration, API for accessing workflow specification,
513
+ integration, API for accessing workflow specification,
476
514
  method_missing free implementation) rewritten by Vladimir Dobriakov
477
515
  keeping the original workflow DSL spirit.
478
516
 
@@ -503,6 +541,14 @@ when using both a block and a callback method for an event, the block executes p
503
541
  Changelog
504
542
  ---------
505
543
 
544
+ ### New in the version 1.1.0
545
+
546
+ * Tested with ActiveRecord 4.0 (Rails 4.0)
547
+ * Tested with Ruby 2.0
548
+ * automatically generated scopes with names based on state names
549
+ * clean workflow definition override for class inheritance - undefining
550
+ the old convinience methods, s. <http://git.io/FZO02A>
551
+
506
552
  ### New in the version 1.0.0
507
553
 
508
554
  * **Support to private/protected callback methods.**
@@ -568,7 +614,7 @@ Intermixing of transition graph definition (states, transitions)
568
614
  on the one side and implementation of the actions on the other side
569
615
  for a bigger state machine can introduce clutter.
570
616
 
571
- To reduce this clutter it is now possible to use state entry- and
617
+ To reduce this clutter it is now possible to use state entry- and
572
618
  exit- hooks defined through a naming convention. For example, if there
573
619
  is a state :pending, then instead of using a
574
620
  block:
@@ -579,7 +625,7 @@ block:
579
625
  end
580
626
  end
581
627
 
582
- you can hook in by defining method
628
+ you can hook in by defining method
583
629
 
584
630
  def on_pending_exit(new_state, event, *args)
585
631
  # your implementation here
@@ -590,7 +636,7 @@ like `def on_pending_exit(*args)` if your are not interested in
590
636
  arguments. Please note: `def on_pending_exit()` with an empty list
591
637
  would not work.
592
638
 
593
- If both a function with a name according to naming convention and the
639
+ If both a function with a name according to naming convention and the
594
640
  on_entry/on_exit block are given, then only on_entry/on_exit block is used.
595
641
 
596
642
 
data/Rakefile CHANGED
@@ -1,17 +1,11 @@
1
1
  require 'rubygems'
2
- require "bundler/gem_tasks"
3
2
  require 'rake/testtask'
4
3
  require 'rdoc/task'
5
4
 
6
- task :default => [:test]
5
+ require 'bundler'
6
+ Bundler.setup
7
7
 
8
- begin
9
- Bundler.setup(:default, :development)
10
- rescue Bundler::BundlerError => e
11
- $stderr.puts e.message
12
- $stderr.puts "Run `bundle install` to install missing gems"
13
- exit e.status_code
14
- end
8
+ task :default => [:test]
15
9
 
16
10
  require 'rake'
17
11
  Rake::TestTask.new do |t|
@@ -7,4 +7,5 @@ group :development do
7
7
  gem "sqlite3"
8
8
  gem "mocha"
9
9
  gem "rake"
10
+ gem "ruby-graphviz", ">= 1.0"
10
11
  end
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rdoc", ">= 3.12"
5
+ gem "bundler", ">= 1.0.0"
6
+ gem "activerecord", "~>3.2"
7
+ gem "sqlite3"
8
+ gem "mocha"
9
+ gem "rake"
10
+ gem "ruby-graphviz", ">= 1.0"
11
+ end
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rdoc", ">= 3.12"
5
+ gem "bundler", ">= 1.0.0"
6
+ gem "activerecord"
7
+ gem "sqlite3"
8
+ gem "mocha"
9
+ gem "rake"
10
+ gem "ruby-graphviz", ">= 1.0"
11
+ gem 'protected_attributes'
12
+ end
@@ -1,130 +1,12 @@
1
1
  require 'rubygems'
2
2
 
3
+ require 'workflow/specification'
4
+ require 'workflow/adapters/active_record'
5
+ require 'workflow/adapters/remodel'
6
+
3
7
  # See also README.markdown for documentation
4
8
  module Workflow
5
-
6
- class Specification
7
-
8
- attr_accessor :states, :initial_state, :meta,
9
- :on_transition_proc, :before_transition_proc, :after_transition_proc, :on_error_proc
10
-
11
- def initialize(meta = {}, &specification)
12
- @states = Hash.new
13
- @meta = meta
14
- instance_eval(&specification)
15
- end
16
-
17
- def state_names
18
- states.keys
19
- end
20
-
21
- private
22
-
23
- def state(name, meta = {:meta => {}}, &events_and_etc)
24
- # meta[:meta] to keep the API consistent..., gah
25
- new_state = Workflow::State.new(name, self, meta[:meta])
26
- @initial_state = new_state if @states.empty?
27
- @states[name.to_sym] = new_state
28
- @scoped_state = new_state
29
- instance_eval(&events_and_etc) if events_and_etc
30
- end
31
-
32
- def event(name, args = {}, &action)
33
- target = args[:transitions_to] || args[:transition_to]
34
- raise WorkflowDefinitionError.new(
35
- "missing ':transitions_to' in workflow event definition for '#{name}'") \
36
- if target.nil?
37
- @scoped_state.events[name.to_sym] =
38
- Workflow::Event.new(name, target, (args[:meta] or {}), &action)
39
- end
40
-
41
- def on_entry(&proc)
42
- @scoped_state.on_entry = proc
43
- end
44
-
45
- def on_exit(&proc)
46
- @scoped_state.on_exit = proc
47
- end
48
-
49
- def after_transition(&proc)
50
- @after_transition_proc = proc
51
- end
52
-
53
- def before_transition(&proc)
54
- @before_transition_proc = proc
55
- end
56
-
57
- def on_transition(&proc)
58
- @on_transition_proc = proc
59
- end
60
-
61
- def on_error(&proc)
62
- @on_error_proc = proc
63
- end
64
- end
65
-
66
- class TransitionHalted < Exception
67
-
68
- attr_reader :halted_because
69
-
70
- def initialize(msg = nil)
71
- @halted_because = msg
72
- super msg
73
- end
74
-
75
- end
76
-
77
- class NoTransitionAllowed < Exception; end
78
-
79
- class WorkflowError < Exception; end
80
-
81
- class WorkflowDefinitionError < Exception; end
82
-
83
- class State
84
-
85
- attr_accessor :name, :events, :meta, :on_entry, :on_exit
86
- attr_reader :spec
87
-
88
- def initialize(name, spec, meta = {})
89
- @name, @spec, @events, @meta = name, spec, Hash.new, meta
90
- end
91
-
92
- unless RUBY_VERSION < '1.9'
93
- include Comparable
94
-
95
- def <=>(other_state)
96
- states = spec.states.keys
97
- raise ArgumentError, "state `#{other_state}' does not exist" unless other_state.in? states
98
- if states.index(self.to_sym) < states.index(other_state.to_sym)
99
- -1
100
- elsif states.index(self.to_sym) > states.index(other_state.to_sym)
101
- 1
102
- else
103
- 0
104
- end
105
- end
106
- end
107
-
108
- def to_s
109
- "#{name}"
110
- end
111
-
112
- def to_sym
113
- name.to_sym
114
- end
115
- end
116
-
117
- class Event
118
-
119
- attr_accessor :name, :transitions_to, :meta, :action
120
-
121
- def initialize(name, transitions_to, meta = {}, &action)
122
- @name, @transitions_to, @meta, @action = name, transitions_to.to_sym, meta, action
123
- end
124
-
125
- end
126
-
127
- module WorkflowClassMethods
9
+ module ClassMethods
128
10
  attr_reader :workflow_spec
129
11
 
130
12
  def workflow_column(column_name=nil)
@@ -138,7 +20,35 @@ module Workflow
138
20
  end
139
21
 
140
22
  def workflow(&specification)
141
- @workflow_spec = Specification.new(Hash.new, &specification)
23
+ assign_workflow Specification.new(Hash.new, &specification)
24
+ end
25
+
26
+ private
27
+
28
+ # Creates the convinience methods like `my_transition!`
29
+ def assign_workflow(specification_object)
30
+
31
+ # Merging two workflow specifications can **not** be done automically, so
32
+ # just make the latest specification win. Same for inheritance -
33
+ # definition in the subclass wins.
34
+ if respond_to? :inherited_workflow_spec # undefine methods defined by the old workflow_spec
35
+ inherited_workflow_spec.states.values.each do |state|
36
+ state_name = state.name
37
+ module_eval do
38
+ undef_method "#{state_name}?"
39
+ end
40
+
41
+ state.events.values.each do |event|
42
+ event_name = event.name
43
+ module_eval do
44
+ undef_method "#{event_name}!".to_sym
45
+ undef_method "can_#{event_name}?"
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ @workflow_spec = specification_object
142
52
  @workflow_spec.states.values.each do |state|
143
53
  state_name = state.name
144
54
  module_eval do
@@ -163,7 +73,7 @@ module Workflow
163
73
  end
164
74
  end
165
75
 
166
- module WorkflowInstanceMethods
76
+ module InstanceMethods
167
77
 
168
78
  def current_state
169
79
  loaded_state = load_workflow_state
@@ -335,118 +245,33 @@ module Workflow
335
245
  end
336
246
  end
337
247
 
338
- module ActiveRecordInstanceMethods
339
- def load_workflow_state
340
- read_attribute(self.class.workflow_column)
341
- end
342
-
343
- # On transition the new workflow state is immediately saved in the
344
- # database.
345
- def persist_workflow_state(new_value)
346
- if self.respond_to? :update_column
347
- # Rails 3.1 or newer
348
- update_column self.class.workflow_column, new_value
349
- else
350
- # older Rails; beware of side effect: other (pending) attribute changes will be persisted too
351
- update_attribute self.class.workflow_column, new_value
248
+ def self.included(klass)
249
+ klass.send :include, InstanceMethods
250
+
251
+ # backup the parent workflow spec, making accessible through #inherited_workflow_spec
252
+ if klass.superclass.respond_to?(:workflow_spec, true)
253
+ klass.module_eval do
254
+ # see http://stackoverflow.com/a/2495650/111995 for implementation explanation
255
+ pro = Proc.new { klass.superclass.workflow_spec }
256
+ singleton_class = class << self; self; end
257
+ singleton_class.send(:define_method, :inherited_workflow_spec) do
258
+ pro.call
259
+ end
352
260
  end
353
261
  end
354
262
 
355
- private
356
-
357
- # Motivation: even if NULL is stored in the workflow_state database column,
358
- # the current_state is correctly recognized in the Ruby code. The problem
359
- # arises when you want to SELECT records filtering by the value of initial
360
- # state. That's why it is important to save the string with the name of the
361
- # initial state in all the new records.
362
- def write_initial_state
363
- write_attribute self.class.workflow_column, current_state.to_s
364
- end
365
- end
366
-
367
- module RemodelInstanceMethods
368
- def load_workflow_state
369
- send(self.class.workflow_column)
370
- end
371
-
372
- def persist_workflow_state(new_value)
373
- update(self.class.workflow_column => new_value)
374
- end
375
- end
263
+ klass.extend ClassMethods
376
264
 
377
- def self.included(klass)
378
- klass.send :include, WorkflowInstanceMethods
379
- klass.extend WorkflowClassMethods
380
265
  if Object.const_defined?(:ActiveRecord)
381
266
  if klass < ActiveRecord::Base
382
- klass.send :include, ActiveRecordInstanceMethods
267
+ klass.send :include, Adapter::ActiveRecord::InstanceMethods
268
+ klass.send :extend, Adapter::ActiveRecord::Scopes
383
269
  klass.before_validation :write_initial_state
384
270
  end
385
271
  elsif Object.const_defined?(:Remodel)
386
- if klass < Remodel::Entity
387
- klass.send :include, RemodelInstanceMethods
388
- end
389
- end
390
- end
391
-
392
- # Generates a `dot` graph of the workflow.
393
- # Prerequisite: the `dot` binary. (Download from http://www.graphviz.org/)
394
- # You can use this method in your own Rakefile like this:
395
- #
396
- # namespace :doc do
397
- # desc "Generate a graph of the workflow."
398
- # task :workflow => :environment do # needs access to the Rails environment
399
- # Workflow::create_workflow_diagram(Order)
400
- # end
401
- # end
402
- #
403
- # You can influence the placement of nodes by specifying
404
- # additional meta information in your states and transition descriptions.
405
- # You can assign higher `doc_weight` value to the typical transitions
406
- # in your workflow. All other states and transitions will be arranged
407
- # around that main line. See also `weight` in the graphviz documentation.
408
- # Example:
409
- #
410
- # state :new do
411
- # event :approve, :transitions_to => :approved, :meta => {:doc_weight => 8}
412
- # end
413
- #
414
- #
415
- # @param klass A class with the Workflow mixin, for which you wish the graphical workflow representation
416
- # @param [String] target_dir Directory, where to save the dot and the pdf files
417
- # @param [String] graph_options You can change graph orientation, size etc. See graphviz documentation
418
- def self.create_workflow_diagram(klass, target_dir='.', graph_options='rankdir="LR", size="7,11.6", ratio="fill"')
419
- workflow_name = "#{klass.name.tableize}_workflow".gsub('/', '_')
420
- fname = File.join(target_dir, "generated_#{workflow_name}")
421
- File.open("#{fname}.dot", 'w') do |file|
422
- file.puts %Q|
423
- digraph #{workflow_name} {
424
- graph [#{graph_options}];
425
- node [shape=box];
426
- edge [len=1];
427
- |
428
-
429
- klass.workflow_spec.states.each do |state_name, state|
430
- file.puts %Q{ #{state.name} [label="#{state.name}"];}
431
- state.events.each do |event_name, event|
432
- meta_info = event.meta
433
- if meta_info[:doc_weight]
434
- weight_prop = ", weight=#{meta_info[:doc_weight]}"
435
- else
436
- weight_prop = ''
437
- end
438
- file.puts %Q{ #{state.name} -> #{event.transitions_to} [label="#{event_name.to_s.humanize}" #{weight_prop}];}
439
- end
272
+ if klass < Adapter::Remodel::Entity
273
+ klass.send :include, Remodel::InstanceMethods
440
274
  end
441
- file.puts "}"
442
- file.puts
443
275
  end
444
- `dot -Tpdf -o'#{fname}.pdf' '#{fname}.dot'`
445
- puts "
446
- Please run the following to open the generated file:
447
-
448
- open '#{fname}.pdf'
449
-
450
- "
451
276
  end
452
277
  end