0

RailsアプリにはUserHikingtrailPhotoの3つのモデルがあります。user_idユーザーが写真を作成するときに、新しい写真の外部キーを設定したいと思います。

  1. ユーザー has_many :hikingtrails&has_many :photos
  2. ハイキングコース has_many :photosbelongs_to :user
  3. 写真 belongs_to :hikingtrailbelongs_to :user

Hikingtrail を作成するためのフォームは非常に長いため、2 つの部分に分割しました。最初の部分 ( new.html.erb) では、ユーザーが Hikingtrail を送信/作成した後、検証が必要なデータ (つまり、名前と住所) を入力するだけです。アプリedit.html.erbは、ユーザーがハイキングトレイルを詳細情報で更新し、写真をアップロードできる場所にそれらをルーティングします。

ユーザーがログインすると、Hikingtrail を作成できます。これを行うとuser_id、Hikingtrail モデルに外部キーが設定されます。@hikingtrail.user = current_userHikingtrails Controller の create アクションを追加することで、これを実現できました。

user_idまた、ユーザーが Hikingtrail を更新しようとしたときに、新しい写真に外部キーを設定したいと思います。すべての写真属性が Hikingtrail 内にネストされているため、写真コントローラーはありません。以下のコードを Hikingtrails Controller の更新アクションに追加することで、上記と同様のアプローチを試みました。

@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 に答える