activeadmin_associations 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. data/.coveralls.yml +1 -0
  2. data/.gitignore +57 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +9 -0
  6. data/MIT_LICENSE.txt +20 -0
  7. data/README.md +220 -0
  8. data/Rakefile +20 -0
  9. data/activeadmin_associations.gemspec +32 -0
  10. data/app/controllers/autocomplete_controller.rb +37 -0
  11. data/app/helpers/active_admin_associations_helper.rb +77 -0
  12. data/app/views/admin/shared/_add_to_association.html.erb +21 -0
  13. data/app/views/admin/shared/_association_collection_table_actions.html.erb +4 -0
  14. data/app/views/admin/shared/_blank_slate.html.erb +3 -0
  15. data/app/views/admin/shared/_collection_table.html.erb +58 -0
  16. data/app/views/admin/shared/_form.html.erb +7 -0
  17. data/config/routes.rb +7 -0
  18. data/lib/active_admin_associations/active_admin_extensions.rb +17 -0
  19. data/lib/active_admin_associations/association_actions.rb +77 -0
  20. data/lib/active_admin_associations/association_config.rb +50 -0
  21. data/lib/active_admin_associations/autocompleter.rb +61 -0
  22. data/lib/active_admin_associations/engine.rb +23 -0
  23. data/lib/active_admin_associations/form_config_dsl.rb +15 -0
  24. data/lib/active_admin_associations/redirect_destroy_actions.rb +7 -0
  25. data/lib/active_admin_associations/version.rb +3 -0
  26. data/lib/activeadmin_associations.rb +20 -0
  27. data/lib/formtastic/inputs/token_input.rb +43 -0
  28. data/lib/formtastic/token_input_default_for_association.rb +19 -0
  29. data/spec/association_config_spec.rb +41 -0
  30. data/spec/autocompleter_spec.rb +58 -0
  31. data/spec/controllers/admin_posts_controller_spec.rb +87 -0
  32. data/spec/controllers/autocomplete_controller_spec.rb +36 -0
  33. data/spec/dummy/README.rdoc +261 -0
  34. data/spec/dummy/Rakefile +7 -0
  35. data/spec/dummy/app/admin/dashboards.rb +33 -0
  36. data/spec/dummy/app/admin/posts.rb +20 -0
  37. data/spec/dummy/app/admin/tags.rb +11 -0
  38. data/spec/dummy/app/assets/javascripts/active_admin.js +8 -0
  39. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  40. data/spec/dummy/app/assets/stylesheets/active_admin.css.scss +6 -0
  41. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  42. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  43. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  44. data/spec/dummy/app/mailers/.gitkeep +0 -0
  45. data/spec/dummy/app/models/.gitkeep +0 -0
  46. data/spec/dummy/app/models/admin_user.rb +10 -0
  47. data/spec/dummy/app/models/post.rb +11 -0
  48. data/spec/dummy/app/models/tag.rb +15 -0
  49. data/spec/dummy/app/models/tagging.rb +7 -0
  50. data/spec/dummy/app/models/user.rb +8 -0
  51. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  52. data/spec/dummy/config.ru +4 -0
  53. data/spec/dummy/config/application.rb +63 -0
  54. data/spec/dummy/config/boot.rb +10 -0
  55. data/spec/dummy/config/database.yml +19 -0
  56. data/spec/dummy/config/environment.rb +5 -0
  57. data/spec/dummy/config/environments/development.rb +37 -0
  58. data/spec/dummy/config/environments/production.rb +67 -0
  59. data/spec/dummy/config/environments/test.rb +37 -0
  60. data/spec/dummy/config/initializers/active_admin.rb +129 -0
  61. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  62. data/spec/dummy/config/initializers/devise.rb +218 -0
  63. data/spec/dummy/config/initializers/inflections.rb +15 -0
  64. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  65. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  66. data/spec/dummy/config/initializers/session_store.rb +8 -0
  67. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  68. data/spec/dummy/config/locales/devise.en.yml +57 -0
  69. data/spec/dummy/config/locales/en.yml +5 -0
  70. data/spec/dummy/config/routes.rb +62 -0
  71. data/spec/dummy/db/.gitignore +1 -0
  72. data/spec/dummy/db/migrate/20120504220404_devise_create_admin_users.rb +52 -0
  73. data/spec/dummy/db/migrate/20120504221534_create_posts.rb +12 -0
  74. data/spec/dummy/db/migrate/20120504221936_create_users.rb +9 -0
  75. data/spec/dummy/db/migrate/20120504222040_create_tags.rb +8 -0
  76. data/spec/dummy/db/migrate/20120504222247_create_taggings.rb +10 -0
  77. data/spec/dummy/db/schema.rb +65 -0
  78. data/spec/dummy/lib/assets/.gitkeep +0 -0
  79. data/spec/dummy/public/404.html +26 -0
  80. data/spec/dummy/public/422.html +26 -0
  81. data/spec/dummy/public/500.html +25 -0
  82. data/spec/dummy/public/favicon.ico +0 -0
  83. data/spec/dummy/script/rails +6 -0
  84. data/spec/dummy/test/unit/admin_user_test.rb +7 -0
  85. data/spec/dummy/test/unit/post_test.rb +7 -0
  86. data/spec/dummy/test/unit/tag_test.rb +7 -0
  87. data/spec/dummy/test/unit/tagging_test.rb +7 -0
  88. data/spec/dummy/test/unit/user_test.rb +7 -0
  89. data/spec/factories/admin_users.rb +9 -0
  90. data/spec/factories/posts.rb +11 -0
  91. data/spec/factories/taggings.rb +6 -0
  92. data/spec/factories/tags.rb +7 -0
  93. data/spec/factories/users.rb +8 -0
  94. data/spec/features/active_admin_associations_spec.rb +94 -0
  95. data/spec/spec_helper.rb +41 -0
  96. data/spec/support/admin_login_controller_helper.rb +7 -0
  97. data/spec/support/admin_login_integration_helper.rb +8 -0
  98. data/vendor/assets/javascripts/active_admin_associations.js +14 -0
  99. data/vendor/assets/javascripts/jquery.tokeninput.js +915 -0
  100. data/vendor/assets/stylesheets/active_admin_associations.css.scss +18 -0
  101. data/vendor/assets/stylesheets/token-input-facebook.css +121 -0
  102. metadata +383 -0
@@ -0,0 +1,21 @@
1
+ <%- if resource_administrated?(relationship_class) && relationship_class.respond_to?(:autocomplete_attribute) && relationship_class.autocomplete_attribute.present? -%>
2
+ <%= form_tag relate_to_url(object), :class => 'relate-to-form', :method => :put do %>
3
+ <fieldset class="inputs">
4
+ <h4>Add a <%= relationship_class.model_name.human %></h4>
5
+ <%= hidden_field_tag 'relationship_name', relationship %>
6
+ <ol>
7
+ <li class="label">
8
+ <label for="related_id">
9
+ <%= relationship_class.model_name.human %>
10
+ <%= relationship_class.human_attribute_name(relationship_class.autocomplete_attribute) %>
11
+ </label>
12
+ </li>
13
+ <li class="input">
14
+ <%= hidden_field_tag 'related_id', nil, :class => 'token-input',
15
+ 'data-model-name' => relationship_class.model_name.singular %>
16
+ </li>
17
+ <li class="button"><%= submit_tag "Add #{relationship_class.model_name.human}" %></li>
18
+ </ol>
19
+ </fieldset>
20
+ <% end %>
21
+ <%- end -%>
@@ -0,0 +1,4 @@
1
+ <%= link_to "Edit", edit_url_for(record) %>
2
+ <%= button_to "Unrelate", {:action => :unrelate, :id => object.to_param,
3
+ :relationship_name => relationship,
4
+ :related_id => record.id}, :method => :put %>
@@ -0,0 +1,3 @@
1
+ <div class="blank_slate_container">
2
+ <span class="blank_slate flash"><%= blank_text %></span>
3
+ </div>
@@ -0,0 +1,58 @@
1
+ <div class="relationship-table" id="relationship-table-<%= relationship %>">
2
+ <h3><%= object.class.human_attribute_name(relationship) %></h3>
3
+ <%= render :partial => 'admin/shared/add_to_association', :locals => {
4
+ :relationship_class => relationship_class,
5
+ :object => object,
6
+ :relationship => relationship
7
+ } %>
8
+ <%- if collection.present? -%>
9
+ <table class="index_table">
10
+ <thead>
11
+ <tr>
12
+ <th>ID</th>
13
+ <% columns.each do |column| %>
14
+ <th><%= relationship_class.human_attribute_name(column) %></th>
15
+ <%- end -%>
16
+ <th>&nbsp;</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <%- collection.each do |record| -%>
21
+ <tr class="<%= cycle("odd", "even") -%>">
22
+ <td><%= record.id %></td>
23
+ <%- columns.each do |column| -%>
24
+ <%- if record.send(column).is_a?(ActiveRecord::Base) -%>
25
+ <td><%= display_name_for(record.send(column)) %></td>
26
+ <%- else -%>
27
+ <td><%= record.send(column) %></td>
28
+ <%- end -%>
29
+ <%- end -%>
30
+ <td class="actions">
31
+ <%- if resource_administrated?(record.class) -%>
32
+ <%= render :partial => 'admin/shared/association_collection_table_actions', :locals => {
33
+ :record => record,
34
+ :object => object,
35
+ :relationship => relationship
36
+ } %>
37
+ <%- else -%>
38
+ &nbsp;
39
+ <%- end -%>
40
+ </td>
41
+ </tr>
42
+ <%- end -%>
43
+ </tbody>
44
+ </table>
45
+ <div class="resource-table-footer">
46
+ <%= paginate collection, :remote => true, :params => {
47
+ :action => 'page_related', :relationship_name => relationship
48
+ } %>
49
+ <div class="pagination_information">
50
+ <%= raw page_entries_info(collection) %>
51
+ </div>
52
+ </div>
53
+ <%- else -%>
54
+ <%= render :partial => 'admin/shared/blank_slate', :locals => {
55
+ :blank_text => "There are no #{object.class.human_attribute_name(relationship)}"
56
+ } %>
57
+ <%- end -%>
58
+ </div>
@@ -0,0 +1,7 @@
1
+ <%= admin_form_for resource %>
2
+
3
+ <%- if resource.persisted? && active_admin_config.form_associations.present? -%>
4
+ <%- active_admin_config.form_associations.each do |association| -%>
5
+ <%= collection_relationship_manager(resource, association) %>
6
+ <%- end -%>
7
+ <%- end -%>
@@ -0,0 +1,7 @@
1
+ if Rails.application.config.activeadmin_associations.autocomplete_models
2
+ models = Rails.application.config.activeadmin_associations.autocomplete_models.join('|')
3
+ Rails.application.routes.draw do
4
+ match '/autocomplete/:model' => 'autocomplete#index', :model => /(#{models})/,
5
+ :defaults => { :format => 'json' }
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveAdmin
2
+ class Resource
3
+ attr_accessor :form_columns
4
+ attr_accessor :form_associations
5
+ attr_accessor :active_association_form
6
+ end
7
+
8
+ class << self
9
+ def resources
10
+ application.namespaces.values.map{|n|
11
+ n.resources.send(:resources)
12
+ }.flatten.compact.select{|r|
13
+ r.class == ActiveAdmin::Resource
14
+ }.map(&:resource_class)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,77 @@
1
+ module ActiveAdminAssociations
2
+ module AssociationActions
3
+ def association_actions
4
+ controller do
5
+ include InstanceMethods
6
+ end
7
+
8
+ member_action :unrelate, :method => :put do
9
+ unrelate_record
10
+ end
11
+
12
+ member_action :relate, :method => :put do
13
+ relate_record
14
+ end
15
+
16
+ member_action :page_related, :method => :get do
17
+ page_related_resources
18
+ end
19
+ end
20
+
21
+ module InstanceMethods
22
+ private
23
+
24
+ def unrelate_record
25
+ if relationship_reflection.collection?
26
+ related_record = relationship_class.find(params[:related_id])
27
+ resource.send(relationship_name).delete(related_record)
28
+ else
29
+ resource.update_attribute(relationship_reflection.foreign_key, nil)
30
+ end
31
+
32
+ redirect_back_or_dashboard("The recored has been unrelated.")
33
+ end
34
+
35
+ def relate_record
36
+ if relationship_reflection.collection?
37
+ record_to_relate = relationship_class.find(params[:related_id])
38
+ resource.send(relationship_name) << record_to_relate
39
+ else
40
+ resource.update_attribute(relationship_reflection.foreign_key, params[:related_id])
41
+ end
42
+
43
+ redirect_back_or_dashboard("The recored has been related.")
44
+ end
45
+
46
+ def page_related_resources
47
+ association_config = active_admin_config.form_associations[relationship_name]
48
+ association_columns = association_config.fields.presence || relationship_class.content_columns
49
+
50
+ render :partial => 'admin/shared/collection_table', :locals => {
51
+ :object => resource,
52
+ :collection => resource.send(relationship_name).page(params[:page]),
53
+ :relationship => relationship_name,
54
+ :columns => association_columns,
55
+ :relationship_class => relationship_class
56
+ }
57
+ end
58
+
59
+ def relationship_name
60
+ params[:relationship_name].to_sym
61
+ end
62
+
63
+ def relationship_reflection
64
+ resource_class.reflect_on_association(relationship_name)
65
+ end
66
+
67
+ def relationship_class
68
+ relationship_reflection.klass
69
+ end
70
+
71
+ def redirect_back_or_dashboard(flash_message)
72
+ flash[:notice] = flash_message
73
+ redirect_to request.headers["Referer"].presence || admin_dashboard_url
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,50 @@
1
+ module ActiveAdminAssociations
2
+ class AssociationConfig
3
+ include Enumerable
4
+
5
+ class Association
6
+ attr_reader :name
7
+ attr_reader :fields
8
+
9
+ def initialize(name, fields = [], &block)
10
+ @name = name.to_sym
11
+ @fields = fields
12
+ instance_exec(&block) if block_given?
13
+ end
14
+
15
+ def field(name)
16
+ @fields << name
17
+ end
18
+
19
+ def fields(*names)
20
+ @fields += names
21
+ end
22
+ end
23
+
24
+ attr_reader :association_configs
25
+ delegate :each, :to => :association_configs
26
+
27
+ def initialize(&block)
28
+ @association_configs = []
29
+ instance_exec(&block)
30
+ end
31
+
32
+ def blank?
33
+ @association_configs.blank?
34
+ end
35
+
36
+ def [](association_name)
37
+ @association_configs.detect {|a| a.name == association_name.to_sym }
38
+ end
39
+
40
+ def association(name, fields = [], &block)
41
+ @association_configs << Association.new(name, fields, &block)
42
+ end
43
+
44
+ def associations(*names)
45
+ names.each do |name|
46
+ @association_configs << Association.new(name)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,61 @@
1
+ module ActiveAdminAssociations
2
+ module Autocompleter
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def autocomplete(attribute, options = {})
7
+ class_attribute :autocomplete_attribute
8
+ class_attribute :autocomplete_options
9
+
10
+ self.autocomplete_attribute = attribute
11
+ self.autocomplete_options = options
12
+
13
+ extend AutocompleteMethods
14
+ end
15
+ end
16
+
17
+ module AutocompleteMethods
18
+ def autocomplete_results(query)
19
+ results = where("LOWER(#{table_name}.#{autocomplete_attribute}) LIKE ?", "#{query.downcase}%").
20
+ order("#{table_name}.#{autocomplete_attribute} ASC")
21
+ results.map do |record|
22
+ _autocomplete_format_result(record)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def _autocomplete_format_result(record)
29
+ if configured_autocomplete_result_formatter?
30
+ activeadmin_associations_config.autocomplete_result_formatter.call(record,
31
+ autocomplete_attribute, autocomplete_options)
32
+ else
33
+ label = _format_autocomplete_label(record)
34
+ {"label" => label, # This plays nice with both jQuery UI autocomplete and jquery.tokeninput
35
+ "value" => record.send(autocomplete_attribute),
36
+ "id" => record.id}
37
+ end
38
+ end
39
+
40
+ def _format_autocomplete_label(record)
41
+ if autocomplete_options[:format_label].present?
42
+ if autocomplete_options[:format_label].is_a?(Symbol)
43
+ return record.send(autocomplete_options[:format_label])
44
+ elsif autocomplete_options[:format_label].respond_to?(:call)
45
+ return autocomplete_options[:format_label].call(record)
46
+ end
47
+ end
48
+ record.send(autocomplete_attribute)
49
+ end
50
+
51
+ def configured_autocomplete_result_formatter?
52
+ activeadmin_associations_config.autocomplete_result_formatter.present? &&
53
+ activeadmin_associations_config.autocomplete_result_formatter.respond_to?(:call)
54
+ end
55
+
56
+ def activeadmin_associations_config
57
+ Rails.application.config.activeadmin_associations
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,23 @@
1
+ module ActiveAdminAssociations
2
+ class Engine < Rails::Engine
3
+ config.activeadmin_associations = ActiveSupport::OrderedOptions.new
4
+
5
+ initializer "active_admin_associations.load_extensions" do |app|
6
+ ActiveAdmin::BaseController.helper ActiveAdminAssociationsHelper
7
+ ActiveAdmin::ResourceDSL.send(:include, ActiveAdminAssociations::AssociationActions)
8
+ ActiveAdmin::ResourceDSL.send(:include, ActiveAdminAssociations::FormConfigDSL)
9
+
10
+ unless app.config.activeadmin_associations.destroy_redirect == false
11
+ ActiveAdmin::BaseController.send(:include, ActiveAdminAssociations::RedirectDestroyActions)
12
+ end
13
+
14
+ unless app.config.activeadmin_associations.autocomplete == false
15
+ ActiveSupport.on_load(:active_record) do
16
+ ActiveRecord::Base.send(:include, ActiveAdminAssociations::Autocompleter)
17
+ end
18
+
19
+ Formtastic::Helpers::InputHelper.send(:include, Formtastic::TokenInputDefaultForAssociation)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveAdminAssociations
2
+ module FormConfigDSL
3
+ def active_association_form(&block)
4
+ config.active_association_form = block
5
+ end
6
+
7
+ def form_columns(*column_names)
8
+ config.form_columns = column_names.flatten
9
+ end
10
+
11
+ def form_associations(&block)
12
+ config.form_associations = AssociationConfig.new(&block)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveAdminAssociations
2
+ module RedirectDestroyActions
3
+ def destroy
4
+ destroy!{ request.headers["Referer"].presence || admin_dashboard_url }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveAdminAssociations
2
+ VERSION = "0.1.3"
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'active_support'
2
+ require 'activeadmin'
3
+ require 'active_admin_associations/version'
4
+ require 'active_admin_associations/active_admin_extensions'
5
+ require 'formtastic/token_input_default_for_association'
6
+ require 'formtastic/inputs/token_input'
7
+
8
+ module ActiveAdminAssociations
9
+ extend ActiveSupport::Autoload
10
+
11
+ eager_autoload do
12
+ autoload :AssociationActions
13
+ autoload :FormConfigDSL
14
+ autoload :RedirectDestroyActions
15
+ autoload :Autocompleter
16
+ autoload :AssociationConfig
17
+ end
18
+ end
19
+
20
+ require 'active_admin_associations/engine'
@@ -0,0 +1,43 @@
1
+ module Formtastic
2
+ module Inputs
3
+ class TokenInput
4
+ include Base
5
+
6
+ def to_html
7
+ input_wrapping do
8
+ label_html <<
9
+ builder.hidden_field(input_name, input_html_options)
10
+ end
11
+ end
12
+
13
+ def input_html_options
14
+ super.merge({
15
+ :required => nil,
16
+ :autofocus => nil,
17
+ :class => 'token-input',
18
+ 'data-model-name' => reflection.klass.model_name.singular
19
+ }).tap do |html_options|
20
+ if record.present?
21
+ html_options["data-pre"] = prepopulated_value.to_json
22
+ end
23
+ end
24
+ end
25
+
26
+ def prepopulated_value
27
+ [{"value" => name_value, "id" => record.id}]
28
+ end
29
+
30
+ def name_method
31
+ builder.collection_label_methods.find { |m| record.respond_to?(m) }
32
+ end
33
+
34
+ def name_value
35
+ record.send(name_method)
36
+ end
37
+
38
+ def record
39
+ @object.send(method)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module Formtastic
2
+ module TokenInputDefaultForAssociation
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias_method_chain :default_input_type, :token_default_for_association
7
+ end
8
+
9
+ def default_input_type_with_token_default_for_association(method, options = {})
10
+ if @object
11
+ reflection = reflection_for(method)
12
+ if reflection && reflection.klass.respond_to?(:autocomplete_attribute) && reflection.macro == :belongs_to
13
+ return :token
14
+ end
15
+ end
16
+ default_input_type_without_token_default_for_association(method, options)
17
+ end
18
+ end
19
+ end