Class: Controller

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/controller.rb

Overview

the table is managed so as to make it mirror the file system the only reason this is necessary is to be able to store in the database a last_modified attribute for a file, in order to know whether the actions table is up to date for this controller or should be regenerated from the file contents The class represents both filesystem objects and database objects hmmm… is that really the best way to design it, shouldn’t it be two classes?

Constant Summary collapse

CONTROLLERS_DIR =
"#{Rails.root.to_s}/app/controllers"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.all_actions_from_filesObject



97
98
99
# File 'app/models/controller.rb', line 97

def self.all_actions_from_files
  all_controller_names.inject([]){ |ar,c| ar+=c.action_list; ar  }
end

.all_controller_namesObject

returns an array of strings, each one is a controller name



15
16
17
# File 'app/models/controller.rb', line 15

def self.all_controller_names
  @@controllers ||= all_files.map { |file| file.camelize.gsub(".rb","") }
end

.all_filesObject



19
20
21
# File 'app/models/controller.rb', line 19

def self.all_files
  application_files + engine_files
end

.all_modified_filesObject



47
48
49
# File 'app/models/controller.rb', line 47

def self.all_modified_files
  all_controller_names.select(&:modified?)
end

.application_filesObject



43
44
45
# File 'app/models/controller.rb', line 43

def self.application_files
  Dir.new(CONTROLLERS_DIR).entries.reject{|c| c.match(/^\./)}.reject{|c| c == 'application_controller.rb'}
end

.engine_controller_pathsObject



37
38
39
40
41
# File 'app/models/controller.rb', line 37

def self.engine_controller_paths
  Rails::Engine.subclasses.collect { |engine|
    engine.config.eager_load_paths.detect{|p| p=~/controller/}
  }.flatten.compact
end

.engine_filesObject



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'app/models/controller.rb', line 23

def self.engine_files
  engine_controller_paths.inject([]) do |array, path|
    # entries may be controller files, but if there are namespaced controllers
    # then entries are directories
    directories, files = Dir.new(path).entries.reject{|c|c.match(/^\./)}.partition{|c| File.directory?(File.new(File.join(path,c)))}
    array += files
    directories.each do |directory|
      files = Dir.new(File.join(path,directory)).reject{|c|c.match(/^\./)}.entries.map{|file| File.join(directory,file)}
      array += files
    end
    array
  end
end

.update_tableObject

updates the controllers table so that it contains a record for each controller file in the /app/controllers directory



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'app/models/controller.rb', line 75

def self.update_table
  cc = Controller.all.inject({}){ |hash,controller| hash[controller.controller_name]=controller; hash } # from the database
  all_controller_names.each do |f| # f is of the form "ItemsController"
    cont = f.tableize.gsub!("_controllers","") # cont is of the form "items"
    admin_name = Role.find_by_name("administrator") ? "administrator" : "admin"
    if !cc.keys.include?(cont) # it's not in the db
      new_controller = new(:controller_name=>f.underscore.gsub!("_controller", ""), :last_modified=>Date.today) # add controller to controllers table as there's not a record corresponding with the file
      new_controller.actions << new_controller.action_list.map { |a| Action.new(:action_name=>a[1]) }# when a new controller is added, its actions must be added to the actions.file
      new_controller.save
    elsif cc[cont].modified? # file was modified since db was updated, so read the actions from the file, and add/delete as necessary
      action_names = cc[cont].actions_from_file # action_names is of the form ["index", "new", "edit", "create", "update"]
      Action.update_table_for(cc[cont],action_names)
      # finally modify the last_modified date of the controller record to match the actual file
      cc[cont].update_attribute(:last_modified,cc[cont].file_modification_time)
    end
  end
  ActionRole.assign_developer_access
  # delete any records in the controllers table for which there's no xx_controller.rb file... it must've been deleted
  cc.each { |name,controller| controller.destroy if !all_controller_names.map{|cn| cn.tableize.gsub!("_controllers","")}.include?(name) }

end

Instance Method Details

#action_listObject

returns an array of arrays, each contains the controller controller_name and action name



120
121
122
# File 'app/models/controller.rb', line 120

def action_list
  actions_from_file.collect { |m| [model_name,m] }
end

#actions_from_fileObject

the actions returned are those that were in the file when it was loaded this is reasonable only because the actions are changed by the developer and never get changed “on the fly”. However this fact should be considered when testing!



108
109
110
111
112
113
# File 'app/models/controller.rb', line 108

def actions_from_file
  # there's a workaround here for some strangeness that appeared in Rails 3
  # where public_instance_methods returns some spurious methods with the format
  # _one_time_conditions_valid_nnn?
  controller.public_instance_methods(false).reject{|m| m.match(/one_time_conditions_valid/)}.map(&:to_s)
end

#controllerObject



115
116
117
# File 'app/models/controller.rb', line 115

def controller
  (controller_name+"_controller").classify.constantize
end

#fileObject



64
65
66
67
68
69
70
71
# File 'app/models/controller.rb', line 64

def file
  all_paths = Controller.engine_controller_paths << CONTROLLERS_DIR
  controller_path = all_paths.detect do |path|
    full_path = File.join(path, "#{controller_name}_controller.rb")
    File.exists?(full_path)
  end
  File.new(File.join(controller_path, "#{controller_name}_controller.rb"))
end

#file_modification_timeObject



60
61
62
# File 'app/models/controller.rb', line 60

def file_modification_time
  file.mtime.getutc.to_datetime
end

#model_nameObject



101
102
103
# File 'app/models/controller.rb', line 101

def model_name
  controller_name.gsub("Controller","").underscore
end

#modified?Boolean

a file is declared to be modified if it’s either older or newer than the last_modified date this triggers re-parsing the actions in the file whether it’s older or newer and so responds both to the file being edited and also the database being restored from an older version. Only by converting to string could I persuade two apparently equal DateTime objects to match!

Returns:

  • (Boolean)


56
57
58
# File 'app/models/controller.rb', line 56

def modified?
  file_modification_time.to_s != last_modified.getutc.to_datetime.to_s
end