kojac 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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