0

我在 Rails 应用程序中有三个模型,用户徒步旅行照片。我想user_id在用户创建新照片时为新照片设置外键。

  1. 每个用户 has_many :hikingtrails&has_many :photos
  2. 每条远足径 has_many :photosbelongs_to :user
  3. 每张照片 belongs_to :hikingtrail&belongs_to :user

我已将用于创建 Hikingtrail 的表单分成两部分,因为它很长,第一部分 ( new.html.erb) 只需要用户在用户提交/创建 Hikingtrail 后输入需要验证的数据(即姓名和地址)应用程序将它们路由到edit.html.erb用户可以使用更多信息更新 Hikingtrail 并上传照片的位置。

用户登录后,他们可以创建 Hikingtrail,当他们这样做时,它会user_id在 Hikingtrail 模型中设置外键。我可以通过添加@hikingtrail.user = current_userHikingtrails 控制器的创建操作来实现这一点。

user_id当用户尝试更新 Hikingtrail 时,我还想在新照片中设置外键。没有照片控制器,因为所有照片属性都嵌套在 Hikingtrail 中。通过将以下代码添加到 Hikingtrails 控制器的更新操作中,我尝试了与上述类似的方法:

@photo = Photo.new(params[:photo])
@photo.user_id = current_user.id

&

@photo = Hikingtrail.photo.new(params[:photo])
@photo.user_id = current_user.id

但它要么添加照片而不在数据库中设置 user_id 外键,要么我得到一个undefined method 'photo'错误。

- - - 编辑 - - -

我也尝试过添加

@photo = @hikingtrail.photos
@photo.user_id = current_user.id

到我的远足径控制器,但这给了我一个undefined method 'user_id='错误。

-- 结束编辑 --

这是我的其余代码:

用户模型

class User < ActiveRecord::Base
  attr_accessible :user_name, :email, :password, :password_confirmation, :photos_attributes, :hikingtrails_attributes

  validates :email, :presence => true, :uniqueness => true, :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i } #, :message => "Invalid email address format"
  validates :user_name, :presence => true

  # has_secure_password automatically validates presence of password fields
  # validates :password, :presence => true
  # validates :password_confirmation, :presence => true
  has_secure_password

  has_many :photos
  accepts_nested_attributes_for :photos, :allow_destroy => :true, :reject_if => lambda { |a| a[:image].blank? }

  has_many :hikingtrails
  accepts_nested_attributes_for :hikingtrails, :allow_destroy => :true, :reject_if => :all_blank

  before_create { generate_token(:auth_token) } # calls the generate_token method below to store unique token in :auth_token field

  def send_password_reset
    generate_token(:password_reset_token) # uses method below to generate a unique token
    self.password_reset_sent_at = Time.zone.now # sets the time token was generated
    save! # save to database
    UserMailer.password_reset(self).deliver # calls user mailer to send email with instructions
  end  

  # generates a unique token which is unguessable for each user
  def generate_token(column) # take a column argument so that we can have multiple tokens
    begin
      self[column] = SecureRandom.urlsafe_base64 # ActiveSupport’s SecureRandom class generates a random string
    end while User.exists?(column => self[column]) # checks that no other user exists with this token and repeatedly generates another random token while this is true
  end

end

远足径模型

class Hikingtrail < ActiveRecord::Base
  attr_accessible :description, 
                                  :duration_hours, 
                                  :duration_mins, 
                                  :name, 
                                  :looped,
                                  :addr_1,
                                  :addr_2,
                                  :addr_3,
                                  :country,
                                  :latitude,
                                  :longitude,
                                  :photos_attributes,
                                  :trails_attributes,
                                  :directions_attributes,
                                  :user_id

    validates :name,  :presence => {message:"Name can't be blank."}, :length => { :minimum => 3, message:"Name is too short (minimum is 3 characters)" } 
    validates :addr_2,  :presence => {message:"Address field can't be blank."}
    validates :addr_3,  :presence => {message:"Address field can't be blank."}
    validates :country,  :presence => {message:"Country field can't be blank."}
    validates :description, :length => { :minimum => 50, message:"Description is too short (minimum is 50 characters)" }, :allow_blank => true

    validates :latitude, :presence => {message: "Not a valid location on Google Maps, please check name address & country fields" }

    has_many :photos
    has_many :trails
    has_many :directions
    belongs_to :user

    accepts_nested_attributes_for :photos, :allow_destroy => :true,
    :reject_if => lambda { |a| a[:image].blank? }

    accepts_nested_attributes_for :trails, :allow_destroy => :true,
    :reject_if => lambda { |a| a[:step].blank? }

    accepts_nested_attributes_for :directions, :allow_destroy => :true,
    :reject_if => :all_blank

    geocoded_by :address
    before_validation :geocode, :if => :address_changed?

  def address
    [name, addr_1, addr_2, addr_3, country].compact.join(' ')
    end

    def address_changed?
      attrs = %w(name addr_1 addr_2 addr_3 country)
      attrs.any?{|a| send "#{a}_changed?"}
    end

    # Full text search method utilizing PostgreSQL search functionality for stemming  & ommitting stop words
    # http://www.postgresql.org/docs/9.1/static/textsearch.html
    def self.text_search(query)
      if query.present?
        # sets a rank variable to the value of the sum of the ranks for the name and description fields. So that the results are ordered by that rank in descending order.
        rank = <<-RANK
        ts_rank(to_tsvector(name), plainto_tsquery(#{sanitize(query)})) +
        ts_rank(to_tsvector(description), plainto_tsquery(#{sanitize(query)}))
        RANK
        where("name @@ :q or description @@ :q", q: query).order("#{rank} desc") # uses the PG @@ operater as opposed to ilike operator
      else
        scoped
      end
    end

end

照片模型

class Photo < ActiveRecord::Base
  belongs_to :hikingtrail
  belongs_to :user

  attr_accessible :image, :user_id

  mount_uploader :image, ImageUploader
end

远足径控制器

    class HikingtrailsController < ApplicationController
      before_filter :authorize, only: [:new, :destroy]

      # GET /hikingtrails
      # GET /hikingtrails.json
      def index
        if params[:search_nearby].present?
          @hikingtrails = Hikingtrail.near(params[:search_nearby], 50, :order => :distance)
          @hikingtrails = @hikingtrails.page(params[:page]).per_page(2)
        elsif params[:query].present?
          @hikingtrails = Hikingtrail.text_search(params[:query]).page(params[:page]).per_page(3)        
        else
          @hikingtrails = Hikingtrail.order("created_at DESC").page(params[:page]).per_page(4)
        end

        respond_to do |format|
          format.html # index.html.erb
          format.json { render json: @hikingtrails }
        end
      end

      # GET /hikingtrails/1
      # GET /hikingtrails/1.json
      def show
        @hikingtrail = Hikingtrail.find(params[:id])

        respond_to do |format|
          format.html # show.html.erb
          format.json { render json: @hikingtrail }
        end
      end

      # GET /hikingtrails/new
      # GET /hikingtrails/new.json
      def new
        @hikingtrail = Hikingtrail.new

        respond_to do |format|
          format.html # new.html.erb
          format.json { render json: @hikingtrail }
        end
      end

      # GET /hikingtrails/1/edit
      def edit
        @hikingtrail = Hikingtrail.find(params[:id])
      end

      # POST /hikingtrails
      # POST /hikingtrails.json
      def create
        @hikingtrail = Hikingtrail.new(params[:hikingtrail])
        @hikingtrail.user = current_user

        respond_to do |format|
          if @hikingtrail.save
            #format.html { redirect_to @hikingtrail, notice: 'Hikingtrail was successfully created.' }
            format.html { redirect_to edit_hikingtrail_path(@hikingtrail), notice: 'Hikingtrail was successfully created.' }        
            format.json { render json: @hikingtrail, status: :created, location: @hikingtrail }
          else
            format.html { render action: "new" }
            format.json { render json: @hikingtrail.errors, status: :unprocessable_entity }
          end
        end
      end

      # PUT /hikingtrails/1
      # PUT /hikingtrails/1.json
      def update
        @hikingtrail = Hikingtrail.find(params[:id])
        @photo = Photo.new(params[:photo])
        @photo.user_id = current_user.id

        respond_to do |format|
          if @hikingtrail.update_attributes(params[:hikingtrail])
            format.html { redirect_to @hikingtrail, notice: 'Hikingtrail was successfully updated.' }
            format.json { head :no_content }
          else
            format.html { render action: "edit" }
            format.json { render json: @hikingtrail.errors, status: :unprocessable_entity }
          end
        end
      end

      # DELETE /hikingtrails/1
      # DELETE /hikingtrails/1.json
      def destroy
        @hikingtrail = Hikingtrail.find(params[:id])
        @hikingtrail.destroy

        respond_to do |format|
          format.html { redirect_to hikingtrails_url }
          format.json { head :no_content }
        end
      end
    end

徒步旅行/_form.html.erb

<% @hikingtrail.photos.build %>
<% @hikingtrail.trails.build %>
<% @hikingtrail.directions.build %>

<%= simple_form_for @hikingtrail, :html => { :class => 'form-horizontal' } do |f| %>
  <% if @hikingtrail.errors.any? %>
            <div class="alert">
              <a class="close" data-dismiss="alert">×</a> Please correct the
      <%= pluralize(@hikingtrail.errors.count, "error") %> below.  
      <ol>
      <% @hikingtrail.errors.each do |attribute, errors_array| %> 
        <li><%= errors_array %></li>
      <% end %>
      </ol>
    </div>
  <% end %>

<div class="accordion" id="accordion2">
  <div class="accordion-group">
    <div class="accordion-heading">
      <a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseOne">
        Name &amp; Location
      </a>
    </div>
    <div id="collapseOne" class="accordion-body collapse">
      <div class="accordion-inner">

        <%= render :partial => 'new_form' %>

      </div>
    </div>
  </div>

    <div class="accordion-group">
    <div class="accordion-heading">
      <a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseTwo">
        Description
      </a>
    </div>
    <div id="collapseTwo" class="accordion-body collapse in">
      <div class="accordion-inner">

        <%= f.input :description, :input_html => { :cols => 10, :rows => 3 } %> <br/>

        <%= f.input :looped, :as => :boolean %> 

        <%= f.input :duration_hours, :label => 'Duration',  :collection => 0..12, :include_blank => false, :hint => "hours" %>

        <%= f.input :duration_mins, collection: [ 0, 15, 30, 45 ], :include_blank => false, label: false, :hint => "mins" %>
      </div>
    </div>
  </div>


  <div class="accordion-group">
    <div class="accordion-heading">
      <a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseThree">
        Images
      </a>
    </div>
    <div id="collapseThree" class="accordion-body collapse">
      <div class="accordion-inner">
        <% if current_user %>
          <%= f.simple_fields_for :photos do |builder| %>
            <%= render 'photo_fields', f: builder %>
          <% end %>
          <div style=clear:both;> </div>
          <%= link_to_add_fields "Add More", f, :photos %>
        <% else %>
          You must <%= link_to "Log In", login_path %> before you can upload photos.
        <% end %>
      </div>
    </div>
  </div>


  <div class="accordion-group">
    <div class="accordion-heading">
      <a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseFour">
        Hiking Trail Route
      </a>
    </div>
    <div id="collapseFour" class="accordion-body collapse">
      <div class="accordion-inner">
        <%= f.simple_fields_for :trails do |builder| %>
          <%= render 'trail_fields', f: builder %>
        <% end %>
        <%= link_to_add_fields "Add Step", f, :trails %>
      </div>
    </div>
  </div>

  <div class="accordion-group">
    <div class="accordion-heading">
      <a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseFive">
        Directions to The Hike
      </a>
    </div>
    <div id="collapseFive" class="accordion-body collapse">
      <div class="accordion-inner">
        <%= f.simple_fields_for :directions do |builder| %>
          <%= render 'direction_fields', f: builder %>
        <% end %>
        <%= link_to_add_fields "Add More Directions", f, :directions %>
      </div>
    </div>
  </div>

</div>

  <div class="form-actions">
    <%= f.submit nil, :class => 'btn btn-primary' %>
    <%= link_to t('.cancel', :default => t("helpers.links.cancel")),
                hikingtrails_path, :class => 'btn' %>
  </div>
<% end %>
4

2 回答 2

1

您需要使用构建方法

@user=current_user
@photo= @user.photos.build(params[:photo])
@photo.save
于 2013-04-11T10:25:35.560 回答
0

我通过不同的方法解决了这个问题。

我在嵌套的 photo_fields 表单中使用了一个隐藏字段来设置我在 Photo 模型中设置的 user_id 属性:

<%= f.hidden_field :user_id, :value => current_user.id %>

因此,当用户创建一个新的远足小径时,他们的 user_id 已与表单一起提交并更新了照片属性。

我不确定这是否是最好的方法,但它有效。如果有更多 Rails 经验的人可以对此解决方案发表评论,我会很感兴趣。

于 2013-04-11T13:53:42.390 回答