aa_associations 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/.gitignore +53 -0
  2. data/Gemfile +10 -0
  3. data/MIT_LICENSE.txt +20 -0
  4. data/README.md +213 -0
  5. data/Rakefile +17 -0
  6. data/aa_associations.gemspec +31 -0
  7. data/app/controllers/autocomplete_controller.rb +37 -0
  8. data/app/helpers/active_admin_associations_helper.rb +77 -0
  9. data/app/views/admin/shared/_add_to_association.html.erb +21 -0
  10. data/app/views/admin/shared/_association_collection_table_actions.html.erb +4 -0
  11. data/app/views/admin/shared/_blank_slate.html.erb +3 -0
  12. data/app/views/admin/shared/_collection_table.html.erb +58 -0
  13. data/app/views/admin/shared/_form.html.erb +7 -0
  14. data/config/routes.rb +7 -0
  15. data/lib/aa_associations.rb +20 -0
  16. data/lib/active_admin_associations/active_admin_extensions.rb +13 -0
  17. data/lib/active_admin_associations/association_actions.rb +43 -0
  18. data/lib/active_admin_associations/association_config.rb +50 -0
  19. data/lib/active_admin_associations/autocompleter.rb +64 -0
  20. data/lib/active_admin_associations/engine.rb +25 -0
  21. data/lib/active_admin_associations/form_config_dsl.rb +15 -0
  22. data/lib/active_admin_associations/redirect_destroy_actions.rb +7 -0
  23. data/lib/active_admin_associations/version.rb +3 -0
  24. data/lib/formtastic/inputs/token_input.rb +43 -0
  25. data/lib/formtastic/token_input_default_for_association.rb +19 -0
  26. data/test/active_admin_associations_test.rb +69 -0
  27. data/test/admin_posts_controller_test.rb +52 -0
  28. data/test/association_config_test.rb +43 -0
  29. data/test/autocomplete_controller_test.rb +27 -0
  30. data/test/autocompleter_test.rb +62 -0
  31. data/test/dummy/README.rdoc +261 -0
  32. data/test/dummy/Rakefile +7 -0
  33. data/test/dummy/app/admin/dashboards.rb +44 -0
  34. data/test/dummy/app/admin/posts.rb +20 -0
  35. data/test/dummy/app/admin/tags.rb +11 -0
  36. data/test/dummy/app/assets/javascripts/active_admin.js +8 -0
  37. data/test/dummy/app/assets/javascripts/application.js +15 -0
  38. data/test/dummy/app/assets/stylesheets/active_admin.css.scss +6 -0
  39. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  40. data/test/dummy/app/controllers/application_controller.rb +3 -0
  41. data/test/dummy/app/helpers/application_helper.rb +2 -0
  42. data/test/dummy/app/mailers/.gitkeep +0 -0
  43. data/test/dummy/app/models/.gitkeep +0 -0
  44. data/test/dummy/app/models/admin_user.rb +10 -0
  45. data/test/dummy/app/models/post.rb +11 -0
  46. data/test/dummy/app/models/tag.rb +15 -0
  47. data/test/dummy/app/models/tagging.rb +7 -0
  48. data/test/dummy/app/models/user.rb +8 -0
  49. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  50. data/test/dummy/config.ru +4 -0
  51. data/test/dummy/config/application.rb +63 -0
  52. data/test/dummy/config/boot.rb +10 -0
  53. data/test/dummy/config/database.yml +19 -0
  54. data/test/dummy/config/environment.rb +5 -0
  55. data/test/dummy/config/environments/development.rb +37 -0
  56. data/test/dummy/config/environments/production.rb +67 -0
  57. data/test/dummy/config/environments/test.rb +37 -0
  58. data/test/dummy/config/initializers/active_admin.rb +129 -0
  59. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/test/dummy/config/initializers/devise.rb +216 -0
  61. data/test/dummy/config/initializers/inflections.rb +15 -0
  62. data/test/dummy/config/initializers/mime_types.rb +5 -0
  63. data/test/dummy/config/initializers/secret_token.rb +7 -0
  64. data/test/dummy/config/initializers/session_store.rb +8 -0
  65. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  66. data/test/dummy/config/locales/devise.en.yml +57 -0
  67. data/test/dummy/config/locales/en.yml +5 -0
  68. data/test/dummy/config/routes.rb +62 -0
  69. data/test/dummy/db/.gitignore +1 -0
  70. data/test/dummy/db/migrate/20120504220404_devise_create_admin_users.rb +52 -0
  71. data/test/dummy/db/migrate/20120504221534_create_posts.rb +12 -0
  72. data/test/dummy/db/migrate/20120504221936_create_users.rb +9 -0
  73. data/test/dummy/db/migrate/20120504222040_create_tags.rb +8 -0
  74. data/test/dummy/db/migrate/20120504222247_create_taggings.rb +10 -0
  75. data/test/dummy/db/schema.rb +65 -0
  76. data/test/dummy/lib/assets/.gitkeep +0 -0
  77. data/test/dummy/public/404.html +26 -0
  78. data/test/dummy/public/422.html +26 -0
  79. data/test/dummy/public/500.html +25 -0
  80. data/test/dummy/public/favicon.ico +0 -0
  81. data/test/dummy/script/rails +6 -0
  82. data/test/dummy/test/factories/admin_users.rb +9 -0
  83. data/test/dummy/test/factories/posts.rb +11 -0
  84. data/test/dummy/test/factories/taggings.rb +6 -0
  85. data/test/dummy/test/factories/tags.rb +7 -0
  86. data/test/dummy/test/factories/users.rb +8 -0
  87. data/test/dummy/test/unit/admin_user_test.rb +7 -0
  88. data/test/dummy/test/unit/post_test.rb +7 -0
  89. data/test/dummy/test/unit/tag_test.rb +7 -0
  90. data/test/dummy/test/unit/tagging_test.rb +7 -0
  91. data/test/dummy/test/unit/user_test.rb +7 -0
  92. data/test/support/should_change.rb +29 -0
  93. data/test/test_helper.rb +51 -0
  94. data/vendor/assets/javascripts/active_admin_associations.js +14 -0
  95. data/vendor/assets/javascripts/jquery.tokeninput.js +915 -0
  96. data/vendor/assets/stylesheets/active_admin_associations.css.scss +18 -0
  97. data/vendor/assets/stylesheets/token-input-facebook.css +121 -0
  98. metadata +362 -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.aa_associations.autocomplete_models
2
+ models = Rails.application.config.aa_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,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,13 @@
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| n.resources.resources }.flatten.compact.map(&:resource_class)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveAdminAssociations
2
+ module AssociationActions
3
+ def association_actions
4
+ member_action :unrelate, :method => :put do
5
+ reflection = resource_class.reflect_on_association(params[:relationship_name].to_sym)
6
+ if reflection.collection?
7
+ related_record = reflection.klass.find(params[:related_id])
8
+ resource.send(params[:relationship_name]).delete(related_record)
9
+ else
10
+ resource.update_attribute("#{params[:relationship_name]}_id", nil)
11
+ end
12
+ flash[:notice] = "The recored has been unrelated."
13
+ redirect_to request.headers["Referer"].presence || admin_dashboard_url
14
+ end
15
+
16
+ member_action :relate, :method => :put do
17
+ reflection = resource_class.reflect_on_association(params[:relationship_name].to_sym)
18
+ if reflection.collection?
19
+ record_to_relate = reflection.klass.find(params[:related_id])
20
+ resource.send(params[:relationship_name]) << record_to_relate
21
+ else
22
+ resource.update_attribute("#{params[:relationship_name]}_id", record_to_relate)
23
+ end
24
+ flash[:notice] = "The recored has been related."
25
+ redirect_to request.headers["Referer"].presence || admin_dashboard_url
26
+ end
27
+
28
+ member_action :page_related, :method => :get do
29
+ relationship_name = params[:relationship_name].to_sym
30
+ association_config = active_admin_config.form_associations[relationship_name]
31
+ relationship_class = resource_class.reflect_on_association(relationship_name).klass
32
+ association_columns = association_config.fields.presence || relationship_class.content_columns
33
+ render :partial => 'admin/shared/collection_table', :locals => {
34
+ :object => resource,
35
+ :collection => resource.send(relationship_name).page(params[:page]),
36
+ :relationship => relationship_name,
37
+ :columns => association_columns,
38
+ :relationship_class => relationship_class
39
+ }
40
+ end
41
+ end
42
+ end
43
+ 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,64 @@
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
+ aa_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
+ record.send(autocomplete_options[:format_label])
44
+ elsif autocomplete_options[:format_label].respond_to?(:call)
45
+ autocomplete_options[:format_label].call(record)
46
+ else
47
+ record.send(autocomplete_attribute)
48
+ end
49
+ else
50
+ record.send(autocomplete_attribute)
51
+ end
52
+ end
53
+
54
+ def configured_autocomplete_result_formatter?
55
+ aa_associations_config.autocomplete_result_formatter.present? &&
56
+ aa_associations_config.autocomplete_result_formatter.respond_to?(:call)
57
+ end
58
+
59
+ def aa_associations_config
60
+ Rails.application.config.aa_associations
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveAdminAssociations
2
+ class Engine < Rails::Engine
3
+ config.aa_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.aa_associations.destroy_redirect == false
11
+ ActiveAdmin::BaseController.send(:include, ActiveAdminAssociations::RedirectDestroyActions)
12
+ end
13
+
14
+ unless app.config.aa_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
+
22
+ ActiveAdmin.application.register_stylesheet 'active_admin_associations'
23
+ end
24
+ end
25
+ 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.0"
3
+ end
@@ -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
@@ -0,0 +1,69 @@
1
+ require 'test_helper'
2
+
3
+ class ActiveAdminAssociationsTest < ActionDispatch::IntegrationTest
4
+ setup do
5
+ admin_login_as
6
+ @post = Factory(:post)
7
+ @tag = Factory(:tag)
8
+ @post.tags = [@tag]
9
+ end
10
+
11
+ context 'editing a post' do
12
+ setup do
13
+ visit "/admin/posts/#{@post.id}/edit"
14
+ end
15
+
16
+ should 'have correct inputs from form_columns config' do
17
+ assert page.has_selector?('form.post fieldset.inputs input#post_title'), 'no input for Post title'
18
+ assert page.has_selector?('form.post fieldset.inputs textarea#post_body'), 'no input for Post body'
19
+ assert page.has_selector?('form.post fieldset.inputs input#post_creator_id'), 'no input for Post creator'
20
+ end
21
+
22
+ should 'have correct inputs from active_association_form config' do
23
+ assert page.has_selector?('form.post fieldset#more-inputs input.my-date-picker#post_published_at'), 'no input for Post published_at'
24
+ assert page.has_selector?('form.post fieldset#more-inputs input#post_featured'), 'no input for Post featured'
25
+ end
26
+
27
+ should 'have correct token input for post creator' do
28
+ token_input = page.find('form.post fieldset.inputs input.token-input#post_creator_id')
29
+ assert_equal 'hidden', token_input["type"]
30
+ assert_equal 'user', token_input['data-model-name']
31
+ assert_equal '1', token_input['value']
32
+ assert_equal [{"value" => "Bill Tester", "id" => @post.creator_id}], MultiJson.decode(token_input['data-pre'])
33
+ end
34
+
35
+ should 'have a form to relate new tags' do
36
+ assert page.has_selector?('.relationship-table#relationship-table-tags form.relate-to-form')
37
+ assert page.has_selector?('#relationship-table-tags form.relate-to-form input[type="hidden"]#relationship_name')
38
+ assert_equal 'tags', page.find('#relationship-table-tags form.relate-to-form input#relationship_name')['value']
39
+ token_input = page.find('#relationship-table-tags form.relate-to-form input.token-input#related_id')
40
+ assert_equal 'tag', token_input['data-model-name']
41
+ end
42
+
43
+ should 'have a table of the related tags' do
44
+ assert page.has_xpath?('//div[@id="relationship-table-tags"]//table[@class="index_table"]/thead//th[text()="Name"]')
45
+ assert_equal @tag.name, page.find(:xpath, "//div[@id='relationship-table-tags']//table[@class='index_table']/tbody//tr[td[1][text()=#{@tag.id}]]//td[2]").text
46
+ edit_link = page.find(:xpath, "//div[@id='relationship-table-tags']//table[@class='index_table']/tbody//tr/td[3]/a[text()='Edit']")
47
+ assert_match %r{\A/admin/tags/\d+/edit}, edit_link['href']
48
+
49
+ assert page.has_xpath?("//div[@id='relationship-table-tags']//table[@class='index_table']/tbody//tr/td[3]/form//input[@type='submit'][@value='Unrelate']")
50
+ unrelate_form = page.find(:xpath, "//div[@id='relationship-table-tags']//table[@class='index_table']/tbody//tr/td[3]/form[@class='button_to']")
51
+ unrelate_url = unrelate_form['action']
52
+ assert_match %r{\A/admin/posts/#{@post.id}/unrelate}, unrelate_url
53
+ query_params = unrelate_url.split('?')[1].split('&')
54
+ assert_same_elements ["related_id=#{@tag.id}", 'relationship_name=tags'], query_params
55
+ end
56
+ end
57
+
58
+ context 'editing a tag' do
59
+ setup do
60
+ visit "/admin/tags/#{@tag.id}/edit"
61
+ end
62
+
63
+ should 'have a table of the related posts' do
64
+ assert page.has_xpath?('//div[@id="relationship-table-posts"]//table[@class="index_table"]/thead//th[text()="Title"]')
65
+ assert_equal @post.title, page.find(:xpath, "//div[@id='relationship-table-posts']//table[@class='index_table']/tbody//tr[td[1][text()=#{@post.id}]]//td[2]").text
66
+ assert_equal @post.creator.name, page.find(:xpath, "//div[@id='relationship-table-posts']//table[@class='index_table']/tbody//tr[td[1][text()=#{@post.id}]]//td[3]").text
67
+ end
68
+ end
69
+ end