Class: ActiveRecord::Base

Inherits:
Object show all
Extended by:
ClassMethods
Includes:
InstanceMethods
Defined in:
lib/active_record_base.rb,
lib/active_record_base.rb,
lib/reactive_record/permissions.rb,
lib/reactive_record/active_record/base.rb,
lib/reactive_record/active_record/aggregations.rb,
lib/reactive_record/active_record/associations.rb,
lib/reactive_record/active_record/public_columns_hash.rb

Overview

adds method to get the HyperMesh public column types this works because the public folder is currently required to be eager loaded.

Constant Summary

Constants included from ClassMethods

ClassMethods::SERVER_METHODS

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes included from InstanceMethods

#backing_record

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ClassMethods

_all_filter, _attribute_aliases, _dealias_attribute, _new_without_sti_type_cast, _react_param_conversion, abstract_class=, abstract_class?, alias_attribute, all, base_class, column_names, columns_hash, composed_of, create, default_scope, define_attribute_methods, enum, find, find_by, finder_method, inheritance_column, inheritance_column=, method_missing, model_name, new, primary_key, primary_key=, scope, serialize, server_method, server_methods, unscoped

Methods included from InstanceMethods

#<=>, #==, #[], #[]=, #attributes, #becomes, #becomes!, #cast_to_current_sti_type, #changed?, #changed_attributes, #changes, #destroy, #destroyed?, #dup, #errors, #id, #id=, #id?, #initialize, #inspect, #itself, #load, #model_name, #new?, #primary_key, #revert, #save, #saving?, #to_key, #update, #update_attribute, #valid?, #validate

Class Attribute Details

.reactive_record_association_keysObject (readonly)

Returns the value of attribute reactive_record_association_keys.



74
75
76
# File 'lib/reactive_record/permissions.rb', line 74

def reactive_record_association_keys
  @reactive_record_association_keys
end

Instance Attribute Details

#acting_userObject

Returns the value of attribute acting_user.



26
27
28
# File 'lib/reactive_record/permissions.rb', line 26

def acting_user
  @acting_user
end

Class Method Details

.__secure_remote_access_to_all(_self, _acting_user) ⇒ Object

Here we set up the base ‘all` and `unscoped` methods. See below for more on how access protection works on relationships.



66
67
68
# File 'lib/active_record_base.rb', line 66

def __secure_remote_access_to_all(_self, _acting_user)
  all
end

.__secure_remote_access_to_find(_self, _acting_user, *args) ⇒ Object

add secure access for find, find_by, and belongs_to and has_one relations. No explicit security checks are needed here, as the data returned by these objects will be further processedand checked before returning. I.e. it is not possible to simply return ‘find(1)` but if you try returning `find(1).name` the permission system will check to see if the name attribute can be legally sent to the current acting user.



259
260
261
# File 'lib/active_record_base.rb', line 259

def __secure_remote_access_to_find(_self, _acting_user, *args)
  find(*args)
end

.__secure_remote_access_to_find_by(_self, _acting_user, *args) ⇒ Object



263
264
265
# File 'lib/active_record_base.rb', line 263

def __secure_remote_access_to_find_by(_self, _acting_user, *args)
  find_by(*args)
end

.__secure_remote_access_to_unscoped(_self, _acting_user, *args) ⇒ Object



70
71
72
# File 'lib/active_record_base.rb', line 70

def __secure_remote_access_to_unscoped(_self, _acting_user, *args)
  unscoped(*args)
end

.__set_synchromesh_permission_granted(old_rel, new_rel, obj, acting_user, args = [], &block) ⇒ Object

helper method to set the value of __synchromesh_permission_granted on the relationship Set acting_user on the object, then or in the result of running the block in context of the obj with the current value of __synchromesh_permission_granted



173
174
175
176
177
178
179
180
181
# File 'lib/active_record_base.rb', line 173

def __set_synchromesh_permission_granted(old_rel, new_rel, obj, acting_user, args = [], &block)
  saved_acting_user = obj.acting_user
  obj.acting_user = acting_user
  new_rel.__synchromesh_permission_granted =
    obj.instance_exec(*args, &block) || (old_rel && old_rel.try(:__synchromesh_permission_granted))
  new_rel
ensure
  obj.acting_user = saved_acting_user
end

.__synchromesh_parse_regulator_params(name, block) ⇒ Object

We allow three forms: regulate_xxx name &block : the block is the regulation regulate_xxx name: const : const can be denied!, deny, denied, or any other truthy or

falsy value

regulate_xxx name: proc : the proc is the regulation



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/active_record_base.rb', line 141

def __synchromesh_parse_regulator_params(name, block)
  if name.is_a? Hash
    name, block = name.first
    if %i[denied! deny denied].include? block
      block = ->(*_args) { denied! }
    elsif !block.is_a? Proc
      value = block
      block = ->(*_args) { value }
    end
  end
  [name, block || ->(*_args) { true }]
end

.__synchromesh_regulate_from_macro(opts, name, already_defined) ⇒ Object

helper method for providing a regulation in line with a scope or relationship this is done using the ‘regulate` key on the opts. if no regulate key is provided and there is no regulation already defined for this name, then we create one that returns nil (don’t care) once we have things figured out, we yield to the provided proc which is either regulate_scope or regulate_relationship



161
162
163
164
165
166
167
# File 'lib/active_record_base.rb', line 161

def __synchromesh_regulate_from_macro(opts, name, already_defined)
  if opts.key?(:regulate)
    yield name => opts[:regulate]
  elsif !already_defined
    yield name => ->(*_args) {}
  end
end

._synchromesh_scope_args_check(args) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/active_record_base.rb', line 8

def self._synchromesh_scope_args_check(args)
  opts = if args.count == 2 && args[1].is_a?(Hash)
           args[1].merge(server: args[0])
         elsif args[0].is_a? Hash
           args[0]
         else
           { server: args[0] }
         end
  return opts if opts && opts[:server].respond_to?(:call)
  raise 'must provide either a proc as the first arg or by the '\
        '`:server` option to scope and default_scope methods'
end

.belongs_to(attr_name, *args) ⇒ Object



90
91
92
93
94
95
96
# File 'lib/reactive_record/permissions.rb', line 90

def belongs_to(attr_name, *args)
  belongs_to_without_reactive_record_add_is_method(attr_name, *args).tap do
    define_method "#{attr_name}_is?".to_sym do |model|
      self.class.reflections[attr_name].foreign_key == model.id
    end
  end
end

.belongs_to_without_reactive_record_add_is_methodObject



88
# File 'lib/reactive_record/permissions.rb', line 88

alias belongs_to_without_reactive_record_add_is_method belongs_to

.default_scope(*args, &block) ⇒ Object



219
220
221
222
223
224
225
226
227
# File 'lib/active_record_base.rb', line 219

def default_scope(*args, &block)
  __synchromesh_regulate_from_macro(
    (opts = _synchromesh_scope_args_check([*block, *args])),
    :all,
    respond_to?(:__secure_remote_access_to_all),
    &method(:regulate_scope)
  )
  pre_synchromesh_default_scope(opts[:server], &block)
end

.denied!Object

The wrapper method may simply return the normal result or may act to secure the data. The simpliest case is for the method to call ‘denied!` which will raise a Hyperloop access protection fault.



59
60
61
# File 'lib/active_record_base.rb', line 59

def denied!
  Hyperloop::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self} regulation denies scope access.  Called from #{caller_locations(1)}")
end

.do_not_synchronizeObject

call do_not_synchronize to block synchronization of a model



284
285
286
# File 'lib/active_record_base.rb', line 284

def self.do_not_synchronize
  @do_not_synchronize = true
end

.do_not_synchronize?Boolean

used by the broadcast mechanism to determine if this model is to be synchronized

Returns:

  • (Boolean)


290
291
292
# File 'lib/active_record_base.rb', line 290

def self.do_not_synchronize?
  @do_not_synchronize
end

.finder_method(name, &block) ⇒ Object

For finder_method we have to preapply ‘all` so that we always have a relationship



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/active_record_base.rb', line 80

def finder_method(name, &block)
  singleton_class.send(:define_method, :"__secure_remote_access_to__#{name}") do |this, acting_user, *args|
    this = respond_to?(:acting_user) ? this : all
    begin
      old = this.acting_user
      this.acting_user = acting_user
      # returns a PsuedoRelationArray which will respond to the
      # __secure_collection_check method
      ReactiveRecordPsuedoRelationArray.new([this.instance_exec(*args, &block)])
    ensure
      this.acting_user = old
    end
  end
  singleton_class.send(:define_method, name) do |*args|
    all.instance_exec(*args, &block)
  end
end

.has_many(name, *args, &block) ⇒ Object



243
244
245
246
247
248
249
250
251
# File 'lib/active_record_base.rb', line 243

def has_many(name, *args, &block)
  __synchromesh_regulate_from_macro(
    opts = args.extract_options!,
    name,
    method_defined?(:"__secure_remote_access_to_#{name}"),
    &method(:regulate_relationship)
  )
  pre_syncromesh_has_many name, *args, opts.except(:regulate), &block
end

.pre_synchromesh_default_scopeObject



217
# File 'lib/active_record_base.rb', line 217

alias pre_synchromesh_default_scope default_scope

.pre_synchromesh_scopeObject

monkey patch scope and default_scope macros to process hyperloop special opts, and add regulations if present



205
# File 'lib/active_record_base.rb', line 205

alias pre_synchromesh_scope scope

.pre_syncromesh_has_manyObject



241
# File 'lib/active_record_base.rb', line 241

alias pre_syncromesh_has_many has_many

.public_columns_hashObject



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/reactive_record/active_record/public_columns_hash.rb', line 9

def self.public_columns_hash
  return @public_columns_hash if @public_columns_hash && Rails.env.production?
  files = []
  Hyperloop.public_model_directories.each do |dir|
    dir_length = Rails.root.join(dir).to_s.length + 1
    Dir.glob(Rails.root.join(dir, '**', '*.rb')).each do |file|
      require_dependency(file) # still the file is loaded to make sure for development and test env
      files << file[dir_length..-4]
    end
  end
  @public_columns_hash = {}
  # descendants only works for already loaded models!
  descendants.each do |model|
    if files.include?(model.name.underscore) && model.name.underscore != 'application_record'
      @public_columns_hash[model.name] = model.columns_hash rescue nil # why rescue?
    end
    # begin
    #   @public_columns_hash[model.name] = model.columns_hash if model.table_name
    # rescue Exception => e
    #   binding.pry
    #   @public_columns_hash = nil
    #   raise $!, "Could not read 'columns_hash' for #{model}: #{$!}", $!.backtrace
    # end if files.include?(model.name.underscore) && model.name.underscore != 'application_record'
  end
  @public_columns_hash
end

.public_columns_hash_as_jsonObject



36
37
38
39
40
41
42
# File 'lib/reactive_record/active_record/public_columns_hash.rb', line 36

def self.public_columns_hash_as_json
  return @public_columns_hash_json if @public_columns_hash_json && Rails.env.production?
  pch = public_columns_hash
  return @public_columns_hash_json if @prev_public_columns_hash == pch
  @prev_public_columns_hash = pch
  @public_columns_hash_json = pch.to_json
end

.reflect_on_aggregation(attribute) ⇒ Object



9
10
11
# File 'lib/reactive_record/active_record/aggregations.rb', line 9

def self.reflect_on_aggregation(attribute)
  reflect_on_all_aggregations.detect { |aggregation| aggregation.attribute == attribute }
end

.reflect_on_all_aggregationsObject



5
6
7
# File 'lib/reactive_record/active_record/aggregations.rb', line 5

def self.reflect_on_all_aggregations
  base_class.instance_eval { @aggregations ||= [] }
end

.reflect_on_all_associationsObject



5
6
7
# File 'lib/reactive_record/active_record/associations.rb', line 5

def self.reflect_on_all_associations
  base_class.instance_eval { @associations ||= superclass.instance_eval { (@associations && @associations.dup) || [] } }
end

.reflect_on_association(attr) ⇒ Object



9
10
11
# File 'lib/reactive_record/active_record/associations.rb', line 9

def self.reflect_on_association(attr)
  reflection_finder { |assoc| assoc.attribute == attr }
end

.reflect_on_association_by_foreign_key(key) ⇒ Object



13
14
15
# File 'lib/reactive_record/active_record/associations.rb', line 13

def self.reflect_on_association_by_foreign_key(key)
  reflection_finder { |assoc| assoc.association_foreign_key == key }
end

.reflection_finder(&block) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/reactive_record/active_record/associations.rb', line 17

def self.reflection_finder(&block)
  found = reflect_on_all_associations.detect do |assoc|
    assoc.owner_class == self && yield(assoc)
  end
  if found
    found
  elsif superclass == Base
    nil
  else
    superclass.reflection_finder(&block)
  end
end

.regulate_default_scope(*args, &block) ⇒ Object

regulate_default_scope



197
198
199
200
# File 'lib/active_record_base.rb', line 197

def regulate_default_scope(*args, &block)
  block = __synchromesh_parse_regulator_params({ all: args[0] }, block).last unless args.empty?
  regulate_scope(:all, &block)
end

.regulate_relationship(name, &block) ⇒ Object

add regulate_relationship method and monkey patch has_many macro to add regulations if present



232
233
234
235
236
237
238
239
# File 'lib/active_record_base.rb', line 232

def regulate_relationship(name, &block)
  name, block = __synchromesh_parse_regulator_params(name, block)
  define_method(:"__secure_remote_access_to_#{name}") do |this, acting_user, *args|
    this.class.__set_synchromesh_permission_granted(
      nil, this.send(name, *args), this, acting_user, &block
    )
  end
end

.regulate_scope(name, &block) ⇒ Object

regulate scope has to deal with the special case that the scope returns an an array instead of a relationship. In this case we wrap the array and go on



186
187
188
189
190
191
192
193
# File 'lib/active_record_base.rb', line 186

def regulate_scope(name, &block)
  name, block = __synchromesh_parse_regulator_params(name, block)
  singleton_class.send(:define_method, :"__secure_remote_access_to_#{name}") do |this, acting_user, *args|
    r = this.send(name, *args)
    r = ReactiveRecordPsuedoRelationArray.new(r) if r.is_a? Array
    __set_synchromesh_permission_granted(this, r, r, acting_user, args, &block)
  end
end

.scope(name, *args, &block) ⇒ Object



207
208
209
210
211
212
213
214
215
# File 'lib/active_record_base.rb', line 207

def scope(name, *args, &block)
  __synchromesh_regulate_from_macro(
    (opts = _synchromesh_scope_args_check(args)),
    name,
    respond_to?(:"__secure_remote_access_to_#{name}"),
    &method(:regulate_scope)
  )
  pre_synchromesh_scope(name, opts[:server], &block)
end

.server_method(name, _opts = {}, &block) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/active_record_base.rb', line 98

def server_method(name, _opts = {}, &block)
  # callable from the server internally
  define_method(name, &block)
  # callable remotely from the client
  define_method("__secure_remote_access_to_#{name}") do |_self, acting_user, *args|
    begin
      old = self.acting_user
      self.acting_user = acting_user
      send(name, *args)
    ensure
      self.acting_user = old
    end
  end
end

Instance Method Details

#__hyperloop_secure_attributes(acting_user) ⇒ Object



317
318
319
320
321
# File 'lib/active_record_base.rb', line 317

def __hyperloop_secure_attributes(acting_user)
  accessible_attributes =
    Hyperloop::InternalPolicy.accessible_attributes_for(self, acting_user)
  attributes.select { |attr| accessible_attributes.include? attr.to_sym }
end

#all_changed?(*attributes) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
68
69
70
# File 'lib/reactive_record/permissions.rb', line 65

def all_changed?(*attributes)
  attributes.each do |key|
    return false unless self.send("#{key}_changed?")
  end
  true
end

#any_changed?(*attributes) ⇒ Boolean

Returns:

  • (Boolean)


58
59
60
61
62
63
# File 'lib/reactive_record/permissions.rb', line 58

def any_changed?(*attributes)
  attributes.each do |key|
    return true if self.send("#{key}_changed?")
  end
  false
end

#check_permission_with_acting_user(user, permission, *args) ⇒ Object



99
100
101
102
103
104
105
106
107
108
# File 'lib/reactive_record/permissions.rb', line 99

def check_permission_with_acting_user(user, permission, *args)
  old = acting_user
  self.acting_user = user
  if self.send(permission, *args)
    self.acting_user = old
    self
  else
    Hyperloop::InternalPolicy.raise_operation_access_violation(:crud_access_violation, "for #{self} - #{permission}(#{args}) acting_user: #{user}")
  end
end

#create_permitted?Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/reactive_record/permissions.rb', line 32

def create_permitted?
  false
end

#denied!Object



278
279
280
# File 'lib/active_record_base.rb', line 278

def denied!
  Hyperloop::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self.class} regulation denies scope access.  Called from #{caller_locations(1)}")
end

#destroy_permitted?Boolean

Returns:

  • (Boolean)


40
41
42
# File 'lib/reactive_record/permissions.rb', line 40

def destroy_permitted?
  false
end

#do_not_synchronize?Boolean

Returns:

  • (Boolean)


294
295
296
# File 'lib/active_record_base.rb', line 294

def do_not_synchronize?
  self.class.do_not_synchronize?
end

#none_changed?(*attributes) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
54
55
56
# File 'lib/reactive_record/permissions.rb', line 51

def none_changed?(*attributes)
  attributes.each do |key|
    return false if self.send("#{key}_changed?")
  end
  true
end

#only_changed?(*attributes) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
47
48
49
# File 'lib/reactive_record/permissions.rb', line 44

def only_changed?(*attributes)
  (self.attributes.keys + self.class.reactive_record_association_keys).each do |key|
    return false if self.send("#{key}_changed?") and !attributes.include? key
  end
  true
end

#synchromesh_after_changeObject



307
308
309
310
# File 'lib/active_record_base.rb', line 307

def synchromesh_after_change
  return if do_not_synchronize? || previous_changes.empty?
  ReactiveRecord::Broadcast.after_commit :change, self
end

#synchromesh_after_createObject



302
303
304
305
# File 'lib/active_record_base.rb', line 302

def synchromesh_after_create
  return if do_not_synchronize?
  ReactiveRecord::Broadcast.after_commit :create, self
end

#synchromesh_after_destroyObject



312
313
314
315
# File 'lib/active_record_base.rb', line 312

def synchromesh_after_destroy
  return if do_not_synchronize?
  ReactiveRecord::Broadcast.after_commit :destroy, self
end

#update_permitted?Boolean

Returns:

  • (Boolean)


36
37
38
# File 'lib/reactive_record/permissions.rb', line 36

def update_permitted?
  false
end

#view_permitted?(attribute) ⇒ Boolean

Returns:

  • (Boolean)


28
29
30
# File 'lib/reactive_record/permissions.rb', line 28

def view_permitted?(attribute)
  Hyperloop::InternalPolicy.accessible_attributes_for(self, acting_user).include? attribute.to_sym
end