Rails Users updating information in custom routes? - ruby-on-rails

Not sure how to title my question...
My intent is that users will have a list of items (and only the current_user can edit this list)
class Item < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :items
end
I have a Users controller (note: I'm also using devise gem, but created a separate Users Controller)
class UsersController < ApplicationController
load_and_authorize_resource
def show
#user = User.find(params[:id])
end
def list
#user.items.build
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(
items_attributes: [:user_id, :item, :name, :_destroy]).merge(user_id: current_user.id)
end
end
Right now I have set up routing
get 'users/:id/list', to: 'users#list', as: :list
Which gives me this: localhost:3000/users/1/list
Theoretically, this should be the show view... where it'll populate the list of items for user id: 1.
I want to be able to do localhost:3000/list/edit So the user can update this list.
My form in views/users/list.html.erb (I'm using cocoon gem for help with nested forms).
<%= simple_form_for #user do |f| %>
<%= f.simple_fields_for :items do |i| %>
<%= f.text_field :item %>
<%= f.text_field :name %>
<% end%>
<%= link_to_add_association 'add item', f, :items %>
<%= f.button :submit %>
<% end %>
I know my form is in my list.html.erb where it'll populate all the items, but I'm just doing this for testing purposes.
How do I update the form to save into items database and its associated with the current user?
EDIT:
def update
#user = current_user.items.find_by_user_id(current_user.id)
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'Congrats on your successful update!' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end

There is nothing wrong with having a form in a "show" view. If you only want the current_user to view the form, then wrap your form in an if statement:
<% if #user == current_user %>
(form goes here...)
<% end %>
As long as you have an update route/action for users, that form should submit (disclaimer: I've never used the cocoon gem).
Edit: Your form should be fine as is. Add this route to your routes.rb file:
patch 'users/:id', to: 'users#update'

For current user authentication you should use cancan gem
https://github.com/ryanb/cancan
For nested fields related to user nested_form gem is the best option
https://github.com/ryanb/nested_form

Related

How do you update an attribute with a button from a join table with another controller in rails?

I have a rails application, where I have set up a scaffold named event, users with devise and compared them in a third table UserEventStates, which makes it possible for each user to select their state for each event different (like here). In my events_controller, I have a create action which sets the status for each user to 0 (default). I can display and count the states as expected, but how can I update them through a button and how can I change the .update(state: 1) to .update(state: state) and set/specify the state in my view? I tried the following:
Edit: I found out that the error is based on the route get /update_user_event_state/:id, it should be another.. How do I find out which route I need?
#app/controllers/events_controller.rb
before_action :set_event, only: %i[ show edit update destroy update_user_event_state]
before_action :set_user_event_state, only: %i[ show update_user_event_state]
def create
#event = Event.new(event_params)
#users = User.all
respond_to do |format|
if #event.save
format.html { redirect_to event_url(#event), notice: "Termin wurde erstellt" }
format.json { render :show, status: :created, location: #event }
#users.each do |user|
UserEventState.new(user_id: user.id, event_id: #event.id, state: 0).save
end
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
end
end
def update_user_event_state
self.update(state: 1)
end
private
def set_event
#event = Event.find(params[:id])
end
def set_user_event_state
#ues_event = UserEventState.where(event_id: #event.id)
end
def event_params
params.require(:event).permit(:description, :date, :meeting_time, :start_time, :end_time)
end
#app/views/events/show.html.erb
<% #ues_event.each do |uesev| %>
<%= uesev.user.email %>
<%= uesev.state %>
<%= uesev.night %>
<%= button_to "Zusagen", update_user_event_state_path(uesev) %>
<% end %>
#config/routes.rb
devise_for :users
resources :events do
resources :users
end
get '/update_user_event_state/:id', to: 'events#update_user_event_state', as: 'update_user_event_state'
root "events#index"
get '/update_user_event_state/:id', to: 'events#update_user_event_state', as: 'update_user_event_state'
Change the above routes to have 2 parameters.
First parameter will have the event id
Second parameter will have state value(I believe its an id)
get '/update_user_event_state/:id/:state_id', to: 'events#update_user_event_state', as: 'update_user_event_state'
Now in the views add an extra parameter for the user which would pass the state_id parameter to the controller while clicking the button.
#app/views/events/show.html.erb
<% #ues_event.each do |uesev| %>
<%= uesev.user.email %>
<%= uesev.state %>
<%= uesev.night %>
<%= button_to "Zusagen", update_user_event_state_path(uesev, state_id: YOUR_STATE_ID_VALUE) %>
<% end %>
Now access this state_id parameter in the controller on update_user_event_state method like below
def update_user_event_state
self.update(state: params[:state_id])
end
Posted this answer assuming that the existing state updation workflow works perfectly.

Submitting a form from one model's controller and view to a different model

I'm new to Rails and working on a sample app. The idea behind the app is that it's a computerized check-in sheet for kids to ride a school bus. There are four models: Family, Kid, SchoolRide, and HomeRide. For Family and Kid, I generated complete scaffolds, but for SchoolRide and HomeRide, they're just models with a boolean field each of whether the kid has checked in to the schoolbus in the morning or checked out in the afternoon when coming home.
I want to be able to have a user check in a kid from a form rendered on the kid show view, but I'm having trouble creating instances of my ride models from the kids controller. How do I set up the views, routing, and controllers? Where/how do I pass in the parameters to the ride models in the kids controller?
Here's my form rendered into the kid's show view. Currently, I'm getting a syntax error.
<%= form_with(model: #school_ride, remote: true), :url => school_rides_path, :html => { :method => :post } do |form| %>
<div><p>
<%= form.label :check_in %><br>
<%= form.check_box :check_in %><br>
</p>
</div>
<div>
<%= form.hidden_field :kid_id, value: #kid.id %>
</div>
<p>
<%= form.submit %>
</p>
<% end %>
Here're my models:
class Kid < ApplicationRecord
belongs_to :family
has_many :school_rides
has_many :home_rides
end
class HomeRide < ApplicationRecord
belongs_to :kid
end
class SchoolRide < ApplicationRecord
belongs_to :kid
end
Here are some relevant parts of my kids controller:
def show
#family = Family.all
#school_ride = SchoolRide.new
end
# GET /kids/new
def new
#kid = Kid.new
end
# GET /kids/1/edit
def edit
end
# POST /kids
# POST /kids.json
def create
#kid = Kid.new(kid_params)
respond_to do |format|
if #kid.save
format.html { redirect_to family_path(id: #kid.family_id), notice: 'Kid was successfully created.' }
format.json { render :show, status: :created, location: #kid }
else
format.html { render :new }
format.json { render json: #kid.errors, status: :unprocessable_entity }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_kid
#kid = Kid.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def kid_params
params.require(:kid).permit(:name, :birthdate, :grade, :family_id)
end
def school_ride_params
params.require(:school_ride).permit(:check_in)
end
Here's some of my routing:
resources :kids
resources :school_rides, only: [:new, :create]
Try this:
<%=form_for #school_rid, remote: true do |form| %>

Associating nested attributes to user

I'm trying to build a small expense tracking app using Rails 4.1. Using devise for authorization. Expense and it's nested attribute, comments belong to a user. The associations are set up in the model and expenses are getting associated with the user. Here's the Expense controller:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
#item = #expense.items.build
##comment = #expense.comments.build
end
def index
#expenses = Expense.all
##items = Item.where(:expense_id => #expense.id)
end
def show
#expense = Expense.find(params[:id])
#items = Item.where(:expense_id => #expense.id)
end
def create
#expense = current_user.expenses.new(expense_params)
respond_to do |format|
if #expense.save
ExpenseMailer.expense_submission(#expense).deliver
format.html { redirect_to #expense, notice: 'Expense Report Submitted.' }
format.json { render :show, status: :created, location: #expense }
else
format.html { render :new }
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def edit
#expense = Expense.find(params[:id])
end
def update
#expense = Expense.find(params[:id])
##comment = #expense.comments.build
if #expense.update(expense_params)
#if #comment.save
#ExpenseMailer.comments_added(#expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
#else
# flash[:notice] = "Expense Report Updated"
#redirect_to expenses_path
##end
else
render 'edit'
end
end
The form from where the comment attributes are built looks like:
<%= nested_form_for (#expense) do |f| %>
<div class="form-group">
<%= f.label :state %><br />
<%= f.select :state, Expense.states, :include_blank => false, class: "form-control" %>
</div>
<%= f.fields_for :comments, #expense.comments.build do |comment| %>
<div class="form-group">
<%= comment.label :comment%>
<%= comment.text_area :comment, class: "form-control" %>
</div>
<%= comment.hidden_field :commenter %>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
The #comment.commenter = current_user isn't adding the current user id to the database. Should I include it in the expense controller somewhere?
You have to add:
#comment.commenter = current_user
below that if statement. Like this:
def create
#article = Expense.find(params[:expense_id])
if #comment = #expense.comments.create(comment_params)
#comment.commenter = current_user
#comment.save
ExpenseMailer.comments_added(#expense).deliver
redirect_to expenses_path
end
end
And then save the comment again. In your current code you're overwriting the #comment object with the newly created object by doing:
#comment = #expense.comments.create(comment_params)
but you haven't set the commenter on that new object anywhere yet.
Model
I just tried to create better code for your strong params, but I couldn't work out how to include the param in your nested attributes
I would therefore recommend using the inverse_of: method in your Comment model to get it sorted properly:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
belongs_to :user
has_many :comments, inverse_of: :expense
accepts_nested_attributes_for :comments
end
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :expense, inverse_of: :comments
before_create :populate_expense, on: :create
private
def populate_expense
self.commenter_id = self.expense.user_id
end
end
This should work if you're populating the comments from the accepts_nested_attributes_for directive
Comments
I don't understand why you've created two create actions for both your expenses and comments controllers - the controller action is meant to be independent of the Model
What I'm trying to say is that if you think the comments#create controller action will be invoked by your nested attribute creation, you'd be mistaken - it is only invoked when you send a request to it through the Rails router :)
If you're creating Comments and Expenses separately, you'll be able to use these two different actions; but they won't be invoked by each other. Only Model methods can be invoked by the controller (you shouldn't be calling other controller methods)
If you wanted to create a Comment from the expenses#show page, here's how you'd set it up:
#config/routes.rb
resources :expenses do
resources :comments #-> domain.com/expenses/:expense_id/comments/new
end
#app/controllers/expenses_controller.rb
Class CommentsController < ApplicationController
def new
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new
end
def create
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new(comment_params)
#comment.save
end
private
def comment_params
params.require(:comment).permit(:comment, :params).merge(commenter_id: current_user.id)
end
end
This will work if you wanted to create a comment from the expenses#show page. If you do this, you need to ensure you are calling the comments#new / comments#create actions, rather than those of the expenses controller

Rails 4 controller actions for fields_for nested resources

I am trying to understand Rails' field_for, specifically what should go into the controller for nested resources. My issue is that when I create a comic with comic pages through the Comic form, the page's image are not saved.
I have Users, Comics, and ComicPages. Here are the models:
class User < ActiveRecord::Base
has_many :comics
has_many :comic_pages, through: :comics
end
class Comic < ActiveRecord::Base
belongs_to :user
has_many :comic_pages, :dependent => :destroy
accepts_nested_attributes_for :comic_pages
end
class ComicPage < ActiveRecord::Base
belongs_to :comic
end
Here is the form for Comic, where I also want to add comic_pages:
<%= form_for ([#user, #comic]) do |f| %>
<%= f.text_field :title %>
<%= f.fields_for :comic_pages do |comic_page| %>
<%= comic_page.file_field :comic_page_image %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I am confused about the comics_controller (new and create actions). How can I pass comic_page params to this controller???
def new
#user = current_user
#comic = #user.comics.new
#comic.comic_pages.build
end
def create
#user = current_user
#comic = #user.comics.new(comic_params)
#comic.comic_pages.build
respond_to do |format|
if #comic.save
format.html { redirect_to #user, notice: 'Comic was successfully created.' }
format.json { render action: 'show', status: :created, location: #user }
else
format.html { render action: 'new' }
format.json { render json: #comic.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comic
#comic = Comic.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comic_params
params.require(:comic).permit(:title, :synopsis)
end
def comic_page_params
params.require(:comic_page).permit(:comic_page_image, :comic_image_file_name)
end
Many thanks!
--- EDIT ---
After the answer for the params, I used it to create the following create action:
def create
#user = current_user
#comic = #user.comics.new(comic_params)
i = 0
until i = 1
#comic_page = #comic.comic_pages.new(comic_params[:comic_pages_attributes]["#{i}"])
#comic_page.save
i += 1
end
respond_to do |format|
if #comic.save
...
end
end
end
You need to permit those fields from comic_pages that you want to save through in the comic_params section of your controller
params.require(:comic).permit(:title, :synopsis, comic_pages_attributes: [:comic_page_image])

Saving to another Model database, with current Form record ID - Rails

I'm trying to get the text from a text_area field in a form to save to a database in a different Model with the current Model's ID.
Currently, this works but only will save integers. If I put text into the 'Notes' field, then its saves it as a '0'. I suspect this is working correctly but I'm missing a piece to my puzzle. This is because I only want the 'Ticket' to save the note_id because I will have multiple 'Notes' per 'Ticket.
How can I get the Note to save in the Note Model, with an ID, and associate that note_id with this specific ticket?
Form - /app/views/tickets/_form.html.erb
<%= form_for(#ticket) do |f| %>
<% if #ticket.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#ticket.errors.count, "error") %> prohibited this ticket from being saved:</h2>
<ul>
<% #ticket.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.fields_for :notes do |u|%>
<%= u.label :note %>
<%= u.text_area :note, :size => "101x4", :placeholder => "Leave notes here." %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Tickets_controller.rb
class TicketsController < ApplicationController
# GET /tickets
# GET /tickets.json
def index
#tickets = Ticket.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #tickets }
end
end
# GET /tickets/1
# GET /tickets/1.json
def show
#ticket = Ticket.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #ticket }
end
end
# GET /tickets/new
# GET /tickets/new.json
def new
#ticket = Ticket.new
#ticket.notes.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #ticket }
end
end
# GET /tickets/1/edit
def edit
#ticket = Ticket.find(params[:id])
end
# POST /tickets
# POST /tickets.json
def create
#ticket = Ticket.new(params[:ticket])
respond_to do |format|
if #ticket.save
format.html { redirect_to #ticket, notice: 'Ticket was successfully created.' }
format.json { render json: #ticket, status: :created, location: #ticket }
else
format.html { render action: "new" }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
# PUT /tickets/1
# PUT /tickets/1.json
def update
#ticket = Ticket.find(params[:id])
respond_to do |format|
if #ticket.update_attributes(params[:ticket])
format.html { redirect_to #ticket, notice: 'Ticket was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
# DELETE /tickets/1
# DELETE /tickets/1.json
def destroy
#ticket = Ticket.find(params[:id])
#ticket.destroy
respond_to do |format|
format.html { redirect_to tickets_url }
format.json { head :no_content }
end
end
end
Note.rb
class Note < ActiveRecord::Base
belongs_to :ticket
attr_accessible :note, :ticket_id
end
Ticket.rb
class Ticket < ActiveRecord::Base
attr_accessible :notes_attributes
accepts_nested_attributes_for :notes
end
It is because note_id is an integer type.
Use nested models:
Refer this for Nested Models
Model:
class Ticket < ActiveRecord::Base
has_many :notes
attr_accessible :note_id, :notes_attributes
accepts_nested_attributes_for :notes
end
View:
<%= form_for(#ticket) do |f| %>
<%= f.fields_for :notes do |u|%>
<%= u.label :note %>
<%= u.text_area :note %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
What you have is a nested association, with Ticket as the "parent". The association is governed by the link from note_id in the Note model to the id (primary key) of the Ticket. What you're presently doing right now is manually manipulating that numeric association. Rails, knowing that the note_id column is supposed to be an integer, is taking the text you're trying to insert and turning it in to a number (zero in this case). You've probably got a bunch of orphaned rows right now because of this.
Ultimately, in order to accomplish what you're trying to do, your form will need to provide fields for that associated model. One way you can handle this is by using the accepts_nested_attributes_for in your Ticket model. Like so:
class Ticket < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
And in your form, you can easily create a nested form like so:
<%= form_for(#ticket) do |f| %>
<div class="field">
<%= f.fields_for :notes do |f_notes|%>
<%= f_notes.label :note %><br />
<%= f_notes.text_area :note, :size => "101x4", :placeholder => "Please leave notes here."%>
<% end %>
</div>
<% end %>
Edit Almost forgot: Check out this Railscast from Ryan Bates dealing with Nested Attributes
Edit 2 As codeit pointed out, you don't need the attr_accessible :note_id in Ticket. Since you've indicated that a Ticket has many Notes, and that Note belongs to Ticket, the foreign key column will appear in the Note model as ticket_id, which you already have. Having note_id in the ticket model is useless, and also nonsensical since has_many describes a plural relationship (which can't be expressed with a single column).

Resources