engine2 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ee392cd291e6908be3d724216be1ad7f79ece1e1
4
- data.tar.gz: 5a198484207426f1596052de6542fe27730fabf1
3
+ metadata.gz: 5a2ad0107737d0e5096b9a72b31743f01b8107ba
4
+ data.tar.gz: 6557952f0c927307bb297d71afc2cbb8086d3318
5
5
  SHA512:
6
- metadata.gz: 0273154703273cbdb46f21a26049dad06ad12924e9e1f10c15fc278b1bbb79f7569e821e7fe1c4db9a93a26d519bc7c716115bf76528bee4c2bf5155cfacccbe
7
- data.tar.gz: e53351d9d0af5d5da010f94f28c2a5f73277cc1c92400c56d473d78d595ec3f272db23ef603724440891a2b39f2a2d1c7f7efce7bd048a4946be2bcb56428fa3
6
+ metadata.gz: 247de7a7bd6535a169b713cd08d1c9034d4e9de87301530986d9eae7b4ef98b4ceb9bff5610daf7ee66cc168c7f110dd585dd7db5691e07112f372c3345792d4
7
+ data.tar.gz: b41f9ea4e158dd174b4f71566f93d4fcc0f5bff6270f3885cc5f13e52b9e7fae0ba2f696273611453222bec33aed9f34556cd5a63c46f3a3f3dc8787e4ea7917
data/lib/engine2.rb CHANGED
@@ -30,5 +30,3 @@ require 'engine2/version'
30
30
  ].each do |f|
31
31
  load "engine2/#{f}"
32
32
  end
33
-
34
- Engine2.bootstrap
@@ -1,10 +1,11 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Engine2
4
+
4
5
  class Action < BasicObject
5
6
  ACCESS_FORBIDDEN ||= ->h{false}
6
7
  attr_reader :parent, :name, :number, :actions, :recheck_access
7
- attr_reader :meta_proc, :meta_proc_chained
8
+ attr_reader :meta_proc
8
9
 
9
10
  class << self
10
11
  attr_accessor :count
@@ -20,17 +21,7 @@ module Engine2
20
21
  end
21
22
 
22
23
  def * &blk
23
- if blk
24
- @meta_proc = if meta_proc = @meta_proc
25
- @meta_proc_chained = true
26
- ::Kernel::lambda do |obj|
27
- obj.instance_eval(&meta_proc)
28
- obj.instance_eval(&blk)
29
- end
30
- else
31
- blk
32
- end
33
- end
24
+ @meta_proc = @meta_proc ? @meta_proc.chain(&blk) : blk if blk
34
25
  @meta
35
26
  end
36
27
 
@@ -74,6 +65,12 @@ module Engine2
74
65
  end
75
66
  end
76
67
 
68
+ def define_action_invoke name, meta_class = DummyMeta, assets = {}, &blk
69
+ define_action name, meta_class, assets do
70
+ self.*.define_singleton_method(:invoke, &blk)
71
+ end
72
+ end
73
+
77
74
  def undefine_action name
78
75
  ::Kernel.raise E2Error.new("No action #{name} defined") unless @actions[name]
79
76
  @actions.delete(name)
@@ -163,7 +160,7 @@ module Engine2
163
160
  model_name = model.name.to_sym
164
161
  model.synchronize_type_info
165
162
  model_actions[model_name] = action.to_a_rec{|a| !a.*.assets[:assoc]}
166
- action.run_scheme(model_name) if SCHEMES.schemes[model_name]
163
+ action.run_scheme(model_name) if SCHEMES[model_name, false]
167
164
  false
168
165
  else
169
166
  true
@@ -216,6 +213,10 @@ module Engine2
216
213
  end
217
214
  ::Kernel::puts "VERIFY #{::Time.now - t}"
218
215
  end
216
+
217
+ def p *args
218
+ ::Kernel::p *args
219
+ end
219
220
  end
220
221
 
221
222
 
data/lib/engine2/core.rb CHANGED
@@ -27,6 +27,14 @@ class Proc
27
27
  loc = source_location
28
28
  "\"#<Proc:#{loc.first[/\w+.rb/]}:#{loc.last}>\""
29
29
  end
30
+
31
+ def chain &blk
32
+ proc = self
33
+ lambda do |obj|
34
+ obj.instance_eval(&proc)
35
+ obj.instance_eval(&blk)
36
+ end
37
+ end
30
38
  end
31
39
 
32
40
  class Hash
@@ -123,7 +131,7 @@ class Sequel::Database
123
131
  attr_accessor :models, :default_schema
124
132
 
125
133
  def cache_file
126
- "#{APP_LOCATION}/#{opts[:orig_opts][:name]}.dump"
134
+ "#{Engine2::app}/#{opts[:orig_opts][:name]}.dump"
127
135
  end
128
136
 
129
137
  def load_schema_cache_from_file
@@ -236,7 +244,7 @@ module E2Model
236
244
  else
237
245
  info[:validations].each_pair do |validation, args|
238
246
  validation_proc = Engine2::Validations[validation] || args[:lambda] # swap ?
239
- raise "Validation not found for field '#{name}' of type #{validation}" unless validation_proc
247
+ raise E2Error.new("Validation not found for field '#{name}' of type #{validation}") unless validation_proc
240
248
  if result = validation_proc.(self, name, info)
241
249
  errors.add(name, result)
242
250
  break
@@ -345,11 +353,7 @@ module E2Model
345
353
  fields << name
346
354
  else
347
355
  fields << :"#{table}__#{name}"
348
- assoc = model.many_to_one_associations[table]
349
- unless assoc
350
- # fail
351
- end
352
- joins[table] = assoc
356
+ joins[table] = model.many_to_one_associations[table]
353
357
  end
354
358
 
355
359
  if f_info[:dummy]
@@ -375,11 +379,9 @@ module E2Model
375
379
  @opts[:select].compact!
376
380
 
377
381
  joins.reduce(self) do |joined, (table, assoc)|
378
- assoc = model.many_to_one_associations[table] # || model.one_to_one_associations[table]
379
382
  m = Object.const_get(assoc[:class_name])
380
383
  keys = assoc[:qualified_key]
381
- keys = [keys] unless keys.is_a?(Array)
382
- joined.left_join(table, m.primary_keys.zip(keys))
384
+ joined.left_join(table, m.primary_keys.zip(keys.is_a?(Array) ? keys : [keys]))
383
385
  end
384
386
  end
385
387
 
@@ -433,65 +435,74 @@ module Engine2
433
435
  PATH ||= File.expand_path('../..', File.dirname(__FILE__))
434
436
 
435
437
  class << self
436
- attr_accessor :core_loading
437
- end
438
+ attr_reader :app, :reloading
439
+ attr_reader :core_loaded
438
440
 
439
- self.core_loading = true
441
+ def database name
442
+ Object.const_set(name, yield) unless Object.const_defined?(name)
443
+ end
440
444
 
441
- def self.database name
442
- Object.const_set(name, yield) unless Object.const_defined?(name)
443
- end
445
+ def connect *args
446
+ db = Sequel.connect *args
447
+ db
448
+ end
444
449
 
445
- def self.connect *args
446
- db = Sequel.connect *args
447
- db.models = {}
448
- db
449
- end
450
+ def boot &blk
451
+ @boot_blk = blk
452
+ end
450
453
 
451
- e2_db_file = (defined? JRUBY_VERSION) ? "jdbc:sqlite:#{APP_LOCATION}/engine2.db" : "sqlite://#{APP_LOCATION}/engine2.db"
452
- E2DB ||= connect e2_db_file, loggers: [Logger.new($stdout)], convert_types: false, name: :engine2
453
- DUMMYDB ||= Sequel::Database.new uri: 'dummy'
454
- def DUMMYDB.synchronize *args;end
454
+ def model_boot &blk
455
+ @model_boot_blk = blk
456
+ end
455
457
 
456
- def self.boot &blk
457
- @boot_blk = blk
458
- end
458
+ def bootstrap_e2db
459
+ e2_db_file = (defined? JRUBY_VERSION) ? "jdbc:sqlite:#{@app}/engine2.db" : "sqlite://#{@app}/engine2.db"
460
+ Engine2.const_set :E2DB, connect(e2_db_file, loggers: [Logger.new($stdout)], convert_types: false, name: :engine2)
461
+ Engine2.const_set :DUMMYDB, Sequel::Database.new(uri: 'dummy')
462
+ def DUMMYDB.synchronize *args;end
463
+ end
459
464
 
460
- def self.model_boot &blk
461
- @model_boot_blk = blk
462
- end
465
+ def reload
466
+ @core_loaded = true
467
+ t = Time.now
468
+ Action.count = 0
469
+ SCHEMES.user.clear
470
+
471
+ Sequel::DATABASES.each do |db|
472
+ db.models.each{|n, m| Object.send(:remove_const, n) if Object.const_defined?(n)} unless db == E2DB || db == DUMMYDB
473
+ end
463
474
 
464
- def self.bootstrap app = APP_LOCATION
465
- require 'engine2/pre_bootstrap'
466
- t = Time.now
467
- Action.count = 0
468
- SCHEMES.clear
475
+ load "#{app}/boot.rb"
469
476
 
470
- Sequel::DATABASES.each do |db|
471
- db.models.each{|n, m| Object.send(:remove_const, n) if Object.const_defined?(n)} unless db == E2DB || db == DUMMYDB
472
- end
477
+ Sequel::DATABASES.each &:load_schema_cache_from_file
478
+ @model_boot_blk.() if @model_boot_blk
479
+ load 'engine2/models/Files.rb'
480
+ load 'engine2/models/UserInfo.rb'
481
+ Dir["#{app}/models/*"].each{|m| load m}
482
+ puts "MODELS: #{Sequel::DATABASES.reduce(0){|s, d|s + d.models.size}}, Time: #{Time.now - t}"
483
+ Sequel::DATABASES.each &:dump_schema_cache_to_file
473
484
 
474
- load "#{app}/boot.rb"
485
+ Engine2.send(:remove_const, :ROOT) if defined? ROOT
486
+ Engine2.const_set(:ROOT, Action.new(nil, :api, DummyMeta, {}))
475
487
 
476
- Sequel::DATABASES.each &:load_schema_cache_from_file
477
- @model_boot_blk.() if @model_boot_blk
478
- load 'engine2/models/Files.rb'
479
- load 'engine2/models/UserInfo.rb'
480
- Dir["#{app}/models/*"].each{|m| load m}
481
- puts "MODELS, Time: #{Time.now - t}"
482
- Sequel::DATABASES.each &:dump_schema_cache_to_file
488
+ @boot_blk.(ROOT)
489
+ ROOT.setup_action_tree
490
+ puts "BOOTSTRAP #{app}, Time: #{Time.new - t}"
491
+ end
483
492
 
484
- SCHEMES.merge!
485
- Engine2.send(:remove_const, :ROOT) if defined? ROOT
486
- Engine2.const_set(:ROOT, Action.new(nil, :api, DummyMeta, {}))
493
+ def bootstrap app, opts = {}
494
+ @app = app
495
+ @reloading = opts[:reloading]
496
+ bootstrap_e2db
487
497
 
488
- @boot_blk.(ROOT)
489
- ROOT.setup_action_tree
490
- puts "BOOTSTRAP #{app}, Time: #{Time.new - t}"
491
- self.core_loading = false
492
- require 'engine2/post_bootstrap'
498
+ require 'engine2/pre_bootstrap'
499
+ reload
500
+ require 'engine2/post_bootstrap'
501
+ end
493
502
  end
494
503
 
504
+ @core_loaded = false
505
+
495
506
  class E2Error < RuntimeError
496
507
  def initialize msg
497
508
  super
@@ -534,9 +545,9 @@ module Engine2
534
545
  option name, properties, index, &blk
535
546
  end
536
547
 
537
- def option_index iname
548
+ def option_index iname, raise = true
538
549
  index = @entries.index{|e| (e.is_a?(MenuBuilder) ? e.name : e[:name]) == iname}
539
- raise E2Error.new("No menu option #{iname} found") unless index
550
+ raise E2Error.new("No menu option #{iname} found") if !index && raise
540
551
  index
541
552
  end
542
553
 
@@ -113,7 +113,10 @@ module Engine2
113
113
  get '/*' do |name|
114
114
  headers 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0'
115
115
  if name.empty?
116
- load 'engine2.rb' if settings.environment == :development
116
+ if settings.environment == :development
117
+ load('engine2.rb') if Engine2::reloading
118
+ Engine2::reload
119
+ end
117
120
  name = 'index'
118
121
  end
119
122
  slim name.to_sym
data/lib/engine2/meta.rb CHANGED
@@ -140,13 +140,13 @@ module Engine2
140
140
 
141
141
  class DummyMeta < Meta
142
142
  meta_type :dummy
143
-
144
- # def invoke handler
145
- # {}
146
- # end
147
143
  end
148
144
 
149
145
  module MetaAPISupport
146
+ def reload_routes!
147
+ @meta[:reload_routes] = true
148
+ end
149
+
150
150
  def info
151
151
  @meta[:info] ||= {}
152
152
  end
@@ -166,6 +166,10 @@ module Engine2
166
166
  end
167
167
  end
168
168
 
169
+ def loc! hash
170
+ hash.each{|k, v| info! k, loc: v}
171
+ end
172
+
169
173
  def decorate list
170
174
  list.each do |f|
171
175
  m = (info[f] ||= {})
@@ -420,13 +424,10 @@ module Engine2
420
424
  panel_panel_template 'menu_m' unless panel[:panel_template] == false
421
425
  # modal_action false if panel[:panel_template] == false
422
426
  panel_class '' unless panel[:class]
427
+ footer true unless panel[:footer] == false
423
428
  end
424
429
  end
425
430
 
426
- def glyphicon name
427
- "<span class='glyphicon glyphicon-#{name}'></span>"
428
- end
429
-
430
431
  def panel
431
432
  @meta[:panel] ||= {}
432
433
  end
@@ -450,6 +451,10 @@ module Engine2
450
451
  def panel_title tle
451
452
  panel[:title] = tle
452
453
  end
454
+
455
+ def footer ftr
456
+ panel[:footer] = ftr
457
+ end
453
458
  end
454
459
 
455
460
  class MenuMeta < Meta
@@ -543,16 +548,14 @@ module Engine2
543
548
  panel_template 'scaffold/list'
544
549
  panel_panel_template 'panels/menu_m' unless action.parent.*.assets[:model]
545
550
  search_template 'scaffold/search'
546
- panel_title "#{glyphicon('list')} #{LOCS[assets[:model].name.to_sym]}"
551
+ panel_title "#{:list.icon} #{LOCS[assets[:model].name.to_sym]}"
547
552
  menu(:panel_menu).option :cancel, icon: "remove"
548
553
  menu :menu do
549
554
  properties break: 2, group_class: "btn-group-xs"
550
555
  option :search_toggle, icon: "search", show: "action.meta.search_fields", class: "action.ui_state.search_active && 'active'", button_loc: false
551
-
552
556
  # divider
553
557
  option :refresh, icon: "refresh", button_loc: false
554
558
  option :default_order, icon: "signal", button_loc: false
555
- option :select_toggle, icon: "check", enabled: "action.meta.config.selectable", button_loc: false
556
559
  divider
557
560
  option :debug_info, icon: "list-alt" do
558
561
  option :show_meta, icon: "eye-open"
@@ -566,6 +569,13 @@ module Engine2
566
569
  @meta[:state] = [:query, :ui_state]
567
570
  end
568
571
 
572
+ def select_toggle_menu
573
+ m = menu :menu
574
+ unless m.option_index(:select_toggle, false)
575
+ m.option_after :default_order, :select_toggle, icon: "check", enabled: "action.meta.config.selectable", button_loc: false
576
+ end
577
+ end
578
+
569
579
  def post_run
570
580
  unless panel[:class]
571
581
  panel_class case @meta[:fields].size
@@ -666,6 +676,10 @@ module Engine2
666
676
  include MetaModelSupport
667
677
  attr_reader :validations
668
678
 
679
+ def self.included meta
680
+ meta.http_method :post
681
+ end
682
+
669
683
  def validate_fields *fields
670
684
  if fields.empty?
671
685
  @validate_fields
@@ -736,6 +750,10 @@ module Engine2
736
750
  module MetaViewSupport
737
751
  include MetaModelSupport, MetaAPISupport, MetaTabSupport, MetaPanelSupport, MetaMenuSupport
738
752
 
753
+ def self.included meta
754
+ meta.meta_type :view
755
+ end
756
+
739
757
  def pre_run
740
758
  super
741
759
  panel_template 'scaffold/view'
@@ -761,6 +779,35 @@ module Engine2
761
779
  end
762
780
  end
763
781
 
782
+ module MetaDeleteSupport
783
+ include MetaModelSupport
784
+
785
+ def self.included meta
786
+ meta.http_method :delete
787
+ meta.meta_type :delete
788
+ end
789
+
790
+ def pre_run
791
+ super
792
+ action.parent.parent.*.menu(:item_menu).option :confirm_delete, icon: "trash", show: "action.selected_size() == 0", button_loc: false
793
+ end
794
+ end
795
+
796
+ module MetaBulkDeleteSupport
797
+ include MetaModelSupport
798
+
799
+ def self.included meta
800
+ meta.http_method :delete
801
+ meta.meta_type :bulk_delete
802
+ end
803
+
804
+ def pre_run
805
+ super
806
+ action.parent.parent.*.select_toggle_menu
807
+ action.parent.parent.*.menu(:menu).option_after :default_order, :confirm_bulk_delete, icon: "trash", show: "action.selected_size() > 0"
808
+ end
809
+ end
810
+
764
811
  (FormRendererPostProcessors ||= {}).merge!(
765
812
  boolean: lambda{|meta, field, info|
766
813
  meta.info[field][:render].merge! true_value: info[:true_value], false_value: info[:false_value]
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Engine2
4
4
  class DeleteMetaBase < Meta
5
- include MetaModelSupport
6
5
 
7
6
  def invoke_delete_db handler, ids
8
7
  begin
@@ -40,13 +39,7 @@ module Engine2
40
39
  end
41
40
 
42
41
  class DeleteMeta < DeleteMetaBase
43
- http_method :delete
44
- meta_type :delete
45
-
46
- def pre_run
47
- super
48
- action.parent.parent.*.menu(:item_menu).option :confirm_delete, icon: "trash", show: "action.selected_size() == 0", button_loc: false
49
- end
42
+ include MetaDeleteSupport
50
43
 
51
44
  def invoke handler
52
45
  handler.permit id = handler.params[:id]
@@ -55,13 +48,7 @@ module Engine2
55
48
  end
56
49
 
57
50
  class BulkDeleteMeta < DeleteMetaBase
58
- http_method :delete
59
- meta_type :bulk_delete
60
-
61
- def pre_run
62
- super
63
- action.parent.parent.*.menu(:menu).option_after :default_order, :confirm_bulk_delete, icon: "trash", show: "action.selected_size() > 0"
64
- end
51
+ include MetaBulkDeleteSupport
65
52
 
66
53
  def invoke handler
67
54
  ids = handler.param_to_json(:ids)
@@ -12,7 +12,7 @@ module Engine2
12
12
  option :inspect_modal, icon: :wrench, button_loc: false # , show: "action.logged_on"
13
13
 
14
14
  option :logout_form, icon: :"log-out" # , show: "action.logged_on"
15
- option :login_form, icon: :"log-in" # , show: "!action.logged_on"
15
+ option :login_form, icon: :"log-in", disabled: 'action.action_pending' # , show: "!action.logged_on"
16
16
  end
17
17
 
18
18
  @meta_type = :infra
@@ -249,7 +249,6 @@ module Engine2
249
249
 
250
250
  class LoginMeta < Meta
251
251
  include MetaApproveSupport
252
- http_method :post
253
252
  meta_type :login
254
253
 
255
254
  def after_approve handler, record
@@ -113,6 +113,7 @@ module Engine2
113
113
 
114
114
  def pre_run
115
115
  super
116
+ action.parent.parent.*.select_toggle_menu
116
117
  action.parent.parent.*.menu(:menu).option_after :default_order, :confirm_bulk_unlink, icon: "minus", show: "action.selected_size() > 0", button_loc: false
117
118
  end
118
119
 
@@ -155,7 +155,7 @@ module Engine2
155
155
  meta_type :star_to_many_list
156
156
  def pre_run
157
157
  super
158
- panel_title "#{glyphicon('list')} #{LOCS[assets[:assoc][:name]]}"
158
+ panel_title "#{:list.icon} #{LOCS[assets[:assoc][:name]]}"
159
159
  end
160
160
 
161
161
  # def decode_panel_title handler
@@ -177,10 +177,11 @@ module Engine2
177
177
  handler.permit parent = handler.params[:parent_id]
178
178
  model = assets[:model]
179
179
  assoc = assets[:assoc]
180
+ parent_keys = split_keys(parent)
180
181
  case assoc[:type]
181
182
  when :one_to_many
182
183
  keys = assoc[:keys]
183
- condition = parent.empty? ? false : Hash[keys.map{|k| k.qualify(model.table_name)}.zip(split_keys(parent))]
184
+ condition = parent_keys.all?(&:empty?) ? false : Hash[keys.map{|k| k.qualify(model.table_name)}.zip(parent_keys)]
184
185
  if handler.params[:negate]
185
186
  query = query.exclude(condition)
186
187
  query = query.or(Hash[keys.zip([nil])]) if keys.all?{|k|model.db_schema[k][:allow_null] == true} # type_info[:required] ?
@@ -194,7 +195,7 @@ module Engine2
194
195
  l_keys = assoc[:left_keys].map{|k| k.qualify(j_table)}
195
196
  r_keys = assoc[:right_keys].map{|k| k.qualify(j_table)}
196
197
  r_keys_vals = Hash[r_keys.zip(q_pk)]
197
- l_keys_vals = parent.empty? ? false : Hash[l_keys.zip(split_keys(parent))]
198
+ l_keys_vals = parent_keys.all?(&:empty?) ? false : Hash[l_keys.zip(parent_keys)]
198
199
 
199
200
  if handler.params[:negate]
200
201
  query.exclude(model.db[j_table].select(nil).where(r_keys_vals, l_keys_vals).exists)
@@ -4,7 +4,6 @@ module Engine2
4
4
 
5
5
  class SaveMeta < Meta
6
6
  include MetaApproveSupport
7
- http_method :post
8
7
 
9
8
  def validate_and_approve handler, record, json
10
9
  record.skip_save_refresh = true
@@ -28,7 +27,7 @@ module Engine2
28
27
  end
29
28
 
30
29
  class InsertMeta < SaveMeta
31
- meta_type :save
30
+ meta_type :approve
32
31
  def allocate_record handler, json
33
32
  record = super(handler, json)
34
33
  record.instance_variable_set(:"@new", true)
@@ -40,7 +39,7 @@ module Engine2
40
39
  end
41
40
 
42
41
  class UpdateMeta < SaveMeta
43
- meta_type :save
42
+ meta_type :approve
44
43
  def allocate_record handler, json
45
44
  record = super(handler, json)
46
45
  model = assets[:model]
@@ -48,16 +47,4 @@ module Engine2
48
47
  record
49
48
  end
50
49
  end
51
-
52
- module TimeStampMeta
53
- def before_approve handler, record
54
- super
55
- puts "before approve"
56
- end
57
-
58
- def after_approve handler, record
59
- super
60
- puts "after approve"
61
- end
62
- end
63
50
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Engine2
4
4
  class ViewMeta < Meta
5
- meta_type :view
6
5
  include MetaViewSupport, MetaQuerySupport
7
6
 
8
7
  def record handler, record
data/lib/engine2/model.rb CHANGED
@@ -7,28 +7,25 @@ module Engine2
7
7
  attr_reader :before_save_processors, :after_save_processors, :before_destroy_processors, :after_destroy_processors
8
8
  attr_reader :validation_in_transaction
9
9
 
10
- class << self
11
- def extended cls
12
- # cls.dataset.row_proc = nil
13
- models = cls.db.models ||= {}
14
- raise E2Error.new("Model '#{cls.name}' already defined") if models[cls.name.to_sym]
15
- models[cls.name.to_sym] = cls
16
-
17
- cls.instance_eval do
18
- @many_to_one_associations = association_reflections.select{|n, a| a[:type] == :many_to_one}
19
- @one_to_many_associations = association_reflections.select{|n, a| a[:type] == :one_to_many}
20
- @many_to_many_associations = association_reflections.select{|n, a| a[:type] == :many_to_many}
21
- # @one_to_one_associations = association_reflections.select{|n, a| a[:type] == :one_to_one}
22
- @validation_in_transaction = nil
23
- @before_save_processors = nil
24
- @after_save_processors = nil
25
- @around_save_processors = nil
26
- @before_destroy_processors = nil
27
- @after_destroy_processors = nil
28
- @type_info_synchronized = nil
29
- end
30
- cls.setup_schema
10
+ def self.extended cls
11
+ models = cls.db.models
12
+ raise E2Error.new("Model '#{cls.name}' already defined") if models[cls.name.to_sym]
13
+ models[cls.name.to_sym] = cls
14
+
15
+ cls.instance_eval do
16
+ @many_to_one_associations = association_reflections.select{|n, a| a[:type] == :many_to_one}
17
+ @one_to_many_associations = association_reflections.select{|n, a| a[:type] == :one_to_many}
18
+ @many_to_many_associations = association_reflections.select{|n, a| a[:type] == :many_to_many}
19
+ # @one_to_one_associations = association_reflections.select{|n, a| a[:type] == :one_to_one}
20
+ @validation_in_transaction = nil
21
+ @before_save_processors = nil
22
+ @after_save_processors = nil
23
+ @around_save_processors = nil
24
+ @before_destroy_processors = nil
25
+ @after_destroy_processors = nil
26
+ @type_info_synchronized = nil
31
27
  end
28
+ cls.setup_schema
32
29
  end
33
30
 
34
31
  def install_processors processors
@@ -111,6 +108,7 @@ module Engine2
111
108
 
112
109
  def synchronize_type_info
113
110
  resolve_dependencies
111
+ verify_associations
114
112
  @before_save_processors = install_processors(BeforeSaveProcessors)
115
113
  @after_save_processors = install_processors(AfterSaveProcessors)
116
114
  @around_save_processors = {}
@@ -119,6 +117,18 @@ module Engine2
119
117
  @type_info_synchronized = true
120
118
  end
121
119
 
120
+ def verify_associations
121
+ one_to_many_associations.each do |name, assoc|
122
+ other = Object.const_get(assoc[:class_name])
123
+ other_type_info = other.type_info
124
+ if other_keys = assoc[:keys]
125
+ other_keys.each do |key|
126
+ raise E2Error.new("No key '#{key}' found in model '#{other}' being related from #{self}") unless other_type_info[key]
127
+ end
128
+ end
129
+ end
130
+ end
131
+
122
132
  def resolve_dependencies
123
133
  resolved = {}
124
134
  @type_info.each_pair do |name, info|
@@ -133,7 +143,7 @@ module Engine2
133
143
  deps = @type_info[name][:depends]
134
144
  deps.each do |e|
135
145
  if !resolved[e]
136
- raise "Circular dependency for field '#{name}' in model '#{self}'" if seen.include?(e)
146
+ raise E2Error.new("Circular dependency for field '#{name}' in model '#{self}'") if seen.include?(e)
137
147
  resolve_dependency(e, resolved, seen)
138
148
  end
139
149
  end if deps
@@ -6,31 +6,23 @@ module Engine2
6
6
  VIEW ||= {view: true}.freeze
7
7
  LINK ||= {star_to_many_link: true, view: true, star_to_many_unlink: true}.freeze # star_to_many_bulk_unlink: true
8
8
 
9
- attr_reader :schemes, :builtin
9
+ attr_reader :builtin, :user
10
10
  def initialize
11
11
  @builtin = {}
12
- @schemes = {}
12
+ @user = {}
13
13
  end
14
14
 
15
15
  def define_scheme name, &blk
16
- schemes = Engine2::core_loading ? @builtin : @schemes
17
- raise "Scheme '#{name}' already defined" if schemes[name]
16
+ schemes = Engine2::core_loaded ? @user : @builtin
17
+ raise E2Error.new("Scheme '#{name}' already defined") if schemes[name]
18
18
  schemes[name] = blk
19
19
  end
20
20
 
21
- def clear
22
- @schemes = {}
23
- end
24
-
25
- def [] name
26
- scheme = @schemes[name]
27
- raise E2Error.new("Scheme #{name} not found") unless scheme
21
+ def [] name, raise = true
22
+ scheme = @builtin[name] || @user[name]
23
+ raise E2Error.new("Scheme #{name} not found") if !scheme && raise
28
24
  scheme
29
25
  end
30
-
31
- def merge!
32
- @schemes.merge!(@builtin){|n| raise E2Error.new("Scheme collision: #{n}")}
33
- end
34
26
  end
35
27
 
36
28
  SCHEMES ||= Schemes.new
@@ -209,8 +209,8 @@ module Engine2
209
209
  info[:multiple] = multiple
210
210
  info[:table] = table
211
211
  info[:store] = store
212
- info[:store][:upload] ||= "#{APP_LOCATION}/store/upload"
213
- info[:store][:files] ||= "#{APP_LOCATION}/store/files"
212
+ info[:store][:upload] ||= "#{Engine2::app}/store/upload"
213
+ info[:store][:files] ||= "#{Engine2::app}/store/files"
214
214
  info[:transaction] = true
215
215
  end
216
216
  end
@@ -265,9 +265,8 @@ module Engine2
265
265
  end
266
266
 
267
267
  def foreign_blob_store_field assoc_name, name, name_field, mime_field
268
- assoc = @model.association_reflections[assoc_name]
269
- raise E2Error.new("Association '#{assoc_name}' not found for model '#{@model}'") unless assoc
270
- raise E2Error.new("Association '#{assoc_name}' in model '#{@mode}' is not of type many_to_one") unless assoc[:type] == :many_to_one
268
+ assoc = @model.many_to_one_associations[assoc_name]
269
+ raise E2Error.new("'many_to_one' association '#{assoc_name}' not found for model '#{@model}'") unless assoc
271
270
  define_field :"#{assoc[:key]}_blob", :foreign_blob_store do |info|
272
271
  info[:assoc_name] = assoc_name
273
272
  info[:bytes_field] = name
@@ -278,9 +277,8 @@ module Engine2
278
277
  end
279
278
 
280
279
  def many_to_one_field assoc_name
281
- assoc = @model.association_reflections[assoc_name]
282
- raise E2Error.new("Association '#{assoc_name}' not found for model '#{@model}'") unless assoc
283
- raise E2Error.new("Association '#{assoc_name}' in model '#{@mode}' is not of type many_to_one") unless assoc[:type] == :many_to_one
280
+ assoc = @model.many_to_one_associations[assoc_name]
281
+ raise E2Error.new("'many_to_one' association '#{assoc_name}' not found for model '#{@model}'") unless assoc
284
282
  keys = assoc[:keys]
285
283
  modify_field keys.first do |info|
286
284
  info[:type] = :many_to_one
@@ -290,9 +288,8 @@ module Engine2
290
288
  end
291
289
 
292
290
  def star_to_many_field assoc_name
293
- assoc = @model.association_reflections[assoc_name]
294
- raise E2Error.new("Association '#{assoc_name}' not found for model '#{@model}'") unless assoc
295
- raise E2Error.new("Association '#{assoc_name}' in model '#{@model}' is not of type *_to_many") unless [:one_to_many, :many_to_many].include?(assoc[:type])
291
+ assoc = @model.one_to_many_associations[assoc_name] || @model.many_to_many_associations[assoc_name]
292
+ raise E2Error.new("'*_to_many' association '#{assoc_name}' not found for model '#{@model}'") unless assoc
296
293
  define_field assoc_name, :string do |info|
297
294
  info[:type] = :star_to_many_field
298
295
  info[:keys] = assoc[:keys]
@@ -309,7 +306,7 @@ module Engine2
309
306
  # list.map{|k, v| {id: k, value: v}}
310
307
  list.to_a
311
308
  else
312
- raise E2Error.new("type not supported for list_select modifier for field #{name}")
309
+ raise E2Error.new("type '#{list.class}' not supported for list_select modifier for field #{name}")
313
310
  end
314
311
  info[:validations][:list_select] = true
315
312
  end
@@ -317,7 +314,7 @@ module Engine2
317
314
 
318
315
  def decode name, dinfo = {form: {scaffold: true}, search: {scaffold: true}}
319
316
  modify_field name do |info|
320
- raise E2Error.new("Field type of #{name} needs to be :many_to_one") unless info[:type] == :many_to_one
317
+ raise E2Error.new("Field type of '#{name}' in model '#{@model}' needs to be 'many_to_one'") unless info[:type] == :many_to_one
321
318
  dec = info[:decode] ||= {}
322
319
  dec[:search].clear if dinfo[:search] && dec[:search]
323
320
  dec[:form].clear if dinfo[:form] && dec[:form]
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Engine2
4
- MAJOR, MINOR, TINY = [1, 0, 1]
4
+ MAJOR, MINOR, TINY = [1, 0, 2]
5
5
  VERSION = [MAJOR, MINOR, TINY].join('.').freeze
6
6
  def self.version
7
7
  VERSION
data/views/engine2.coffee CHANGED
@@ -279,14 +279,14 @@ angular.module('Engine2', ['ngRoute', 'ngSanitize', 'ngAnimate', 'ngCookies', 'm
279
279
  error: (title, msg, html) ->
280
280
  body = if html then msg else "<div class='alert alert-danger'>#{msg}</div>"
281
281
  clazz = if html then "modal-huge" else "modal-large"
282
- @show meta: panel: (panel_template: "close_m", template_string: body, title: title, class: clazz) # message: msg,
282
+ @show meta: panel: (panel_template: "close_m", template_string: body, title: title, class: clazz, footer: true) # message: msg,
283
283
 
284
284
  confirm: (title, msg, action) ->
285
285
  body = "<div class='alert alert-warning'>#{msg}</div>"
286
286
  clazz = "modal-large"
287
287
  @show
288
288
  confirm: action,
289
- meta: panel: (panel_template: "confirm_m", template_string: body, title: title, class: clazz) # message: msg,
289
+ meta: panel: (panel_template: "confirm_m", template_string: body, title: title, class: clazz, footer: true) # message: msg,
290
290
 
291
291
  .directive 'e2Modal', ($e2Modal) ->
292
292
  restrict: 'E'
@@ -301,7 +301,7 @@ angular.module('Engine2', ['ngRoute', 'ngSanitize', 'ngAnimate', 'ngCookies', 'm
301
301
  return if ev.defaultPrevented
302
302
  ev.preventDefault()
303
303
 
304
- panel = panel_template: attrs.panelTemplate, title: attrs.title, class: attrs.clazz
304
+ panel = panel_template: attrs.panelTemplate, title: attrs.title, class: attrs.clazz, footer: true
305
305
  if obody then panel.template_string = obody.outerHTML else panel.template = attrs.template
306
306
  action = meta: (panel: panel), scope: -> scope
307
307
  _.assign(action, args)
@@ -386,12 +386,16 @@ angular.module('Engine2', ['ngRoute', 'ngSanitize', 'ngAnimate', 'ngCookies', 'm
386
386
  selection = scope.action.selection
387
387
  out = ''
388
388
  _.each scope.action.entries, (e, i) ->
389
- out += if selection then "<tr ng-class='action.selected_class(#{i})' class='tr_hover' ng-click='action.select(#{i}, $event)'>" else "<tr>"
389
+ out += if selection then "<tr ng-class='action.selected_class(#{i})' class='tr_hover' ng-click='action.select(#{i}, $event)'>" else
390
+ row_cls = e.$row_info?.class
391
+ if row_cls then "<tr class=\"#{row_cls}\">" else "<tr>"
390
392
  out += "<td>"
391
- out += "<div e2-button-set='action.meta.menus.item_menu' index='#{i}'></div>" if meta.config.show_item_menu # data='action.entries[#{i}]'></div>
393
+ out += "<div e2-button-set='action.meta.menus.item_menu' index='#{i}'></div>" if meta.config.show_item_menu
392
394
  out += "</td>"
393
- # out += "<td><div e2-button-set='item_menu' index='#{i}' ng-if='action.meta.config.show_item_menu'></div></td>"
394
- _.each meta.fields, (f) -> out += "<td>#{scope.action.list_cell(e, f) ? ''}</td>"
395
+ _.each meta.fields, (f) ->
396
+ out += if col_cls = meta.info[f].column_class then "<td class='#{col_cls}'>" else "<td>"
397
+ out += scope.action.list_cell(e, f) ? ''
398
+ out += "</td>"
395
399
  out += "</tr>"
396
400
 
397
401
  elem.empty()
@@ -128,9 +128,11 @@ angular.module('Engine2')
128
128
 
129
129
  pre_invoke: ->
130
130
  @parent().action_pending = true
131
+ @action_pending = true
131
132
  # @parent().parent().action_pending = true if @parent().parent()
132
133
  post_invoke: ->
133
134
  delete @parent().action_pending # = false
135
+ delete @action_pending
134
136
  # @parent().parent().action_pending = false if @parent().parent()
135
137
  invoke: ->
136
138
  args = arguments
@@ -282,7 +284,7 @@ angular.module('Engine2')
282
284
 
283
285
  traverse: (routes) ->
284
286
  menu_tmpl = _.template("<li><a href='{{href}}'>{{icon}}{{aicon}} {{loc}}</a></li>")
285
- menu_sub_tmpl = _.template("<li e2-drop-down='{{dropdown}}'><a href='javascript://'>{{icon}}{{aicon}}{{loc}}<span class='caret'></span></a></li>")
287
+ menu_sub_tmpl = _.template("<li e2-drop-down='{{dropdown}}'><a href='javascript://'>{{icon}}{{aicon}} {{loc}}<span class='caret'></span></a></li>")
286
288
  out = routes.map (route, i) ->
287
289
  if route.menu
288
290
  menu_sub_tmpl
@@ -565,6 +567,7 @@ angular.module('Engine2')
565
567
  super()
566
568
  @decode_field = @scope().f
567
569
  @dinfo = @parentp().meta.info[@decode_field]
570
+ throw "Primary and foreign key list lengths dont match: [#{@meta.primary_fields}] and [#{@dinfo.fields}]" unless @meta.primary_fields.length == @dinfo.fields.length
568
571
  @scope().$on "search_reset", => @clean()
569
572
 
570
573
  if_fk_values: (f) ->
@@ -1,5 +1,5 @@
1
1
  e2-action action="'inspect'"
2
- div bs-tabs="" ng-if="'true'"
2
+ div bs-tabs=""
3
3
  br
4
4
  div title="API" bs-pane=""
5
5
  .row
@@ -8,7 +8,7 @@
8
8
  .modal-body
9
9
  | modal-content-to-replace
10
10
 
11
- .modal-footer
11
+ .modal-footer ng-if="::action.meta.panel.footer"
12
12
  .loader.pull-left: img src="img/ajax-loader.gif"
13
13
  .btn.btn-default ng-click="$hide()"
14
14
  span.glyphicon.glyphicon-remove>
@@ -8,7 +8,7 @@
8
8
  .modal-body
9
9
  | modal-content-to-replace
10
10
 
11
- .modal-footer
11
+ .modal-footer ng-if="::action.meta.panel.footer"
12
12
  .loader.pull-left: img src="img/ajax-loader.gif"
13
13
  .btn-group
14
14
  .btn.btn-default ng-click="action.confirm()"
@@ -8,5 +8,5 @@
8
8
  .modal-body
9
9
  | modal-content-to-replace
10
10
 
11
- .modal-footer
11
+ .modal-footer ng-if="::action.meta.panel.footer"
12
12
  .loader.pull-left: img src="img/ajax-loader.gif"
@@ -8,6 +8,6 @@
8
8
  .modal-body
9
9
  | modal-content-to-replace
10
10
 
11
- .modal-footer
11
+ .modal-footer ng-if="::action.meta.panel.footer"
12
12
  .loader.pull-left: img src="img/ajax-loader.gif"
13
13
  div e2-button-set="action.meta.menus.panel_menu"
@@ -8,7 +8,7 @@
8
8
  .modal-body
9
9
  | modal-content-to-replace
10
10
 
11
- .modal-footer
11
+ .modal-footer ng-if="::action.meta.panel.footer"
12
12
  .loader.pull-left: img src="img/ajax-loader.gif"
13
13
  .btn-group
14
14
  .btn.btn-default ng-click="action.yes(); $hide()"
@@ -3,7 +3,7 @@
3
3
  h4.modal-title ng-bind-html="action.meta.panel.title"
4
4
  .panel-body
5
5
  | modal-content-to-replace
6
- .panel-footer
6
+ .panel-footer ng-if="::action.meta.panel.footer"
7
7
  // .loader.pull-left: img src="img/ajax-loader.gif"
8
8
  div e2-button-set="action.meta.menus.panel_menu"
9
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: engine2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - lopex
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-21 00:00:00.000000000 Z
11
+ date: 2016-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement