Update owner tags via form

2019-01-25 13:55发布

I would like to uniquely use owner tags in my app. My problem is that when I create / update a post via a form I only have f.text_field :tag_list which only updates the tags for the post but has no owner. If I use f.text_field :all_tags_list it doesn't know the attribute on create / update. I could add in my controller:

User.find(:first).tag( @post, :with => params[:post][:tag_list], :on => :tags )

but then I have duplicate tags, for post and for the owner tags. How can I just work with owner tags?

7条回答
放荡不羁爱自由
2楼-- · 2019-01-25 14:06

Try using delegation:

class User < ActiveRecord::Base
  acts_as_taggable_on
end

class Post < ActiveRecord::Base
  delegate :tag_list, :tag_list=, :to => :user
end

So when you save your posts it sets the tag on the user object directly.

查看更多
相关推荐>>
3楼-- · 2019-01-25 14:11

the set_tag_owner before_save worked for me. But as bcb mentioned, I had to add a condition (tag_list_changed?) to prevent the tags from being deleted on update:

def set_tag_owner
  if tag_list_changed?
    set_owner_tag_list_on(account, :tags, tag_list)
    self.tag_list = nil
  end
end
查看更多
放我归山
4楼-- · 2019-01-25 14:12

When working with ownership the taggable model gets its tags a little different. Without ownership it can get its tags like so:

@photo.tag_list << 'a tag' # adds a tag to the existing list
@photo.tag_list = 'a tag' # sets 'a tag' to be the tag of the @post

However, both of these opperations create taggins, whose tagger_id and tagger_type are nil.

In order to have these fields set, you have to use this method:

@user.tag(@photo, on: :tags, with: 'a tag')

Suppose you add this line to the create/update actions of your PhotosController:

@user.tag(@photo, on: :tags, with: params[:photo][:tag_list])

This will create two taggings (one with and one without tagger_id/_type), because params[:photo][:tag_list] is already included in photo_params. So in order to avoid that, just do not whitelist :tag_list.

For Rails 3 - remove :tag_list from attr_accessible.

For Rails 4 - remove :tag_list from params.require(:photo).permit(:tag_list).

At the end your create action might look like this:

def create
  @photo = Photo.new(photo_params) # at this point @photo will not have any tags, because :tag_list is not whitelisted
  current_user.tag(@photo, on: :tags, with: params[:photo][:tag_list])

  if @photo.save
    redirect_to @photo
  else
    render :new
  end
end

Also note that when tagging objects this way you cannot use the usual tag_list method to retrieve the tags of a photo, because it searches for taggings, where tagger_id IS NULL. You have to use instead

@photo.tags_from(@user)

In case your taggable object belongs_to a single user you can also user all_tags_list.

查看更多
时光不老,我们不散
5楼-- · 2019-01-25 14:13

Late to the party, but I found guillaume06's solution worked well, and I added some additional functionality to it:

What this will enable: You will be able to specify the tag owner by the name of the relationship between the tagged model and the tag owner model.

How: write a module and include in your lib on initialization (require 'lib/path/to/tagger'):

  module Giga::Tagger
    extend ActiveSupport::Concern
    included do
      def self.tagger owner
        before_save :set_tag_owner
        def set_tag_owner
          self.tag_types.each do |tag|
            tag_type = tag.to_s
            # Set the owner of some tags based on the current tag_list
            set_owner_tag_list_on(owner, :"#{tag_type}", self.send(:"#{tag_type.chop}_list"))
            # Clear the list so we don't get duplicate taggings
            self.send(:"#{tag_type.chop}_list=",nil)
          end
        end

      end
    end
  end

Usage Instructions:

  Given: A model, Post, that is taggable
         A model, User, that is the tag owner
         A post is owned by the user through a relationship called :owner
  Then add to Post.rb:
         include Tagger
         acts_as_taggable_on :skills, :interests, :tags
         tagger :owner
  Make sure Post.rb already has called acts_as_taggable_on, and that User.rb has acts_as_tagger
  Note: This supports multiple tag contexts, not just tags (eg skills, interests)..
查看更多
我欲成王,谁敢阻挡
6楼-- · 2019-01-25 14:16

I ended up creating a virtual attribute that runs the User.tag statement:

In my thing.rb Model:

attr_accessible :tags
belongs_to :user
acts_as_taggable

def tags
    self.all_tags_list
end

def tags=(tags)
    user = User.find(self.user_id)
    user.tag(self, :with => tags, :on => :tags, :skip_save => true)
end

The only thing you have to do is then change your views and controllers to update the tag_list to tags and make sure you set the user_id of the thing before the tags of the thing.

查看更多
Explosion°爆炸
7楼-- · 2019-01-25 14:18

I used an observer to solve this. Something like:

in /app/models/tagging_observer.rb

class TaggingObserver < ActiveRecord::Observer
  observe ActsAsTaggableOn::Tagging

  def before_save(tagging)
    tagging.tagger = tagging.taggable.user if (tagging.taggable.respond_to?(:user) and tagging.tagger != tagging.taggable.user)
  end
end

Don't forget to declare your observer in application.rb

config.active_record.observers = :tagging_observer
查看更多
登录 后发表回答