core/lib/refinery/crud.rb
# Base methods for CRUD actions
# Simply override any methods in your action controller you want to be customised
# Don't forget to add:
# resources :plural_model_name_here
# or for scoped:
# scope(:as => 'module_module', :module => 'module_name') do
# resources :plural_model_name_here
# end
# to your routes.rb file.
# Full documentation about CRUD and resources go here:
# -> http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources
# Example (add to your controller):
# crudify :foo, :title_attribute => 'name' for CRUD on Foo model
# crudify :'foo/bar', :title_attribute => 'name' for CRUD on Foo::Bar model
# Note: @singular_name will result in @foo for :foo and @bar for :'foo/bar'
module Refinery
module Crud
def self.default_options(model_name)
class_name = "#{model_name.to_s.camelize.gsub('/', '::')}".gsub('::::', '::')
this_class = class_name.constantize.base_class
singular_name = ActiveModel::Naming.param_key(this_class)
plural_name = singular_name.pluralize
{
:conditions => '',
:include => [],
:order => ('position ASC' if this_class.connected? && this_class.table_exists? && this_class.column_names.include?('position')),
:paging => true,
:per_page => false,
:redirect_to_url => "refinery.#{Refinery.route_for_model(class_name.constantize, :plural => true)}",
:searchable => true,
:search_conditions => '',
:sortable => true,
:title_attribute => "title",
:class_name => class_name,
:singular_name => singular_name,
:plural_name => plural_name
}
end
def self.append_features(base)
super
base.extend(ClassMethods)
end
module ClassMethods
def crudify(model_name, options = {})
options = ::Refinery::Crud.default_options(model_name).merge(options)
class_name = options[:class_name]
singular_name = options[:singular_name]
plural_name = options[:plural_name]
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def self.crudify_options
#{options.inspect}
end
prepend_before_action :find_#{singular_name},
:only => [:update, :destroy, :edit, :show]
prepend_before_action :merge_position_into_params!, :only => :create
def new
@#{singular_name} = #{class_name}.new
end
def create
@#{singular_name} = #{class_name}.new(#{singular_name}_params)
if @#{singular_name}.save
flash.notice = t(
'refinery.crudify.created',
:what => "'\#{@#{singular_name}.#{options[:title_attribute]}}'"
)
create_or_update_successful
else
create_or_update_unsuccessful 'new'
end
end
def edit
# object gets found by find_#{singular_name} function
end
def update
if @#{singular_name}.update(#{singular_name}_params)
flash.notice = t(
'refinery.crudify.updated',
:what => "'\#{@#{singular_name}.#{options[:title_attribute]}}'"
)
create_or_update_successful
else
create_or_update_unsuccessful 'edit'
end
end
def destroy
# object gets found by find_#{singular_name} function
title = @#{singular_name}.#{options[:title_attribute]}
if @#{singular_name}.destroy
flash.notice = t('destroyed', :scope => 'refinery.crudify', :what => "'\#{title}'")
end
redirect_to redirect_url
end
# Finds one single result based on the id params.
def find_#{singular_name}
@#{singular_name} = find_#{singular_name}_scope.find(params[:id])
end
def find_#{singular_name}_scope
_finder_scope = #{class_name}.includes(#{options[:include].map(&:to_sym).inspect})
_finder_scope = _finder_scope.friendly if _finder_scope.respond_to?(:friendly)
_finder_scope
end
# Find the collection of @#{plural_name} based on the conditions specified into crudify
# It will be ordered based on the conditions specified into crudify
# And eager loading is applied as specified into crudify.
def find_all_#{plural_name}(conditions = #{options[:conditions].inspect})
@#{plural_name} = find_#{singular_name}_scope
.where(conditions)
.order("#{options[:order]}")
end
def merge_position_into_params!
# if the position field exists, set this object as last object, given the conditions of this class.
if #{class_name}.column_names.include?("position") && params[:#{singular_name}][:position].nil?
params[:#{singular_name}].merge!({
position: ((#{class_name}.where(#{options[:conditions].inspect}).maximum(:position)||-1) + 1)
})
end
end
# Paginate a set of @#{plural_name} that may/may not already exist.
def paginate_all_#{plural_name}
# If we have already found a set then we don't need to again
find_all_#{plural_name} if @#{plural_name}.nil?
@#{plural_name} = @#{plural_name}.paginate(:page => params[:page], :per_page => paginate_per_page)
end
def paginate_per_page
if #{options[:per_page].present?.inspect}
#{options[:per_page].inspect}
elsif #{class_name}.methods.map(&:to_sym).include?(:per_page)
#{class_name}.per_page
end
end
def redirect_url
if params[:page].present?
page = params[:page].to_i rescue 1
page -= 1 while #{class_name}.paginate(:page => page).empty? && page > 1
#{options[:redirect_to_url]}(:page => page)
else
#{options[:redirect_to_url]}
end
end
# If the controller is being accessed via an ajax request
# then render only the collection of items.
def render_partial_response?
if request.xhr?
render plain: render_to_string(partial: '#{plural_name}', layout: false).html_safe,
layout: 'refinery/flash' and return false
end
end
def create_or_update_successful
if from_dialog?
self.index
@dialog_successful = true
render :index
else
if /true|on|1/ === params[:continue_editing]
if request.xhr?
render :partial => '/refinery/message'
else
redirect_back(fallback_location: { action: 'edit' })
end
else
redirect_back_or_default redirect_url
end
end
end
def create_or_update_unsuccessful(action)
if request.xhr?
render :partial => '/refinery/admin/error_messages', :locals => {
:object => @#{singular_name},
:include_object_name => true
}
else
render :action => action
end
end
# Returns a weighted set of results based on the query specified by the user.
def search_all_#{plural_name}
# First find normal results.
find_all_#{plural_name}(#{options[:search_conditions].inspect})
# Now get weighted results by running the query against the results already found.
@#{plural_name} = @#{plural_name}.with_query(params[:search])
end
def #{singular_name}_params
raise "Please override #{singular_name}_params with your desired parameter security."
end
# Ensure all methods are protected so that they should only be called
# from within the current controller.
protected :find_#{singular_name},
:find_#{singular_name}_scope,
:find_all_#{plural_name},
:paginate_all_#{plural_name},
:paginate_per_page,
:render_partial_response?,
:search_all_#{plural_name},
:#{singular_name}_params,
:redirect_url,
:create_or_update_successful,
:create_or_update_unsuccessful,
:merge_position_into_params!
RUBY
# Methods that are only included when this controller is searchable.
if options[:searchable]
if options[:paging]
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def index
search_all_#{plural_name} if searching?
paginate_all_#{plural_name}
render_partial_response?
end
RUBY
else
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def index
if searching?
search_all_#{plural_name}
else
find_all_#{plural_name}
end
render_partial_response?
end
RUBY
end
else
if options[:paging]
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def index
paginate_all_#{plural_name}
render_partial_response?
end
RUBY
else
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def index
find_all_#{plural_name}
render_partial_response?
end
RUBY
end
end
if options[:sortable]
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def reorder
find_all_#{plural_name}
end
# Based upon https://github.com/matenia/jQuery-Awesome-Nested-Set-Drag-and-Drop
def update_positions
previous = nil
params.to_unsafe_h[:ul].each do |_, list|
list.each do |index, hash|
moved_item_id = hash['id'][/\\d+\\z/]
@current_#{singular_name} = #{class_name}.find_by_id(moved_item_id)
if @current_#{singular_name}.respond_to?(:move_to_root)
if previous.present?
@current_#{singular_name}.move_to_right_of(#{class_name}.find_by_id(previous))
elsif !@current_#{singular_name}.root?
@current_#{singular_name}.move_to_root
end
else
@current_#{singular_name}.update_columns position: index
end
if hash['children'].present?
update_child_positions(hash, @current_#{singular_name})
end
previous = moved_item_id
end
end
#{class_name}.rebuild! if #{class_name}.respond_to?(:rebuild!)
after_update_positions
end
def update_child_positions(_node, #{singular_name})
list = _node['children']['0']
child_positions_changed = false
list.sort_by { |k, v| k.to_i}.map { |item| item[1] }.each_with_index do |child, index|
child_id = child['id'].split(/#{singular_name}\_?/).reject(&:empty?).first
child_#{singular_name} = #{class_name}.where(:id => child_id).first
child_positions_changed ||= #{singular_name}.children[index] != child_#{singular_name}
if child_positions_changed
child_#{singular_name}.move_to_child_of(#{singular_name})
end
if child['children'].present?
update_child_positions(child, child_#{singular_name})
end
end
end
def after_update_positions
head :ok
end
protected :after_update_positions
RUBY
end
module_eval <<-RUBY, __FILE__, __LINE__ + 1
class << self
def pageable?
#{options[:paging]}
end
alias_method :paging?, :pageable?
def sortable?
#{options[:sortable]}
end
def searchable?
#{options[:searchable]}
end
end
RUBY
end
end
end
end