module ExpressAdmin module StandardActions def self.included(base) base.class_eval do extend ClassMethods include InstanceMethods helper_method :collection helper_method :resource helper_method :resource_path helper_method :collection_path class_attribute :defaults self.defaults = {layout: 'layouts/express_admin/admin'} before_filter :expose_parent_resources end end module ClassMethods def inherited(base) base.class_eval <<-RUBY def #{base.resource_name}_params params.require(:#{base.resource_name}).permit! end RUBY base.class_eval do class_attribute :resource_class self.resource_class = infer_resource_class if self.resource_class.respond_to?(:commands) self.resource_class.commands.each do |command| define_command_method(command.debang, command) end end end end def manages(a_resource_class) self.resource_class = a_resource_class end def resource_name self.to_s.demodulize.gsub(/Controller$/, '').singularize.underscore end def infer_resource_class klass = nil if self.parent klass = self.parent.const_get(resource_name.classify) rescue nil end klass ||= resource_name.classify.constantize rescue nil klass end protected def define_command_method(action, command) define_method(action) do load_resource begin resource.send(command) respond_to do |format| format.html { redirect_to action: show_view } format.js { render status: :ok, body: nil } # maybe we should return enough info #to alter display of available or enabled commands? end rescue => e respond_to do |format| format.html { flash[:error] = e.to_s redirect_to action: show_view } format.js { render status: :bad_request, body: e.to_s } end end end end end module InstanceMethods def index build_resource load_collection respond_to do |format| format.html { render index_view, layout: defaults[:layout] } end end def new build_resource respond_to do |format| format.html { render new_view, layout: defaults[:layout] } end end def create build_resource(resource_params) if resource.save respond_to do |format| format.html { redirect_to resource_path } end else respond_to do |format| format.html { render new_view, layout: defaults[:layout] } end end end def show load_resource load_collection respond_to do |format| format.html { render show_view, layout: defaults[:layout] } end end def edit load_resource respond_to do |format| format.html { render edit_view, layout: defaults[:layout] } end end def update load_resource if resource.update_attributes(resource_params) respond_to do |format| format.html { redirect_to resource_path } end else respond_to do |format| format.html { render edit_view, layout: defaults[:layout] } end end end def destroy load_resource resource.destroy! respond_to do |format| format.html { redirect_to collection_path } end end protected def edit_view :edit end def new_view :new end def show_view :show end def index_view :index end def resource_path proxy = route_proxy proxy ||= self if parent_resource_names.blank? proxy.send(scoped_path_helper("#{resource_name}_path"), resource) else proxy.send(scoped_path_helper(nested_resource_path_helper), resource_ids_hash) end end def scoped_path_helper(path_helper) [scope_name, path_helper].compact.join('_') end def collection_path proxy = route_proxy proxy ||= self if parent_resource_names.blank? proxy.send(scoped_path_helper("#{collection_name}_path")) else proxy.send(scoped_path_helper(nested_collection_path_helper), resource_ids_hash) end end def parent_module_name self.class.to_s.match(/(\w+)::/).try(:[], 1) end def route_proxy engine = "#{parent_module_name}::Engine".constantize rescue nil if parent_module_name && engine self.send(parent_module_name.underscore) else nil end end def scope_name engine = "#{parent_module_name}::Engine".constantize rescue nil if parent_module_name && engine.nil? return parent_module_name.underscore else nil end end def resource_params self.send("#{resource_name}_params") end def nested? !parent_resource_names.empty? end def parent_resource self.send("current_#{parent_resource_names.last}") end def end_of_association_chain if nested? parent_resource else nil end end def load_collection scope = if end_of_association_chain end_of_association_chain.send(collection_name) else resource_class end scope = search?(scope) scope = instance_variable(scope) scope = params[:asc] || params[:desc] ? sort_table(scope) : scope self.instance_variable_set(collection_ivar, scope) end def instance_variable(scope) scope.kind_of?(Array) ? scope : scope.all end def sort_table(scope) if params.keys.include?('asc') scope.sort{ |a, b| a.send(params[:asc]) <=> b.send(params[:asc])} elsif params.keys.include?('desc') scope.sort{ |a, b| b.send(params[:desc]) <=> a.send(params[:desc])} else scope end end def search?(scope) params[:search_string].present? ? search(scope) : scope end def search(scope) Search.execute(scope, params[:search_string]) end def collection_ivar "@#{collection_name}".to_sym end def collection self.instance_variable_get(collection_ivar) end def collection_name self.class.to_s.demodulize.gsub(/Controller$/, '').pluralize.underscore end def resource self.instance_variable_get(resource_ivar) end def build_resource(*args) if nested? self.instance_variable_set(resource_ivar, end_of_association_chain.send(collection_name).build(*args)) else self.instance_variable_set(resource_ivar, resource_class.new(*args)) end end def load_resource self.instance_variable_set(resource_ivar, resource_class.find(params[:id])) end def resource_ivar "@#{resource_name}".to_sym end # Foo::WidgetsController -> widget def resource_name self.class.resource_name end def nested_resource_path_helper (parent_resource_names + [resource_name, 'path']).join('_') end def nested_collection_path_helper (parent_resource_names + [collection_name, 'path']).join('_') end def expose_parent_resources parent_resources = parent_resource_names return if parent_resources.blank? previous_parent = nil parent_resources.each do |parent_name| # TODO: optimize parent_id = extract_path_info_from_routes["#{parent_name}_id".to_sym] current_parent = "current_#{parent_name}".to_sym unless self.methods.include?(current_parent) if previous_parent.nil? self.class_eval do define_method(current_parent) do parent_class = parent_module_name.constantize current_class_name = parent_name.camelize current_class = parent_class.const_defined?(current_class_name) ? parent_class.const_get(current_class_name) : "::#{parent_name.camelize}".constantize current_class.find(parent_id) end end else self.class_eval do grandparent_accessor = previous_parent define_method(current_parent) do grandparent_resource = self.send(grandparent_accessor) parent_scope = grandparent_resource.send(parent_name.pluralize) parent_scope.find(parent_id) end end end end previous_parent = current_parent end end def parent_resource_names path_info = extract_path_info_from_routes unless path_info.nil? path_info.keys.grep(/_id$/).map do |id| id.to_s.gsub(/_id$/, '') end end end def extract_path_info_from_routes recognized_path = nil Rails::Engine.subclasses.each do |engine| engine_instance = engine.instance engine_route = Rails.application.routes.routes.find do |r| r.app.app == engine_instance.class end next unless engine_route path_for_engine = request.path.gsub(%r(^#{engine_route.path.spec.to_s}), "") begin recognized_path = engine_instance.routes.recognize_path(path_for_engine) rescue ActionController::RoutingError => e end end if recognized_path.nil? begin recognized_path = Rails.application.routes.recognize_path(request.path) rescue ActionController::RoutingError => e end end recognized_path end def resource_ids_hash parent_resource_names.inject({id: resource.to_param}) do |hash, name| hash["#{name}_id".to_sym] = self.send("current_#{name}".to_sym).to_param hash end end end end end