我在 Rails 应用程序中有三个模型,用户、徒步旅行和照片。我想user_id
在用户创建新照片时为新照片设置外键。
- 每个用户
has_many :hikingtrails
&has_many :photos
- 每条远足径
has_many :photos
和belongs_to :user
- 每张照片
belongs_to :hikingtrail
&belongs_to :user
我已将用于创建 Hikingtrail 的表单分成两部分,因为它很长,第一部分 ( new.html.erb
) 只需要用户在用户提交/创建 Hikingtrail 后输入需要验证的数据(即姓名和地址)应用程序将它们路由到edit.html.erb
用户可以使用更多信息更新 Hikingtrail 并上传照片的位置。
用户登录后,他们可以创建 Hikingtrail,当他们这样做时,它会user_id
在 Hikingtrail 模型中设置外键。我可以通过添加@hikingtrail.user = current_user
Hikingtrails 控制器的创建操作来实现这一点。
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 & 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 %>