There is the following code in Comments controller:
def create
current_user.comments.create!(place: current_place, content: params[:content])
render json: encode_result
end
private
def current_place
Place.find(params[:place_id])
end
This code creates a new comment for a current user and a current place; if the current place doesn't exist than this code throws exception RecordNotFound. I can send 'place_id' instead of 'place' for 'create' method, but I need to check if place exists before creating a new comment. Please, tell me, is my solution good or there is any better way? Thanks in advance.
This is doing what it's supposed to do. Your code is perfect as it's written now. You specifically want to halt execution if a specified record doesn't exist. All you need to do is handle the exception.
You can do this for your entire controller with rescue_from, or your entire application by placing rescue_from in your ApplicationController.
You can do like this.........
def create
current_user.comments.create!(place: current_place, content: params[:content]) if current_place
render json: encode_result
end
private
def current_place
Place.find(params[:place_id])
end
Related
I'm having an issue tracking down why my update method isn't getting the needed arguments. I have a similar test for show and the payloads are working. In this scenario the route in question is invoice/invoice_id/trip/id. If you can help me spot the error and give any suggestions on how to troubleshoot this type of problem in the future that'd be great.
This is the update method.
def update
if #trip.update(#trip.trip_id, trip_params)
head :no_content
else
render json: [#invoice, #trip].errors, status: :unprocessable_entity
end
end
With the following private methods.
private
def set_trip
#trip = Trip.where(:invoice_id => params[:invoice_id], :trip_id => params[:id] )
end
def trip_params
params.require(:trip).permit(:trip_id, :depart_airport, :arrive_airport, :passenger_first_name, :passenger_last_name, :passenger_count, :departure_date, :merchant_id)
end
def load_invoice
#invoice = Invoice.find(params[:invoice_id])
end
end
My failing test looks like this.
test "should update trip" do
put :update, invoice_id: #invoice.invoice_id, id: #trip,
trip: {arrive_airport: #trip.arrive_airport,
depart_airport: #trip.depart_airport,
departure_date: #trip.departure_date,
passenger_count: #trip.passenger_count,
passenger_first_name: #trip.passenger_first_name,
passenger_last_name: #trip.passenger_last_name}
assert_response 204
end
if you are calling set_trip in before_action then update() method should look like this
def update
if #trip.update(trip_params)
head :no_content
else
render json: [#invoice, #trip].errors, status: :unprocessable_entity
end
end
update() is an instance method that can be called using object, you only need to pass trip_params into it, Hope that helps!
You can get this error message when the method is calling another which is being passed the wrong number of arguments.
update takes a hash as its one and only argument, but you are are passing two arguments (#trip.trip_id, trip_params) in the update method. This is why you are getting the "Wrong number of arguments (1 for 2) for update method" error message. as #RSB said, just pass in the trip_params and the Trip instance will be updated.
RSB was right on the money. It turned out in this case that my issue was at the database level. The table didn't have a primary key so I was using
#trip = Trip.where in the private method and this was causing it to come back with an array of possible rows rather than the specific one. I changed things at the database level to have a primary key and updated the private method. VoilĂ RSB's code worked!
I am in the process of Drying up my Rails 4 app. I have noticed that I type out the exact same code quite a lot and that it might be better to place this in a concern. I am just not exactly sure how to do this, as I suspect it might require some Meta Code.
I would like to place (ex.) this "create" method in a controller concern called CrudGenerator:
def create
if #contact_us.validate(params[:contact_us])
#contact_us.save
flash[:success] = t :create_success_flash, class_object: "Contact Us"
respond_with(#contact_us, :location => new_contact_path)
else
render :new
end
end
Now this will nee to accept 2 params if moved to a concern i.e. "contact_us" and "new_contact_path" as these params will be unique each time this concern is included. These are the only sections of this code that will change.
I have tried to use define_method to generate the below code, but I am not 100% certain.
Any advice on how to generate the below code in a concern and make it part of my ContactUsController ?
Thanks
You can move the code in a concern like this:
def create
resource_class.new(permitted_resource_params)
if resource.save
redirect_to some_path
else
render :new
end
end
And then include this concern in the controllers you want to add this functionality to. To load the resource, you can do it in a before_action call and load resource using params[:controller_name].capitalize.constantize... I hope you get the point
In my Rails app I have an invoices_controller.rb with these actions:
def new
#invoice = current_user.invoices.build(:project_id => params[:project_id])
#invoice.build_item(current_user)
#invoice.set_number(current_user)
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice created."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
Essentially, the new method instantiates a new invoice record plus one associated item record.
Now, what sort of method do I need if I want to duplicate an existing invoice?
I am a big fan of Rails's RESTful approach, so I wonder if I should add a new method like
def duplicate
end
or if I can use the existing new method and pass in the values of the invoice to be duplicated there?
What is the best approach and what might that method look like?
Naturally, you can extend RESTful routes and controllers.
To be rally RESTful, it is important to look exactly, what you want.
i.e. if you want a new invoice and use an existing one as a kind of template, then it is comparable to a new action, and the verb should be GET (get the input form). As is it based on an existing invoice, it should reference that object. After that you would create the new invoice in the usual way.
So in you routes:
resources :invoices do
member do
get 'duplicate'
end
end
giving you a route duplicate_invoice GET /invoices/:id/duplicate(.format) invoices#duplicate
So in your view you can say
<%= link_to 'duplicate this', duplicate_invoice_path(#invoice) %>
and in your controller
def duplicate
template = Invoice.find(params[:id])
#invoice= template.duplicate # define in Invoice.duplicate how to create a dup
render action: 'new'
end
If I understand correctly your question you can:
resources :invoices do
collection do
get 'duplicate'
end
end
and with this you can do:
def duplicate
# #invoice = [get the invoice]
#invoice.clone_invoice
render 'edit' # or 'new', depends on your needs
end
clone_invoice could be a custom method which should have a invoice.clone call in your custom method.
If you question if you can use additional methods except REST, you absolutely can. Google, for example, encourage developers to use something, what they call "extended RESTful" on GoogleIO, http://www.youtube.com/watch?v=nyu5ZxGUfgs
So use additional method duplicate, but don't forget about "Thin controllers, fat models" approach to incapsulate your duplicating logic inside model.
When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.
I've created a custom method in my model, which finds a record by name:
def find_city
Place.find_by_name(city_name)
end
I can call this method in my view with place_path(#place.find_city), which works great when a place with the appropriate name exists. What I would like to be able to do is write in a redirect for when the place doesn't exist, and I'm unsure about where the logic should go. Basically, I would like to write something like:
respond_to do |format|
if #place.find_city.blank?
format.html { redirect_to :action => "new" }
else
format.html { render :action => "show" }
end
end
...but I would still like the controller to respond to place_path(#place) in the usual manner as well. Any help would be much appreciated!
EDIT: sorry for the confusion, should have explained my example further. I have a Place model that has both 'city_name' and 'name' as attributes. The find_city custom method that I detailed above finds the place whose name matches the city_name for another place eg.
Place.name = "foo"
Place.city_name = "baz"
So therefore Place.find_city gives the record where Place.name = "baz". Cheers!
Do something like this
keep your model as
class City < ActiveRecord::Base
named_scope :by_name, lambda {|city|{:conditions => "name=#{city}"}}
end
in your controller
class CitiesController < ApplicationController
def index
#city = City.by_name(<city name>)
if #city.nil?
<redirect to one place>
else
<redirect to another place>
end
end
end
and in your view access the #city parameter.
** Normally we shouldnt access Models directly from views. Either we should do it through controllers or helpers
Hope this helps
cheers
sameera
I've decided to create a helper method for this problem - what I've described above might seem a bit of an unorthodox approach, but I only need the find_city method to create a links bar for each place, so I don't think I really need to create a separate city model, or define a formal self-referential relationship. Just in case anyone might find this useful, I've created a links_bar helper:
module PlacesHelper
def links_bar(place)
if place.city_name.blank?
render "no_city_links"
elsif place.find_city.nil?
render "nil_find_city_links"
else
render "complete_links"
end
end
end
Each partial then has the required behaviour depending upon whether or not the find_city method returns a record or not.