engine2 1.0.1 → 1.0.2

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.
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