kojac 0.9.0

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.
Files changed (101) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +7 -0
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +158 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +69 -0
  7. data/Rakefile +27 -0
  8. data/app/assets/javascripts/can_extensions.js +45 -0
  9. data/app/assets/javascripts/kojac.js +1230 -0
  10. data/app/assets/javascripts/kojac_canjs.js +191 -0
  11. data/app/assets/javascripts/kojac_ember.js +463 -0
  12. data/app/controllers/kojac_controller.rb +70 -0
  13. data/app/serializers/default_kojac_serializer.rb +10 -0
  14. data/diagram.odg +0 -0
  15. data/kojac.gemspec +36 -0
  16. data/lib/kojac/app_serialize.rb +29 -0
  17. data/lib/kojac/kojac_rails.rb +432 -0
  18. data/lib/kojac/ring_strong_parameters.rb +195 -0
  19. data/lib/kojac/version.rb +3 -0
  20. data/lib/kojac.rb +8 -0
  21. data/lib/tasks/kojac_tasks.rake +4 -0
  22. data/notes.txt +48 -0
  23. data/spec/.DS_Store +0 -0
  24. data/spec/can_cache_spec.js +87 -0
  25. data/spec/can_factory_spec.js +144 -0
  26. data/spec/can_model_spec.js +127 -0
  27. data/spec/demo/README.rdoc +261 -0
  28. data/spec/demo/Rakefile +7 -0
  29. data/spec/demo/app/assets/javascripts/application.js +15 -0
  30. data/spec/demo/app/assets/stylesheets/application.css +13 -0
  31. data/spec/demo/app/controllers/application_controller.rb +3 -0
  32. data/spec/demo/app/helpers/application_helper.rb +2 -0
  33. data/spec/demo/app/mailers/.gitkeep +0 -0
  34. data/spec/demo/app/models/.gitkeep +0 -0
  35. data/spec/demo/app/views/layouts/application.html.erb +14 -0
  36. data/spec/demo/config/application.rb +65 -0
  37. data/spec/demo/config/boot.rb +10 -0
  38. data/spec/demo/config/database.yml +25 -0
  39. data/spec/demo/config/environment.rb +5 -0
  40. data/spec/demo/config/environments/development.rb +37 -0
  41. data/spec/demo/config/environments/production.rb +67 -0
  42. data/spec/demo/config/environments/test.rb +37 -0
  43. data/spec/demo/config/initializers/backtrace_silencers.rb +7 -0
  44. data/spec/demo/config/initializers/inflections.rb +15 -0
  45. data/spec/demo/config/initializers/mime_types.rb +5 -0
  46. data/spec/demo/config/initializers/secret_token.rb +7 -0
  47. data/spec/demo/config/initializers/session_store.rb +8 -0
  48. data/spec/demo/config/initializers/wrap_parameters.rb +14 -0
  49. data/spec/demo/config/locales/en.yml +5 -0
  50. data/spec/demo/config/routes.rb +58 -0
  51. data/spec/demo/config.ru +4 -0
  52. data/spec/demo/lib/assets/.gitkeep +0 -0
  53. data/spec/demo/log/.gitkeep +0 -0
  54. data/spec/demo/public/404.html +26 -0
  55. data/spec/demo/public/422.html +26 -0
  56. data/spec/demo/public/500.html +25 -0
  57. data/spec/demo/public/favicon.ico +0 -0
  58. data/spec/demo/script/rails +6 -0
  59. data/spec/ember_factory_spec.js +157 -0
  60. data/spec/ember_model_spec.js +179 -0
  61. data/spec/external/.DS_Store +0 -0
  62. data/spec/external/ember/.DS_Store +0 -0
  63. data/spec/external/ember/ember-1.0.0-rc.6.js +30970 -0
  64. data/spec/external/ember/handlebars-1.0.0-rc.4.js +2239 -0
  65. data/spec/external/jasmine/MIT.LICENSE +20 -0
  66. data/spec/external/jasmine/jasmine-html.js +616 -0
  67. data/spec/external/jasmine/jasmine.css +81 -0
  68. data/spec/external/jasmine/jasmine.js +2529 -0
  69. data/spec/external/jasmine.async.js +51 -0
  70. data/spec/external/jquery/jquery-1.7.2.js +9404 -0
  71. data/spec/external/jquery/jquery-1.7.2.min.js +4 -0
  72. data/spec/external/jquery/jquery-1.9.1.js +9597 -0
  73. data/spec/external/json2.js +480 -0
  74. data/spec/external/steal/steal-121115.js +2747 -0
  75. data/spec/external/steal/steal-3.2.3.js +2098 -0
  76. data/spec/external/steal/steal-3.2.3.min.js +27 -0
  77. data/spec/external/steal/steal.js +2466 -0
  78. data/spec/external/steal/steal.min.js +32 -0
  79. data/spec/external/steal/stealconfig.js +19 -0
  80. data/spec/external/underscore.js +1223 -0
  81. data/spec/external/underscore_plus.js +261 -0
  82. data/spec/external.zip +0 -0
  83. data/spec/handler_stack_spec.js +143 -0
  84. data/spec/helpers/SpecHelper.js +10 -0
  85. data/spec/kojac_caching_spec.js +105 -0
  86. data/spec/kojac_mock_spec.js +230 -0
  87. data/spec/kojac_model_spec.js +126 -0
  88. data/spec/kojac_object_spec.js +171 -0
  89. data/spec/kojac_operations_spec.js +41 -0
  90. data/spec/mockjson/order_item.js +37 -0
  91. data/spec/mockjson/order_item__49.js +15 -0
  92. data/spec/mockjson/order_item__50.js +15 -0
  93. data/spec/mockjson/order_item__51.js +15 -0
  94. data/spec/mockjson/product.js +82 -0
  95. data/spec/mockjson/product__3.js +22 -0
  96. data/spec/model_ring_spec.rb +52 -0
  97. data/spec/operation_include_spec.js +77 -0
  98. data/spec/run.html +81 -0
  99. data/spec/spec.js +2 -0
  100. data/spec/support/jasmine.yml +86 -0
  101. metadata +380 -0
@@ -0,0 +1,10 @@
1
+ class DefaultKojacSerializer < ActiveModel::Serializer
2
+
3
+ def initialize(object, options={})
4
+ super(object,options)
5
+ end
6
+
7
+ def attributes
8
+ object.attributes
9
+ end
10
+ end
data/diagram.odg ADDED
Binary file
data/kojac.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ # Maintain your gem's version:
4
+ require "kojac/version"
5
+
6
+ # Describe your gem and declare its dependencies:
7
+ Gem::Specification.new do |s|
8
+ s.name = "kojac"
9
+ s.version = Kojac::VERSION
10
+ s.authors = ["Gary McGhee"]
11
+ s.email = ["contact@buzzware.com.au"]
12
+ s.homepage = "https://github.com/buzzware/KOJAC"
13
+ s.license = "MIT"
14
+
15
+ s.summary = "KOJAC is an opinionated design and implementation for data management within Single Page Applications."
16
+ s.description = "KOJAC is an opinionated design and implementation for data management within Single Page Applications. It relates most heavily to the client and data protocol. The server may continue the key/value style down to a key/value-style database if desired, but that is not necessary. KOJAC also supports standard REST-style servers."
17
+
18
+ #s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
19
+ s.files = `git ls-files`.split($/)
20
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
22
+ s.require_paths = ["lib"]
23
+
24
+ s.add_dependency "underscore_plus"
25
+ s.add_dependency "json2-rails"
26
+ s.add_dependency "jquery-rails"
27
+ s.add_dependency 'strong_parameters'
28
+
29
+ s.add_development_dependency "rails", "~> 3.2"
30
+ s.add_development_dependency "rspec-rails"
31
+ s.add_development_dependency "canjs-rails"
32
+ s.add_development_dependency "ember-rails"
33
+ s.add_development_dependency "jquery-rails"
34
+ s.add_development_dependency "capybara"
35
+ s.add_development_dependency "sqlite3"
36
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_record/serializer_override'
2
+
3
+ def app_serialize(aObject,aScope)
4
+ result = case aObject.class
5
+ when Fixnum then aObject.to_s
6
+ when Bignum then aObject.to_s
7
+ when Array
8
+ sz_class = ActiveModel::ArraySerializer
9
+ sz_class.new(aObject).to_json(:scope => aScope, :root => false)
10
+ when String then aObject
11
+ when FalseClass then 'false'
12
+ when TrueClass then 'true'
13
+ when Symbol then aObject.to_s
14
+ #when Hash
15
+ #sz_class = ActiveModel::Serializer
16
+ #sz_class.new(aObject).to_json(:scope => aScope, :root => false)
17
+
18
+ else
19
+ sz_class = aObject.respond_to?(:active_model_serializer) && aObject.send(:active_model_serializer)
20
+ sz_class = DefaultKojacSerializer if !sz_class && aObject.is_a?(ActiveModel)
21
+ if sz_class
22
+ sz_class.new(aObject).to_json(:scope => aScope, :root => false)
23
+ else
24
+ aObject.to_json
25
+ end
26
+
27
+ end
28
+ result
29
+ end
@@ -0,0 +1,432 @@
1
+ #require File.expand_path('ring_strong_parameters',File.dirname(__FILE__))
2
+
3
+ Kernel.class_eval do
4
+ def key_join(aResource,aId=nil,aAssoc=nil)
5
+ result = aResource
6
+ if aId
7
+ result += "__#{aId}"
8
+ result += ".#{aAssoc}" if aAssoc
9
+ end
10
+ result
11
+ end
12
+ end
13
+
14
+ class String
15
+ def is_i?
16
+ !!(self =~ /^[-+]?[0-9]+$/)
17
+ end
18
+
19
+ def split_kojac_key
20
+ r,ia = self.split('__')
21
+ id,a = ia.split('.') if ia
22
+ id = id.to_i if id && id.is_i?
23
+ [r,id,a]
24
+ end
25
+
26
+ # eg deals__5 => deals
27
+ def resource
28
+ self.split('__')[0]
29
+ end
30
+
31
+ # eg. deals__5.options => deals__5
32
+ def base_key
33
+ self.split('.')[0]
34
+ end
35
+
36
+ def key_assoc
37
+ self.split('.')[1]
38
+ end
39
+ end
40
+
41
+ module KojacUtils
42
+ module_function
43
+
44
+ def model_class_for_key(aKey)
45
+ resource = aKey.split_kojac_key[0]
46
+ resource.singularize.camelize.constantize rescue nil
47
+ end
48
+
49
+ def model_for_key(aKey)
50
+ klass = KojacUtils.model_class_for_key(aKey)
51
+ resource,id,assoc = aKey.split_kojac_key
52
+ klass.find(id) rescue nil
53
+ end
54
+
55
+ def upgrade_hashes_to_params(aValue)
56
+ if aValue.is_a? Hash
57
+ aValue = ActionController::Parameters.new(aValue) unless aValue.is_a?(ActionController::Parameters)
58
+ elsif aValue.is_a? Array
59
+ aValue = aValue.map {|v| upgrade_hashes_to_params(v)}
60
+ end
61
+ aValue
62
+ end
63
+
64
+ end
65
+
66
+ module Kojac::ModelMethods
67
+
68
+ def self.included(aClass)
69
+ aClass.send :extend, ClassMethods
70
+ end
71
+
72
+ module ClassMethods
73
+ def by_key(aKey,aContext=nil)
74
+ r,id,a = aKey.split_kojac_key
75
+ model = self
76
+ model = self.rescope(model,aContext) if self.respond_to? :rescope
77
+ if id
78
+ model.where(id: id).first
79
+ else
80
+ model.all
81
+ end
82
+ end
83
+ end
84
+
85
+ def kojac_key
86
+ self.class.to_s.snake_case.pluralize+'__'+self.id.to_s
87
+ end
88
+
89
+ def update_permitted_attributes!(aChanges, aRing)
90
+ aChanges = KojacUtils.upgrade_hashes_to_params(aChanges)
91
+ permitted_fields = self.class.permitted_fields(:write, aRing)
92
+ permitted_fields = aChanges.permit(*permitted_fields)
93
+ assign_attributes(permitted_fields, :without_protection => true)
94
+ save!
95
+ end
96
+
97
+ end
98
+
99
+ module Kojac::ControllerOpMethods
100
+
101
+ def self.included(aClass)
102
+ #aClass.send :extend, ClassMethods
103
+ aClass.send :include, ActiveSupport::Callbacks
104
+ aClass.send :define_callbacks, :update_op, :scope => [:kind, :name]
105
+ end
106
+
107
+ #module ClassMethods
108
+ #end
109
+
110
+ module_function
111
+
112
+ public
113
+
114
+ attr_accessor :item
115
+
116
+ def results
117
+ @results ||= {}
118
+ end
119
+
120
+ def deduce_model_class
121
+ KojacUtils.model_class_for_key(self.kojac_resource)
122
+ end
123
+
124
+ def kojac_resource
125
+ self.class.to_s.chomp('Controller').snake_case
126
+ end
127
+
128
+ def create_on_association(aItem,aAssoc,aValues,aRing)
129
+ raise "User does not have permission for create on #{aAssoc}" unless aItem.class.permitted_associations(:create,aRing).include?(aAssoc.to_sym)
130
+
131
+ return nil unless ma = aItem.class.reflect_on_association(aAssoc.to_sym)
132
+ a_model_class = ma.klass
133
+
134
+ aValues = KojacUtils.upgrade_hashes_to_params(aValues || {})
135
+
136
+ case ma.macro
137
+ when :belongs_to
138
+ return nil if !aValues.is_a?(Hash)
139
+ fields = aValues.permit( *a_model_class.permitted_fields(:write,aRing) )
140
+ return aItem.send("build_#{aAssoc}".to_sym,fields)
141
+ when :has_many
142
+ aValues = [aValues] if aValues.is_a?(Hash)
143
+ return nil unless aValues.is_a? Array
144
+ aValues.each do |v|
145
+ fields = v.permit( *a_model_class.permitted_fields(:write,aRing) )
146
+ new_sub_item = nil
147
+ case ma.macro
148
+ when :has_many
149
+ new_sub_item = aItem.send(aAssoc.to_sym).create(fields)
150
+ else
151
+ raise "#{ma.macro} association unsupported in CREATE"
152
+ end
153
+ merge_model_into_results(new_sub_item)
154
+ end
155
+ end
156
+ #
157
+ #
158
+ #
159
+ #a_value = op[:value][a] # get data for this association, assume {}
160
+ #if a_value.is_a?(Hash)
161
+ # a_model_class = ma.klass
162
+ # fields = a_value.permit( *permitted_fields(:write,a_model_class) )
163
+ # item.send("build_#{a}".to_sym,fields)
164
+ # included_assocs << a.to_sym
165
+ #elsif a_value.is_a?(Array)
166
+ # raise "association collections not yet implemented for create"
167
+ #else
168
+ # next
169
+ #end
170
+ end
171
+
172
+ def create_op
173
+ ring = current_user.try(:ring)
174
+ op = params[:op]
175
+ options = op[:options] || {}
176
+ model_class = deduce_model_class
177
+ resource,id,assoc = op['key'].split_kojac_key
178
+ if assoc # create operation on an association eg. {verb: "CREATE", key: "order.items"}
179
+ raise "User does not have permission for #{op[:verb]} operation on #{model_class.to_s}.#{assoc}" unless model_class.permitted_associations(:create,ring).include?(assoc.to_sym)
180
+ item = KojacUtils.model_for_key(key_join(resource,id))
181
+ ma = model_class.reflect_on_association(assoc.to_sym)
182
+ a_value = op[:value] # get data for this association, assume {}
183
+ raise "create multiple not yet implemented for associations" unless a_value.is_a?(Hash)
184
+
185
+ a_model_class = ma.klass
186
+ p_fields = a_model_class.permitted_fields(:write,ring)
187
+ fields = a_value.permit( *p_fields )
188
+ new_sub_item = nil
189
+ case ma.macro
190
+ when :has_many
191
+ new_sub_item = item.send(assoc.to_sym).create(fields)
192
+ else
193
+ raise "#{ma.macro} association unsupported in CREATE"
194
+ end
195
+ result_key = op[:result_key] || new_sub_item.kojac_key
196
+ merge_model_into_results(new_sub_item)
197
+ else # create operation on a resource eg. {verb: "CREATE", key: "order_items"} but may have embedded association values
198
+ p_fields = model_class.permitted_fields(:write,ring)
199
+ raise "User does not have permission for #{op[:verb]} operation on #{model_class.to_s}" unless model_class.ring_can?(:create,ring)
200
+
201
+ p_fields = op[:value].permit( *p_fields )
202
+ item = model_class.create!(p_fields)
203
+
204
+ options_include = options['include'] || []
205
+ included_assocs = []
206
+ p_assocs = model_class.permitted_associations(:write,ring)
207
+ if p_assocs
208
+ p_assocs.each do |a|
209
+ next unless (a_value = op[:value][a]) || options_include.include?(a.to_s)
210
+ create_on_association(item,a,a_value,ring)
211
+ included_assocs << a.to_sym
212
+ end
213
+ end
214
+ item.save!
215
+ result_key = op[:result_key] || item.kojac_key
216
+ merge_model_into_results(item,result_key,:include => included_assocs)
217
+ end
218
+ {
219
+ key: op[:key],
220
+ verb: op[:verb],
221
+ result_key: result_key,
222
+ results: results
223
+ }
224
+ end
225
+
226
+ protected
227
+
228
+ def merge_model_into_results(aItem,aResultKey=nil,aOptions=nil)
229
+ ring = current_user.try(:ring)
230
+ aResultKey ||= aItem.kojac_key
231
+ aOptions ||= {}
232
+ results[aResultKey] = aItem.sanitized_hash(ring)
233
+ if included_assocs = aOptions[:include]
234
+ included_assocs = included_assocs.split(',') if included_assocs.is_a?(String)
235
+ included_assocs = [included_assocs] unless included_assocs.is_a?(Array)
236
+ included_assocs.map!(&:to_sym) if included_assocs.is_a?(Array)
237
+ p_assocs = aItem.class.permitted_associations(:read,ring)
238
+ use_assocs = p_assocs.delete_if do |a|
239
+ if included_assocs.include?(a) and ma = aItem.class.reflect_on_association(a)
240
+ ![:belongs_to,:has_many].include?(ma.macro) # is supported association type
241
+ else
242
+ true # no such assoc
243
+ end
244
+ end
245
+ use_assocs.each do |a|
246
+ next unless a_contents = aItem.send(a)
247
+ if a_contents.is_a? Array
248
+ contents_h = []
249
+ a_contents.each do |sub_item|
250
+ results[sub_item.kojac_key] = sub_item.sanitized_hash(ring)
251
+ #contents_h << sub_item.id
252
+ end
253
+ #results[aResultKey] = contents_h
254
+ else
255
+ results[a_contents.kojac_key] = a_contents.sanitized_hash(ring)
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ public
262
+
263
+ def read_op
264
+ op = params[:op]
265
+ key = op[:key]
266
+ result_key = nil
267
+ resource,id = key.split '__'
268
+ model = deduce_model_class
269
+
270
+ if id # item
271
+ if model
272
+ item = model.by_key(key,op)
273
+ result_key = op[:result_key] || (item && item.kojac_key) || op[:key]
274
+ merge_model_into_results(item,result_key,op[:options])
275
+ else
276
+ result_key = op[:result_key] || op[:key]
277
+ results[result_key] = null
278
+ end
279
+ else # collection
280
+ result_key = op[:result_key] || op[:key]
281
+ results[result_key] = []
282
+ if model
283
+ items = model.by_key(key,op)
284
+ items.each do |m|
285
+ item_key = m.kojac_key
286
+ results[result_key] << item_key.bite(resource+'__')
287
+ merge_model_into_results(m,item_key,op[:options])
288
+ end
289
+ end
290
+ end
291
+ {
292
+ key: op[:key],
293
+ verb: op[:verb],
294
+ results: results,
295
+ result_key: result_key
296
+ }
297
+ end
298
+
299
+ def update_op
300
+ result = nil
301
+ model = deduce_model_class
302
+
303
+ ring = current_user.try(:ring)
304
+ op = params[:op]
305
+ result_key = nil
306
+ if self.item = model.by_key(op[:key],op)
307
+
308
+ run_callbacks :update_op do
309
+ item.update_permitted_attributes!(op[:value], ring)
310
+
311
+ associations = model.permitted_associations(:write,ring)
312
+ associations.each do |k|
313
+ next unless assoc = model.reflect_on_association(k)
314
+ next unless op[:value][k]
315
+ case assoc.macro
316
+ when :belongs_to
317
+ if leaf = (item.send(k) || item.send("build_#{k}".to_sym))
318
+ #permitted_fields = leaf.class.permitted_fields(:write,ring)
319
+ #permitted_fields = op[:value][k].permit( *permitted_fields )
320
+ #leaf.assign_attributes(permitted_fields, :without_protection => true)
321
+ #leaf.save!
322
+ leaf.update_permitted_attributes!(op[:value][k], ring)
323
+ end
324
+ end
325
+ end
326
+
327
+ result_key = item.kojac_key
328
+ results[result_key] = item
329
+
330
+ associations.each do |a|
331
+ next unless assoc_item = item.send(a)
332
+ next unless key = assoc_item.respond_to?(:kojac_key) && assoc_item.kojac_key
333
+ results[key] = assoc_item
334
+ end
335
+ end
336
+ end
337
+ {
338
+ key: op[:key],
339
+ verb: op[:verb],
340
+ result_key: result_key,
341
+ results: results
342
+ }
343
+ end
344
+
345
+ def destroy_op
346
+ ring = current_user.try(:ring)
347
+ op = params[:op]
348
+ result_key = op[:result_key] || op[:key]
349
+ item = KojacUtils.model_for_key(op[:key])
350
+ item.destroy if item
351
+ results[result_key] = nil
352
+ {
353
+ key: op[:key],
354
+ verb: op[:verb],
355
+ result_key: result_key,
356
+ results: results
357
+ }
358
+ end
359
+
360
+ #def execute_op
361
+ # puts 'execute_op'
362
+ #end
363
+
364
+ def add_op
365
+ ring = current_user.try(:ring)
366
+ op = params[:op]
367
+ model = deduce_model_class
368
+ raise "ADD only supports associated collections at present eg order.items" unless op[:key].index('.')
369
+
370
+ item = KojacUtils.model_for_key(op[:key].base_key)
371
+ assoc = (assoc=op[:key].key_assoc) && assoc.to_sym
372
+ id = op[:value]['id']
373
+
374
+ ma = item.class.reflect_on_association(assoc)
375
+ case ma.macro
376
+ when :has_many
377
+ assoc_class = ma.klass
378
+ assoc_item = assoc_class.find(id)
379
+ item.send(assoc) << assoc_item
380
+
381
+ #ids_method = assoc.to_s.singularize+'_ids'
382
+ #ids = item.send(ids_method.to_sym)
383
+ #item.send((ids_method+'=').to_sym,ids + [id])
384
+ result_key = assoc_item.kojac_key
385
+ merge_model_into_results(assoc_item)
386
+ else
387
+ raise "ADD does not yet support #{ma.macro} associations"
388
+ end
389
+ {
390
+ key: op[:key],
391
+ verb: op[:verb],
392
+ result_key: result_key,
393
+ results: results
394
+ }
395
+ end
396
+
397
+ def remove_op
398
+ ring = current_user.try(:ring)
399
+ op = params[:op]
400
+ model = deduce_model_class
401
+ raise "REMOVE only supports associated collections at present eg order.items" unless op[:key].key_assoc
402
+
403
+ item = KojacUtils.model_for_key(op[:key].base_key)
404
+ assoc = (assoc=op[:key].key_assoc) && assoc.to_sym
405
+ id = op[:value]['id']
406
+
407
+ ma = item.class.reflect_on_association(assoc)
408
+ case ma.macro
409
+ when :has_many
410
+ assoc_class = ma.klass
411
+ if assoc_item = item.send(assoc).find(id)
412
+ item.send(assoc).delete(assoc_item)
413
+ result_key = assoc_item.kojac_key
414
+ if (assoc_item.destroyed?)
415
+ results[result_key] = nil
416
+ else
417
+ merge_model_into_results(assoc_item,result_key)
418
+ end
419
+ end
420
+ else
421
+ raise "REMOVE does not yet support #{ma.macro} associations"
422
+ end
423
+ {
424
+ key: op[:key],
425
+ verb: op[:verb],
426
+ result_key: result_key,
427
+ results: results
428
+ }
429
+ end
430
+
431
+
432
+ end
@@ -0,0 +1,195 @@
1
+ #ring_strong_parameters
2
+ #
3
+ #Assists implementation of ring level security (http://en.wikipedia.org/wiki/Ring_(computer_security)) with Rails 4 (or Rails 3 with gem) Strong Parameters.
4
+ #
5
+ #Ring Level Security is a simpler alternative to Role Based Security. Rings are arranged in a concentric hierarchy from most-privileged innermost Ring 0 to the least privileged highest ring number. Users have their own ring level which gives them access to that ring and below.
6
+ #
7
+ #For example, a sysadmin could have Ring 0, a website manager ring 1, a customer ring 2, and anonymous users ring 3. A customer would have all the capabilities of anonymous users, and more. Likewise, a website manager has all the capabilities of a customer, and more etc.
8
+ #
9
+ #This inheritance of capabilities of outer rings, and the simple assigning of users to rings, makes security rules less repetitive and easier to write and maintain, minimising dangerous mistakes.
10
+ #
11
+ #This gem does not affect or replace or prevent the standard strong parameters methods from being used in parallel, it merely generates arguments for the standard strong parameters methods.
12
+ #
13
+ #
14
+ #
15
+ #BASIC_FIELDS = [:name, :address]
16
+ #
17
+ #class Deal
18
+ # ring 1, :write, BASIC_FIELDS
19
+ # ring 1, :write, :phone
20
+ # ring 1, :delete
21
+ # ring 2, :read, BASIC_FIELDS
22
+ #end
23
+ #
24
+ #
25
+ #class DealsController
26
+ #
27
+ # def update
28
+ # ring_fields(:write,model)
29
+ # if ring_can(:write,model,:name)
30
+ # if ring_can(:delete,model)
31
+ # model.update(params.permit( ring_fields(:write,model) ))
32
+ # end
33
+ #
34
+ #end
35
+
36
+
37
+
38
+ class RingStrongParameters
39
+
40
+ cattr_accessor :config
41
+
42
+ def self.lookup_ring(aRingName)
43
+ return nil if !aRingName
44
+ return aRingName if aRingName.is_a?(Fixnum)
45
+ if ring_names = RingStrongParameters.config[:ring_names]
46
+ return ring_names[aRingName.to_sym]
47
+ else
48
+ return nil
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+
55
+ # see http://yehudakatz.com/2009/11/12/better-ruby-idioms/ re class and instance methods and modules
56
+
57
+ module RingStrongParameters::Model
58
+
59
+ def self.included(aClass)
60
+ aClass.cattr_accessor :rings_fields
61
+ aClass.rings_fields = [] # [1] => {read: [:name,:address], delete: true}
62
+ aClass.cattr_accessor :rings_abilities
63
+ aClass.rings_abilities = [] # [1] => {read: [:name,:address], delete: true}
64
+ aClass.send :extend, ClassMethods
65
+ end
66
+
67
+ def sanitized_hash(aRing)
68
+ p_fields = self.class.permitted_fields(:read, aRing)
69
+ self.attributes.filter_include(p_fields)
70
+ end
71
+
72
+ module ClassMethods
73
+
74
+ # supports different formats :
75
+ # ring :sales, :write => [:name,:address] ie. sales can write the name and address fields
76
+ # ring :sales, :read ie. sales can read this model
77
+ # ring :sales, [:read, :create, :destroy] ie. sales can read, create and destroy this model
78
+ def ring(aRing,aAbilities)
79
+ aRing = RingStrongParameters.lookup_ring(aRing)
80
+ raise "aRing must be a number or a symbol defined in RingStrongParameters.config.ring_names" if !aRing.is_a?(Fixnum)
81
+
82
+ if aAbilities.is_a? Hash # eg. fields like :write => [:name,:address]
83
+ ring_rec = self.rings_fields[aRing] || {}
84
+ aAbilities.each do |abilities,fields|
85
+ abilities = [abilities] unless abilities.is_a?(Array)
86
+ fields = [fields] unless fields.is_a?(Array)
87
+ abilities.each do |a|
88
+ a = a.to_sym
89
+ ring_fields = ring_rec[a] || []
90
+ ring_fields = ring_fields + fields.map(&:to_sym)
91
+ ring_fields.uniq!
92
+ ring_fields.sort!
93
+ ring_rec[a] = ring_fields
94
+ end
95
+ end
96
+ self.rings_fields[aRing] = ring_rec
97
+ elsif aAbilities.is_a?(Array) || aAbilities.is_a?(Symbol) # eg. abilities like :sales, [:read, :create, :destroy]
98
+ aAbilities = [aAbilities] unless aAbilities.is_a?(Array)
99
+ ring_ab = self.rings_abilities[aRing] || []
100
+ aAbilities.each do |ability|
101
+ ring_ab << ability.to_sym
102
+ end
103
+ ring_ab.uniq!
104
+ ring_ab.sort!
105
+ self.rings_abilities[aRing] = ring_ab
106
+ end
107
+ end
108
+
109
+ def permitted(aAbility,aRing)
110
+ aRing = RingStrongParameters.lookup_ring(aRing)
111
+ return [] unless aRing and rings_fields = self.respond_to?(:rings_fields).to_nil && self.rings_fields
112
+
113
+ fields = []
114
+ aRing.upto(rings_fields.length-1) do |i|
115
+ next unless ring_rec = rings_fields[i]
116
+ if af = ring_rec[aAbility.to_sym]
117
+ fields += af if af.is_a?(Array)
118
+ end
119
+ end
120
+ fields.uniq!
121
+ fields.sort!
122
+ fields
123
+ end
124
+
125
+ def permitted_fields(aAbility, aRing)
126
+ result = self.permitted(aAbility, aRing)
127
+ result.delete_if { |f| self.reflections.has_key? f }
128
+ result
129
+ end
130
+
131
+ def permitted_associations(aAbility, aRing)
132
+ aRing = RingStrongParameters.lookup_ring(aRing)
133
+ return [] unless aRing and rings_fields = self.respond_to?(:rings_fields).to_nil && self.rings_fields
134
+
135
+ associations = self.reflections.keys
136
+
137
+ fields = []
138
+ aRing.upto(rings_fields.length-1) do |i|
139
+ next unless ring_rec = rings_fields[i]
140
+ if af = ring_rec[aAbility.to_sym]
141
+ fields += associations & af
142
+ end
143
+ end
144
+ fields.uniq!
145
+ fields.sort!
146
+ fields
147
+ end
148
+
149
+ def ring_can?(aAbility, aRing)
150
+ aRing = RingStrongParameters.lookup_ring(aRing)
151
+ return [] unless aRing and rings_abilities = self.respond_to?(:rings_abilities).to_nil && self.rings_abilities
152
+
153
+ fields = []
154
+ aRing.upto(rings_abilities.length-1) do |i|
155
+ next unless ring_ab = rings_abilities[i]
156
+ return true if ring_ab.include?(aAbility)
157
+ end
158
+ return false
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+
165
+ #module RingStrongParameters::Controller
166
+ #
167
+ # #def permitted(aAbility,aModel)
168
+ # # # lookup aModel.rings_fields and return fields that current_user can access with given ability
169
+ # # aModel = aModel.class if aModel.is_a? ActiveRecord::Base
170
+ # # ring = current_user.try(:ring)
171
+ # # aModel.permitted(aAbility,ring)
172
+ # #end
173
+ #
174
+ # #def permitted_fields(aAbility,aModel)
175
+ # # aModel = aModel.class if aModel.is_a? ActiveRecord::Base
176
+ # # ring = current_user.try(:ring)
177
+ # # aModel.permitted_fields(aAbility,ring)
178
+ # #end
179
+ #
180
+ # #def permitted_associations(aAbility,aModel)
181
+ # # aUser = current_user
182
+ # # aRing = aUser.try(:ring)
183
+ # # aModel = aModel.class if aModel.is_a? ActiveRecord::Base
184
+ # # aModel.permitted_associations(aAbility, aRing)
185
+ # #end
186
+ #
187
+ # #def ring_can?(aAbility,aModel)
188
+ # # aModel = aModel.class if aModel.is_a? ActiveRecord::Base
189
+ # # aAbility = aAbility.to_sym
190
+ # # ring = current_user.try(:ring)
191
+ # # aModel.ring_can?(aAbility, ring)
192
+ # #end
193
+ #
194
+ #end
195
+
@@ -0,0 +1,3 @@
1
+ module Kojac
2
+ VERSION = "0.9.0"
3
+ end