/What does the .1 mean in ../users/1/profile.1? In editing an associated model in a one to one relationship, such as a user has one profile; it updates and redirected to ..users/user_id/profile.# instead of ../users/user_d/profile.
In the form_for, i used form_for [:user, #profile] to cover for the namespace through nested resources, but i don't understand why the .#. In an attempt to see if the link will cause my program to break, i clicked home (to take me back to my root page, basically reloading the profile as i had programmed for a logged in user), it reverts back to ../users/user_d/profile.
Using a debugging gem i get:
--- !ruby/hash:ActionController::Parameters
action: show
controller: profiles
user_id: '1'
format: '1'
What is format: '1'? Any explanation appreciated.
Adding my Code
USER.RB
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save {self.email = email.downcase }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format:{with: VALID_EMAIL_REGEX},
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :profile
end
PROFILE.RB
class Profile < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
validates :street, :city, :state, :zipcode, presence: true
belongs_to :user
end
Their controllers
USER CONTROLLER
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def new
#user = User.new
#profile = #user.build_profile
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Mini Olympics"
redirect_to user_profile_path(current_user, #profile)
else
render 'new'
end
end
def show
#user = User.find(params[:id])
end
def edit
# Commented out the code, as its redundant due to the line 'before_action :correct_user'
# #user = User.find(params[:id])
end
def update
# Commented out first line of the code, as its redundant due to the line 'before_action :correct_user'
# #user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "profile updated"
#redirect_to #user
redirect_to user_profile_path(current_user, #profile)
else
render 'edit'
end
end
def index
#users = User.paginate(page: params[:page], per_page: 15)
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:id, :email, :password, :password_confirmation, profile_attributes: [:name,
:street, :city, :state, :zipcode] )
end
# Before filters
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user) # '#user == current_user' = 'current_user?(#user)'
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
PROFILE CONTROLLER
class ProfilesController < ApplicationController
def edit
#profile = User.find(params[:user_id]).profile
end
def show
#profile = User.find(params[:user_id]).profile
end
def update
#profile = User.find(params[:user_id]).profile
if #profile.update_attributes(profile_params)
flash[:success] = "profile updated"
redirect_to user_profile_path(current_user, #profile)
else
render 'edit'
end
end
private
def profile_params
params.require(:profile).permit(:id, :name, :street, :city, :state, :zipcode)
end
end
Profile edit form
<% provide(:title, "Edit Profile") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for [:user, #profile] do |f| %>
<%= render 'fields', f: f %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>
</div>
</div>
APP/VIEWS/PROFILES/_FIELDS.HTML.ERB
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :street %>
<%= f.text_field :street, class: 'form-control' %>
<%= f.label :city %>
<%= f.text_field :city, class: 'form-control' %>
<%= f.label :state %>
<%= f.text_field :state, class: 'form-control' %>
<%= f.label :zipcode %>
<%= f.text_field :zipcode, class: 'form-control' %>
ROUTES FOLDER
Rails.application.routes.draw do
root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users do
resource :profile, only: [:show, :edit, :update ]
end
end
Usually the point following a dot at the end of a url is the format, for instance.
/users/12.html
/users/12.js
/users/12/profiles.xml
It looks like you've got a mal-formed url being generated somewhere which is passing the ID in as the format, as well as the id parameter.
That's the explanation, I'm not sure how to get rid of it without a little more information.
What does the users and profiles controllers look like in your routes file?
What does the link_to or url_for or *_url or *_path which generated this link look like?
Although my best guess is that you could just do form_for(#profile) to tidy this up. Then redirect in your create or update method to users_profiles_path(#user, #profile)
Update:
I put part of your routes file into a new rails app and got these routes
edit_user_profile GET /users/:user_id/profile/edit(.:format) profiles#edit
user_profile GET /users/:user_id/profile(.:format) profiles#show
PATCH /users/:user_id/profile(.:format) profiles#update
PUT /users/:user_id/profile(.:format) profiles#update
I missed the fact that you used resource instead of resources, so that each user has only one profile.
In the redirect, use user_profile_path(#user), you don't need to pass in the profile, the path only has one id in it, and that's the user_id.
the "dot something" at the end of a route indicates the format you want to get.
So if you type profile.json, rails will know you want json answer and will render accordingly in the controller (if this one is supported).
Other have answered about the format.
I am currently using Rails 5.1.5 and experienced similar situation. However, once I removed the instance variables that I was passing, the ids did not append to the url and you can still access them in the views.
user_profile_path(current_user, #profile)
To
user_profile_path
Related
My problem is connected with my previous problem. It was solved by using nested routing. But there was different idea without using nested resources. I tried it as an exercise. But when I submit data, ActiveRecord::RecordNotFound in SentencesController#create shows up.
Rails.application.routes.draw do
root 'stories#index'
get 'stories/show'
get 'stories/new'
post 'stories/:story_id', to: 'sentences#create'
resources :stories
resources :sentences, only: [:create]
end
shared/_sentence_form.html.erb is part of story/show_form.html.erb
<%= form_for(#sentence) do |f| %>
<%= hidden_field_tag :story_id, value: #story.id %>
<%= f.text_area :content, placeholder: "Compose new sentence..." %>
<%= f.submit "Save"%>
<% end %>
SentencesController
class SentencesController < ApplicationController
before_action :sentence_params
before_action :find_story
def create
#sentence = find_story.sentences.build(sentence_params)
if #sentence.save
flash[:success] = "You wrote the continuation!"
redirect_to root_url
else
flash[:danger] = "I did not save your words!"
redirect_to "#"
end
end
private
def sentence_params
params.permit(:content, :story_id)
end
def find_story
#story = Story.find(params[:story_id])
end
end
and model:
class Sentence < ApplicationRecord
belongs_to :story
validates :story_id, presence: true
validates :content, presence: true, length: { maximum: 150 }
end
I tried many combinations in controller and view. When I put some information in the text_area and click submit they don't save.
The param is nested:
def find_story
#story = Story.find(params[:sentance][:story_id])
end
But nesting the route is a better RESTful design anyways.
The route POST /stories/:story_id/sentances makes it very clear what the action does.
resources :stories do
resources :sentences, only: [:create]
end
<%= form_for([#story, #sentence]) do |f| %>
<%= f.text_area :content, placeholder: "Compose new sentence..." %>
<%= f.submit "Save"%>
<% end %>
This will properly pass params[:story_id] as a segment of the URL.
I am not sure that's a solution to your problem, but I believe that
def sentence_params
params.permit(:content, :story_id)
end
needs a :sentence in there somewhere. Your form is a form_for #sentence so I guess your params are params[:sentence][:content] or similar.
I believe your error happens in the line #story = Story.find(params[:story_id]), right? Make sure that the #story variable is present in your form and that the record can be found.
I am following Michael Hartl's Rails Tutorial and have completed the part about creating microposts. I was wondering if anyone have an idea about how to make the micropost form responsive to a hyperlink. For example, when a user types in "Visit our HTML tutorial" in the micropost, I want the link to active. Any help would be appreciated.
micropost_controller.rb
class MicropostsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
def destroy
#micropost.destroy
redirect_to root_url
end
private
def micropost_params
params.require(:micropost).permit(:html)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
default_scope -> { order('created_at DESC') } validates :content,
presence: true, length: { maximum: 140 } validates :user_id,
presence: true end
...
end
micropost_form.html.erb
<%= form_for(#micropost) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
You can use the sanitize helper method and pass in the anchor (a) tag as the only allowable tag. You don't use it when they create the post, you use it when you are showing the micropost in the view
app/views/microposts/show.html.erb
<%= sanitize micropost.content, tags: ['a'] %>
(I don't know exactly how you are showing the content of a micropost, but this should give you an idea)
This is safer than other options like html_safe because you can actually control which html tags you will allow the user to be able to input.
I have two models: author and profile. Author accepts_nested_attributes_for profile. I can't figure out how to save the profile model (namely the :avatar attribute) through author.
author.rb
class Author < ActiveRecord::Base
has_one :profile
accepts_nested_attributes_for :profile
end
profile.rb
class Profile < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
belongs_to :author
end
authors_controller.rb
class AuthorsController < ApplicationController
before_action :set_author, only: [:show, :edit, :update, :destroy]
def index
#authors = Author.all.order("last_name ASC, first_name ASC")
end
def new
#author = Author.new
end
def create
#author = Author.new(author_params)
if #author.save
AuthorMailer.activate(#author).deliver
flash[:notice] = "Please check your email to activate your account."
redirect_to root_path
else
render :new
end
end
def edit
#profile = #author.build_profile
end
def update
if #author.update(author_params)
flash[:notice] = "Profile has been updated."
redirect_to #author
else
flash[:alert] = "Profile has not been updated."
render :edit
end
end
private
def author_params
params.require(:author).permit(:first_name, :last_name, :email, :password, :password_confirmation, :avatar)
end
def set_author
#author = Author.find(params[:id])
end
end
app/views/authors/edit.html.erb
<div class="center">
<h1>Edit Author</h1>
</div>
<div class="row">
<div class="col-xs-6 col-lg-4 center-block">
<%= simple_form_for(#author, :defaults => { :wrapper_html => {:class => 'form-group'}, :input_html => { :class => 'form-control' } }) do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.fields_for [#author, #profile] do |p| %>
<%= p.file_field :avatar %>
<% end %>
<%= f.submit "Submit", class: "btn small btn-default" %>
<% end %>
</div>
</div>
This form doesn't save anything to my profile table.
EDIT1
updating the permit parameters didn't save anything to the profile table. But I did notice that adding the following to my authors_controller in the update action saves an incomplete record to the profile table (the avatar field is blank):
author_controller#update
if #author.update(author_params)
#profile = #author.build_profile()
#author.profile = #profile
flash[:notice] = "Profile has been updated."
redirect_to #author
else
flash[:alert] = "Profile has not been updated."
render :edit
end
I tried placing pry inside the update action and my params look like this:
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"EROrMzOejPmMU/wzlnC5iaoTPu4pyBXelHAs3uiPA2U=",
"author"=>
{"first_name"=>"Mike",
"last_name"=>"Glaz",
"profile"=>
{"avatar"=>
#<ActionDispatch::Http::UploadedFile:0x007feb48127ab0
#content_type="image/jpeg",
#headers=
"Content-Disposition: form-data; name=\"author[profile][avatar]\"; filename=\"me_and_lekeziah.jpg\"\r\nContent-Type: image/jpeg\r\n",
#original_filename="me_and_lekeziah.jpg",
#tempfile=#<File:/tmp/RackMultipart20140504-15793-l4uu6l>>}},
"commit"=>"Submit",
"action"=>"update",
"controller"=>"authors",
"id"=>"3"}
so then I tried the following in my update action:
#profile = #author.build_profile(params[:author][:profile])
#author.profile = #profile
but then I get the following error:
ActiveModel::ForbiddenAttributesError in AuthorsController#update
when you use "fields_for" for a association model xxx, your params need to permit :xxx_attributes fields, which's value should be a array include it's attributes, so change your author_params method to something like this:
def author_params
params.require(:author).permit(:first_name, :last_name, :email, :password, :password_confirmation, profile_attributes: [:avatar])
end
First, you should update your params permitted fields as:
def author_params
params.require(:author).permit(:first_name, :last_name, :email, :password, :password_confirmation, :profile_attributes[:id, :avatar])
end
Second, you are mixing Rails form_for helper and Simple_form in your view.
change
<%= f.fields_for [#author, #profile] do |p| %>
to:
<%= f.simple_fields_for [#author, #profile] do |p| %>
I posted an earlier question about this and was advised to read lots of relevant info. I have read it and tried implementing about 30 different solutions. None of which have worked for me.
Here's what I've got.
I have a Miniatures model.
I have a Manufacturers model.
Miniatures have many manufacturers THROUGH a Productions model.
The associations seem to be set up correctly as I can show them in my views and create them via the console. Where I have a problem is in letting the Miniatures NEW and EDIT views create and update to the Productions table.
In the console the command #miniature.productions.create(manufacturer_id: 1) works, which leads me to believe I should be able to do the same in a form.
I THINK my problem is always in the Miniatures Controller and specifically the CREATE function. I have tried out a ton of other peoples solutions there and none have done the trick. It is also possible that my field_for stuff in my form is wrong but that seems less fiddly.
I've been stuck on this for days and while there are other things I could work on, if this association isn't possible then I'd need to rethink my entire application.
The form now creates a line in the Productions table but doesn't include the all important manufacturer_id.
Any help VERY much appreciated.
My New Miniature form
<% provide(:title, 'Add miniature') %>
<h1>Add a miniature</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#miniature) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :production do |production_fields| %>
<%= production_fields.label :manufacturer_id, "Manufacturer" %>
<%= production_fields.select :manufacturer_id, options_from_collection_for_select(Manufacturer.all, :id, :name) %>
<% end %>
<%= f.label :release_date %>
<%= f.date_select :release_date, :start_year => Date.current.year, :end_year => 1970, :include_blank => true %>
<%= f.submit "Add miniature", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
Miniatures controller
class MiniaturesController < ApplicationController
before_action :signed_in_user, only: [:new, :create, :edit, :update]
before_action :admin_user, only: :destroy
def productions
#production = #miniature.productions
end
def show
#miniature = Miniature.find(params[:id])
end
def new
#miniature = Miniature.new
end
def edit
#miniature = Miniature.find(params[:id])
end
def update
#miniature = Miniature.find(params[:id])
if #miniature.update_attributes(miniature_params)
flash[:success] = "Miniature updated"
redirect_to #miniature
else
render 'edit'
end
end
def index
#miniatures = Miniature.paginate(page: params[:page])
end
def create
#miniature = Miniature.new(miniature_params)
if #miniature.save
#production = #miniature.productions.create
redirect_to #miniature
else
render 'new'
end
end
def destroy
Miniature.find(params[:id]).destroy
flash[:success] = "Miniature destroyed."
redirect_to miniatures_url
end
private
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, :production, :production_attributes)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
end
Miniature model
class Miniature < ActiveRecord::Base
has_many :productions, dependent: :destroy
has_many :manufacturers, :through => :productions
accepts_nested_attributes_for :productions
validates :name, presence: true, length: { maximum: 50 }
validates :material, presence: true
validates :scale, presence: true
validates_date :release_date, :allow_blank => true
def name=(s)
super s.titleize
end
end
Production model
class Production < ActiveRecord::Base
belongs_to :miniature
belongs_to :manufacturer
end
Manufacturer model
class Manufacturer < ActiveRecord::Base
has_many :productions
has_many :miniatures, :through => :productions
validates :name, presence: true, length: { maximum: 50 }
accepts_nested_attributes_for :productions
end
Instead of calling:
#production = #miniature.productions.create
Try Rails' "build" method:
def new
#miniature = Miniature.new(miniature_params)
#miniature.productions.build
end
def create
#miniature = Miniature.new(miniature_params)
if #miniature.save
redirect_to #miniature
else
render 'new'
end
end
Using the build method uses ActiveRecord's Autosave Association functionality.
See http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
You also need to update your params method, e.g.
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, productions_attributes: [:manufacturer_id])
end
Also your fields_for should be plural (I think)...
<%= f.fields_for :productions do |production_fields| %>
So I have a Review model in my app for users leaving reviews for movies. I'm rendering a form for a new Review in the Show view for each individual movie. However, when I try to submit a blank review to check that the errors display on the page, they don't. What could I be doing wrong?
My review model:
class Review < ActiveRecord::Base
attr_accessible :content
belongs_to :user
belongs_to :movie
validates :user_id, presence: true
validates :movie_id, presence: true
validates :content, presence: true, length: { maximum: 1000 }
end
My reviews controller:
class ReviewsController < ApplicationController
before_filter :signed_in_user, only: [:new, :create, :destroy]
def new
end
def create
#I'm doing this to get around the mass assignment error.
movie_id = params[:review].delete(:movie_id)
#review = current_user.reviews.build(params[:review])
#review.movie_id = movie_id
if #review.save
flash[:success] = "Review created!"
redirect_to movie_path(#review.movie)
else
redirect_to movie_path(#review.movie)
end
end
def destroy
end
end
My form:
<%= form_for(:review, url: reviews_path) do |f| %>
<%= f.hidden_field :movie_id %>
<div class="field">
<%= f.text_area :content, placeholder: "Write a new review..." %>
</div>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
When you redirect away from the #create method, you're losing the errors... Change to
if #review.save
flash[:success] = "Review created!"
redirect_to movie_path(#review.movie)
else
render :new
end