问题
I have a RoR app that allows users to tag items in their collections. I use the tag-it.js Jquery plugin and use Ajax calls to add and remove the tags in the ItemsController. My problem is that each tag is added twice so that when I do @item.tags.each, all the tags are shown twice.
ItemsController:
def add_tag
@collection = current_user.collections.find(params[:collection_id])
@item = @collection.items.find(params[:id])
@item.tag_list.add(params[:tag])
current_user.tag(@item, :with => @item.tag_list.to_s, :on => :tags)
@item.save
render nothing: true
end
def remove_tag
@item = current_user.items.find_by_id(params[:id])
@item.tag_list.remove(params[:tag])
current_user.tag(@item, :with => @item.tag_list.to_s, :on => :tags)
@item.save
render nothing: true
end
Javascript that handles the AJAX tagging with Tag-it.js:
$('#item_tags').tagit({
onTagAdded: function(event, tag) {
var add_url = $('#item_tags').attr("data-add-url");
$.ajax({
url: add_url,
data: {tag: tag.text().substring(0, tag.text().length-1)},
})
},
onTagRemoved: function(event, tag) {
var remove_url = $('#item_tags').attr("data-remove-url");
$.ajax({
url: remove_url,
type: 'DELETE',
data: {tag: tag.text().substring(0, tag.text().length-1)},
})
},
tagSource: function(search, showChoices) {
var autocomplete_url = $('#item_tags').attr("data-auctocomplete-url");
$.ajax({
url: autocomplete_url,
data: {term: search.term},
success: function(choices) {
showChoices(choices);
}
})
}
});
item#_form view where the user adds / removes tags:
<ul id='item_tags' class='tagit' data-remove-url="<%= remove_tag_collection_item_path %>" data-add-url="<%= add_tag_collection_item_path %>" data-auctocomplete-url="/collections/<%=@collection.id %>/items/autocomplete_tag_name">
<% @item.tags.each do |tag| %>
<li><%= tag.name %></li>
<% end %>
</ul>
I must note that it is necessary to have tag ownership (by current_user) so that the Jquery auto complete only completes based on current user's previous tags and not all users. I think the problem is that I have to add the tag to the tag_list and then add the tag_list to the user item tagging. I can't find a way around this because the current_user.tag() method seems to overwrite the previous item tags when current_user.tag() is called so I have to add the new tag to the previous tags to preserve them.
Additionally when I submit the item#_form, I need to somehow have the update method ignore the tags attribute because it's trying to save them to the item but they're already saved with an AJAX call so I get this error:
ActiveRecord::AssociationTypeMismatch in ItemsController#update
ActsAsTaggableOn::Tag(#82082080) expected, got String(#72294010)
Thanks in advance.
PS. Here is how I got the auto complete working in the ItemsController:
def get_autocomplete_items(parameters)
tags = current_user.owned_tags.named_like(parameters[:term])
end
回答1:
You were right:
I think the problem is that I have to add the tag to the tag_list and then add the tag_list to the user item tagging. I can't find a way around this because the current_user.tag() method seems to overwrite the previous item tags when current_user.tag() is called so I have to add the new tag to the previous tags to preserve them.
The .tag
method over-writes existing tags with the given list. So if you want to add new tags it seems you need to append your new tags to the existing tags, and then pass in that new list. However, .tag_list.add
actually also creates tags.
So, when you were doing this:
@item.tag_list.add(params[:tag])
current_user.tag(@item, :with => @item.tag_list.to_s, :on => :tags)
You were indeed adding the new tag twice.
And when I was doing this:
tag_list = profile.tag_list // oops!
tag_list.add(tags)
self.tag(profile, with: tag_list, on: :tags)
I was creating a reference to the tag_list, and then calling add on it. What we needed to do was this:
tag_list = profile.tags.map(&:name)
To make an array of the tag names for the object we are tagging. Then we can call add on the copy of the list, no problem! No more duplicate tags.
I'm glad I encountered your question, as it led me to an answer that worked for me. However, I would be even more pleased if there was just a nice way of doing this provided by the library. I wasn't able to find a good way just by reading the docs.
回答2:
You were doing all right, but, just one little mistake.
Here tag_list
is getting duplicate tags, one with Owner and other without Owner
Your @item.tag_list.add(params[:tag])
line is adding tags without Owner
And current_user.tag(@item, :with => @item.tag_list.to_s, :on => :tags)
line is adding same tag with Owner
And later, when you save the object, since tag_list
is behaving as reference to un-ownered tags of object, both get saved
Following code shall work fine.
def add_tag
@collection = current_user.collections.find(params[:collection_id])
@item = @collection.items.find(params[:id])
tag_list = @item.all_tag_list.dup # Reference to original tag_list avoided
# Also instead of "tag_list", use "all_tag_list" method, as "tag_list" only return tags without owner
# Link(go to last line og Tag Ownership topic) : "https://github.com/mbleigh/acts-as-taggable-on#tag-ownership"
tag_list.add(params[:tag])
current_user.tag(@item, :with => tag_list.to_s, :on => :tags)
@item.save
render nothing: true
end
def remove_tag
@item = current_user.items.find_by_id(params[:id])
tag_list = @item.all_tag_list.dup # Reference to original tag_list avoided
tag_list.remove(params[:tag])
current_user.tag(@item, :with => tag_list.to_s, :on => :tags)
@item.save
render nothing: true
end
IMPROVED VERSION
def add_owned_tag
@some_item = Item.find(params[:id])
owned_tag_list = @some_item.all_tag_list - @some_item.tag_list
owned_tag_list += [(params[:tag])]
@tag_owner.tag(@some_item, :with => stringify(owned_tag_list), :on => :tags)
@some_item.save
end
def stringify(tag_list)
tag_list.inject('') { |memo, tag| memo += (tag + ',') }[0..-1]
end
def remove_owned_tag
@some_item = Item.find(params[:id])
owned_tag_list = @some_item.all_tag_list - @some_item.tag_list
owned_tag_list -= [(params[:tag])]
@tag_owner.tag(@some_item, :with => stringify(owned_tag_list), :on => :tags)
@some_item.save
end
回答3:
Take care with onTagAdded event fired on page load. See events sample here http://aehlke.github.com/tag-it/examples.html
//-------------------------------
// Tag events
//-------------------------------
var eventTags = $('#eventTags');
eventTags.tagit({
availableTags: sampleTags,
onTagRemoved: function(evt, tag) {
console.log(evt);
alert('This tag is being removed: ' + eventTags.tagit('tagLabel', tag));
},
onTagClicked: function(evt, tag) {
console.log(tag);
alert('This tag was clicked: ' + eventTags.tagit('tagLabel', tag));
}
}).tagit('option', 'onTagAdded', function(evt, tag) {
// Add this callbackafter we initialize the widget,
// so that onTagAdded doesn't get called on page load.
alert('This tag is being added: ' + eventTags.tagit('tagLabel', tag));
});
来源:https://stackoverflow.com/questions/10725048/acts-as-taggable-on-tags-added-twice