How to update an existing record? - ruby-on-rails

I have a Books model and it has CRUD operations. In config/routes.rb, I have declared
map.resources :books
My new.html.erb looks like as:
<%= form_for :book, url: books_path do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.submit :Add %>
<% end %>
My create method in controller looks like as:
def create
book = Book.new(authorized_params)
book.save
end
So, when I submit my form from views, request would go to the 'create' method and an record for the book gets created in database. Fair enough. Now, I want to have an edit page for book. So, my edit method in controller look like as:
def edit
#book = Book.find_by(params[:id])
render :new
end
When I go to my edit view, it automatically show the value of title in the text box, which is what I expected.. But when I try to submit the form again(ofcourse after changing the title value) it again creates a new record instead of updating it..
Something basic which I missed out in my reading? I googled about it though but did not find satisfactory answer.

The issue is that you are using the 'new' view where form has the post method. If you will check the generated routes, post will be for create method, that is adding a new record. You will have to create a new view for edit where the form target URL will be edit_book_path(#book) and method will be patch. Patch method will route to 'update' function in your controller where you will call #book.update. I am not writing the exact code, but these directions should help you achieve what you want

Related

Associating two models together in Rails 4+

I have a User model which is working under Devise with no problems (using devise sanitizer to update fields, so no UsersController)
I am working on creating a Quiz model, which belongs_to the User model, and the User model has_one Quiz. In my routes, I have: resources :users, :quizzes (is this supposed to be quizzes or quizs? I know that Rails pluralizes but couldn't seem to find which it'd be in this case).
In my views, I'm trying to open up a modal (which works) and inside have it populated with fields that a User can enter in questions they want (q1 through q5 being the database fields).
Inside the modal content area, I have the code:
<%= form_for #quiz, url: {action: "new"} do |f| %>
<%= f.submit "Create" %>
<% end %>
and I get the error "First argument in form cannot contain nil or be empty"
Inside my quizzes controller, I have defined new as
def new
#quiz = Quiz.new
end
I would greatly appreciate some assistance here! Thank you.
In your WelcomeController action: index add this line to initialized #quiz
def index
#quiz = Quiz.new
end
hope you made a good progress in your project.
shoudn't it be like following
<%= form_for #quiz do |f| %>
<%= f.submit "Create" %>
<% end %>

Strong params and action mailer issues

I have tried to create a mailer using the following code:
routes code
resources :listings do
member do
put :lead
end
end
mailer controller code
def lead(listing)
#listing = listing
mail(to: #listing.leadrecepient, subject: "test")
end
standard controller code
def lead
Enquiry.lead(#listing).deliver
end
view
<%= form_for lead_listing_path(#listing), method: :put do |listing| %>
<%= listing.text_field :name %>
<%= listing.submit %>
<% end %>
In the context of a business directory, I want it so that there is a enquiry form on each listing page that when filled out and submitted, the information is sent to the relative listing email.
The problem however is that when I type into the form and click submit, I get the following error:
param is missing or the value is empty: listing
This seems to be because I have it in the "listing" controller which controls the showing and creation of the business listing itself. I therefore have strong params for a new listing which contains all the new listing variables:
def listing_params
params.require(:listing).permit(:fullname, :jobtitle, :email, :franchisename, :leadrecepint, :shortdescription, :longdescription, :website, :branchcount, :scale, :mininvestment, :investmentrange, :category, :hexbg, :logourl, :facebook, :twitter, :linkedin, :googleplus, :approved)
end
How do I go about fixing this? I'm a beginner if I'm honest, could really do with some help to get this mailer working! Thanks.
Strong params are for when you are submitting new resources or modifications to resources. To protect against people adding extra parameters that may circumvent security or other aspects of your application unexpectedly.
If you are adding an action to an existing resource that the user is authorized to access, which this appears to be, you want to just find the object by ID, and use it. So instead of finding it using the params filtered through listing_params, just find it like this in the controller:
def lead
listing = Listing.find(params[:id])
Enquiry.lead(listing).deliver
redirect_to listing
end
And invoke it using a simple link, instead of this:
<%= form_for lead_listing_path(#listing), method: :put do |listing| %>
<%= listing.text_field :name %>
<%= listing.submit %>
<% end %>
Just use this in your view:
= link_to 'Go!', lead_listing_path(#listing), method: :put
Nothing more to it.

Trying to fire an action in my controller when a selection in a drop down menu is made but getting a no route matches error

I'm building a web interface to accompany a mobile app I'm building. I have a drop down select menu that lists a bunch locations.
On selection of a location I want to make a call to a method in my controller and grab some destinations within the location that was selected (each location has several destinations).
I then would like to render my show template with these results allowing the user to select a destination and make a booking.
This is what I have so far:
My view with a list of resorts:
<%= form_tag :url => { :action => :show } do %>
<%= select_tag :resort , options_for_select(#resorts), :prompt => 'Select Resort', :onchange => 'submit()' %>
<% end %>
Controller:
class HomeController < ApplicationController
def index
#resorts = ["A","B", "C", "D", "E"]
end
def new
end
def edit
end
def create
end
def show
#activities = Parse::Query.new("Activity").tap do |a|
a.eq("resort", params[:resort])
end.get
end
end
Just slightly confused. Using form_for makes more sense to me with CRUD in mind and also because the form is object based.
I'd like to just take the selected resorted and pass it into a method in my controller that goes into a database and grabs a bunch of destinations. I then want to list these destinations on my show page where a user can click and be taken to another page where they can make a booking at that destination.
My above code doesn't work. I have resources :home in my routes file.
However when I try to load my page with the form I get:
No route matches {:action=>"show", :controller=>"home"} missing required keys: [:id]
How do I pull this off?
I went on my lynda account and pulled up a rails essential tutorial which I'll have to use to refresh my memory some time tomorrow but the tutor doesn't cover use of select_tag.
Would appreciate some help here
Thanks for your time
So a few thoughts. Not sure why you are using form_tag and also not sure why you aren't using Rails idiomatic conventions.
Declare a resource in your routes for #resorts, like so:
resources :resorts
Then just use Rails form_for helper like:
<%= form_for #resorts, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
<%= f.select :resort, (insert your other options) %>
<%= f.submit "Create" %>
<% end %>
I have not tested the above code, so play around with it, but that should work.
However, let me save you some headache. Checkout SimpleForm.
For your models, you would want to setup an association between your locations and destinations.
class Location < ActiveRecord::Base
belongs_to :resort # or whatever the relation is
has_many :destinations
end
class Destination < ActiveRecord::Base
belongs_to :location # This assumes there is just a one-to-many relationship between Location and Destination
end
Make sure you have a LocationsController with all the actions.
In this case, your SimpleForm form would look something like this:
<%= simple_form_for #locations do |f| %>
<%= f.input :name %>
<%= f.association :password %>
<%= f.button :submit %>
<% end %>
That approach will make your life much easier. Take a look at the collections methods in Simple Form. Rails can make your life difficult with the built in form helpers.
Hope that helps!
In your routes, add
get '/choose_resort' => 'home#show' #you can name the get whatever
Then in your form...
<%= form_tag choose_resort_path do %>
That being said... you should have your query at a separate endpoint, and redirect to the show page. That should get you moving, methinks.
The show action needs an id of the object you are showing. Change your controller:
class HomeController < ApplicationController
def index
#resorts = [["A",1], ["B",2], ["C",3], ["D",4], ["E",5] ]
end
And your view
<%= select_tag :id , options_for_select(#resorts), :prompt => 'Select Resort', :onchange => 'submit()' %>
That gives your show action the proper resort id. You'll have to adjust that action to find the right activities relevant to the resort.

Displaying data from two models in one view

I have two models with respective controllers and views: Profile and Comment.
The entire view (whole webpage) of my application is in the Profile show.html.erb. On this page, the user should be able to create a comment, which belongs_to a Profile.
How can this be accomplished without having to navigate to the standard /comments/new page?
Edit:
After following the rails guide, I implemented:
<%= simple_form_for([#profile, #profile.comment.build], html: {class: "form-inline"}) do |f| %>
<%= f.error_notification %>
<%= f.input :description, label: false, placeholder: 'Create an comment', input_html: { class: "span4" } %>
<%= f.submit 'Submit', class: 'btn btn-small'%>
<% end %>
CommentController
def create
#profile = profile.find(params[:profile_id])
#comment = #profile.comments.create(params[:comment])
redirect_to profile_path(#profile)
And I'm getting this error:
undefined method `comment' for #<Profile:
Fixed: In the build statement, comments needed to be plural
#profile.comments.build
All you need to do is add the comment form code into profile#show. Then in the show action of profile_controller do something like:
def show
#comment = Comment.new
end
And in the comments controller add:
def create
#comment = Comment.create(params[:comment])
end
You might consider saving the form and updating the page using AJAX calls and possibly something like Knockout. So in profiles/show.html.erb, make a regular (separate) form just for posting comments. Use jQuery or something like it to post the form via AJAX to /comments, so it goes the create action in your comments controller. Have that controller return a JSON response, that will either be the saved comment, or a hash of error messages that looks something like {:fieldname => 'too long'}.
On the client, parse the json response and either display the saved comment, or display the error message explaining why it couldn't be saved. You can do all this in plain jQuery, but adding something like Knockout will make it all a bit simpler.

embeddable component in views or how to call a different controller

Let's say you have a form that has its own controller. Is there any way to embed this form in different views (governed by other controllers)? As far as I understand partial templates carry only logic in the Ruby code that is inside the template. I am thinking more of a full-blown component where maybe you can call its controller.
The form is not driven that directly by the controllers. Yeah this is the price of all this magic.
To clarify a bit:
You type in your browser http://yourhost/posts
Your request (GET /posts) hits the router, then your router says that the /post urls belongs to the PostsController and the action is index
Then your controller executes the index method, do your business logic (loads the posts from the database, for example)
loads the view (views/posts/index...) and run it by 'substituting' all the instance variables and stuff defined in your controller (eg #posts = Post.all) that you have in it
then you see the view rendered with a list of posts (if in the view you have something similar to #posts.map{|p| p.title}.join(", ") )
yes I know it's not the best /posts view in the world but it's only to grasp the idea
The same goes for form, your form tag (for example form_for) gets an instance from the controller (let's say #post) and (in edit mode) gets filled with your Post attributes.
Then when you (edit something and) click the submit button it makes a request (by default a PUT to /posts) passing all the values in the form, then your controller gets the (POST) values of the requests (the ones you see in the server log) and makes his work (like saving the post's datas)
and because of this in a controller you can use the method
render :controller => :foo, :action => :bar
to render another controller action different from the default one
Hope this will be useful!
You can create a form in any view to call any controller. In a RESTful app, you can usually just pass an empty object (using the Posts/Commments example from makevoid)
<% form_for #new_comment do |f| %>
<%= f.text_area :text %>
<%= f.submit %>
<% end %>
This form should route to the create action on CommentsController. From there, you could use redirect_to :back in order to get back to the view that triggered this controller. This does have some validation issues I think though.
If you are non-RESTful, you can use the old form_for style:
<% form_for :comment, #new_comment, :url => { :controller => "comments", :action => "create" } do |f| %>
<%= f.text_area :text %>
<%= f.submit %>
<% end %>
For either of these examples, you need to have the #new_comment, which you would create in your PostsController:
def show
#post = Posts.find(params[:id])
#new_comment = #post.comments.build
end

Resources