RESTful API Ruby on Rails - ruby-on-rails

I'm playing with Ruby on Rails for the first time, and have an app up and running. Here's the quick database definition:
Teams
- id: int
- name: char[20]
Answers
- id: int
- answer_value: text
I want to be able to type: "http://localhost:3000/teams/1/answers/purple" in a browser
if team 1 answers purple.
I thought that adding the following to my routes.rb file would allow me to do that, but it hasn't.
resources :teams do
resources :answers
post 'answer_value', :on => :member
end
I can see the first answer by team 1 by going to "http://localhost:3000/teams/1/answers/1" , but I don't know how to actually set values via the URI.

If I understand you correctly, in you answers controller's show action, instead of doing Answer.find(params[:id]), do Answer.find_by_answer_value(params[:id])

If you want to create an answer, please post to
/teams/1/answers
with parameter :answer => { :answer_value => 'purple' }
Don't append answer_value to the url, looks like it's record id.

You need to either over ride the to_param method in the model, which can do what you are looking for although there are some caveats - including needing to use what #Chirantan suggested with Answer.find_by_answer_value(params[:id]) for all your finders. You can find more info in this Railscast on the subject or search for to_param in the Rails API and select the entry under ActiveRecord::Integration.
Alternatively you can go with a gem like FriendlyId which provides some more comprehensive functionality and control over the URL construction.

Typing things into your browser won't perform a post, regardless of what you put in the routes, it will only perform a get.
Creating something by a get is not RESTful, and you really shouldn't do it. That said, since you say you're just 'playing around with rails', you could do this:
If you want a get to create the thing, you have to set it that way in the routes. /teams/1/answers/purple
get '/teams/:team_id/answers/:answer_value' => 'answers#create'
then in your answers controller, create action, you could just check for a params[:team_id] and params[:answer value] and make sure that gets set before the save.
def create
#answer = Answer.new()
#answer.team_id=params[:team_id]
#answer.answer_value=params[:answer_value]
respond_to do |format|
if #answer.save
format.html { redirect_to #answer, notice: 'Answer was successfully created.' }
format.json { render json: #answer, status: :created, location: #answer }
else
format.html { render action: "new" }
format.json { render json: #answer.errors, status: :unprocessable_entity }
end
end
end

Related

nested form rails 4 save existing record on create

I am struggling to get this working. I have three models
Student
Classroomattnd
Classroom
Using the has_many :through relationship. All my relationships are defined correctly and I have setup the nested form using the accepts_nested_attributes.
So when creating a new student I want to select from a list of classrooms instead of creating a new classroom. The form part also works fine the part I am not getting is when I create the student it complains about the following error.
Couldn't find Classrooom with ID=3 for Student with ID=
I have searched around for few days now but can not get the answer I need to get this working.
def new
#student = Student.new
#student.classrooms.build
end
def edit
end
def create
#student = Student.new(student_params)
respond_to do |format|
if #student.save
format.html { redirect_to #student, notice: 'Student was successfully created.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
end
Can someone help here, someone must of face this issue before?
Also in the rails console when I run the following it works:
classroom = Classroom.last
student = Student.create(name: 'Dave', classrooms:[classroom])
Your parameter handling isn't supporting nesting. You can look at request parameters in your server log or inspect the fieldnames of your generated form to be sure of your target. It's going to be something along the lines of
def student_params
params.require(:student).permit(:student => [:name, :classroom => [:id, :name]])
end
Or maybe as below. In this second case I'm not assuming everything in the form is nested under a student container. Also note the switch from classroom to classroom_attributes which is a change I have sometimes needed to make even though the form above is what the docs indicate.
def student_params
params.require(:name).permit(:classroom_attributes => [:id, :name])
end
Hopefully that gives you a notion of how to tailor your parameter definition to what your form is generating. Also note your error messages give you indication of what part of your definition is failing, eg the missing Student id in the error you quote.

How to use parameters value from public_activity gem (Rails)?

I am building a Rails app. And in my app, there are Projects where users can "Follow". When a user follows one of the pages, he/she will get updates if somebody uploads/creates a folder/file.
Below is the screenshot when somebody just created a new folder:
And below is the code for "Create" action in my Folder controller:
def create
#folder = current_user.folders.where(project_id: params[:project_id]).create(folder_params)
respond_to do |format|
if #folder.save
#folder.create_activity :create, owner: current_user, :params => {
:project_id => proc {|controller, project| #folder.project.id},
:project_name => proc {|controller, project| #folder.project.name},
}
format.html { redirect_to #folder.project, notice: 'Folder was successfully created.' }
format.json { render :show, status: :ok, location: #folder }
else
format.html { render :new }
format.json { render json: #folder.errors, status: :unprocessable_entity }
end
end
end
As you can see :project_id and :project_name are the parameters for the public_activity when a new folder being created.
And below is the screenshot on how this parameters value looks like in the database after they were saved:
QUESTION:
So my question is, how do i use this parameters values in my activities_controller?
Here is the code for my activities controller right now:
class ActivitiesController < ApplicationController
def index
#activities = PublicActivity::Activity.order("created_at desc").where(owner_id: current_user.following_users, owner_type: "User")
end
end
Instead of using "owner_id:", I want to use the "project_id" value from parameters column. So how can i do this?
Thank you very much in advanced! :)
The parameters field contains a simple yaml dump, so not really easy to search efficiently.
A simple solution would be to use LIKE operator, for instance
PublicActivity::Activity.where("parameters LIKE '%project_id: #{#project.id}%'")
You might want to consider to add custom fields instead.
Thanks for the answer, but I got a better solution than using the parameters value or custom field.
Here is how my activities_controller looks like right now:
class ActivitiesController < ApplicationController
def index
activity_table = PublicActivity::Activity.arel_table
# We want to view all activity of folders related to projects we are follwing
folder_ids = Folder.where(project_id: current_user.following_projects.pluck(:id)).pluck(:id)
# Generate query for all activity related to folders we care about
folders_query = activity_table[:trackable_type].eq('Folder').and(
activity_table[:trackable_id].in(folder_ids)
)
# Generate query for all users that we follow
users_query = activity_table[:owner_id].in(current_user.following_users.pluck(:id))
activity_query = folders_query.or(users_query)
#activities = PublicActivity::Activity.where(activity_query)
end
end
By using this way, I could easily combine the activities from the "Users" and also from the "Projects" that the user follows.
You can modify it to add any other activities such as from the "Comments" or "Voting".
Hope this will help other people out there that are using public_activity gem! :)

Rails redirect if validation fails

In a Rails 3.2 app, I have a validation for an attachment type.
Attachment model:
class Attachment < ActiveRecord::Base
validates_presence_of :name
validates_attachment_presence :attach, :message => "No file selected"
validate :check_type
def check_type
if self.costproject_id != nil
if self.attach_content_type != 'application/pdf'
self.errors.add(:pdf, " ONLY")
return false
end
end
end
But, the return false sends me to this URL:
http://localhost:3000/attachments
I want it to go back to the previous input screen:
http://localhost:3000/attachments/new?costproject_id=2
How do I accomplish that?
Thanks!!
UPDATE1
Perhaps the redirect has to take place in the controller?
format.html { render action: "new" }
Attachment controller:
# POST /attachments
# POST /attachments.json
def create
#attachment = Attachment.new(params[:attachment])
respond_to do |format|
if #attachment.save
format.html { redirect_to session.delete(:return_to), notice: 'Attachment was successfully created.' }
format.json { render json: #attachment, status: :created, location: #attachment }
else
format.html { render action: "new" }
format.json { render json: #attachment.errors, status: :unprocessable_entity }
end
end
end
I changed this line:
format.html { render action: "new" }
To:
format.html { redirect_to request.referer }
And now it goes back to where I want. But, I've lost the errors - they don't display.
To help you understand what's going on here. When you go to /attachments/new you are rendering a form. When you press submit, you are sending a POST request to /attachments, which invokes the create action.
You're create action appears to be solid and idomatic. However when you render action: "new" in the case of an error, it's not a full redirect, it's rendering the form in the context of the current action.
Normally this is fine, because idomatic rails would have you building a single, very similar, model object in both new and create, and the form for helper would render that object. However your new action is creating all kinds of objects based on a large assortment of query parameters, which I'm guessing is why you are seeing behavior you don't like.
I expect your final solution will involve bringing all those parameters into Attachment in some way, if they don't need to be saved to the database, you can make attr_accessors on Attachment
# Model
class Attachment < ActiveRecord::Base
attr_accessor :worequest_id, :workorder_id # etc
end
# View
<%= form_for #attachment do |f| %>
<%= f.hidden :worequest_id %>
<% end %>
Approaching it this way, your post request params will look like
{
attachment:
{
worequest_id: 1,
# etc
}
}
And you would also need to rework your query params to nest the inidividual ids inside of an attachment
/attachments/new?[attachment][worequest_id]=1
This way you could build attachment from params in both actions:
Attachment.new(params[:attachment])
And now your current create action should more or less work as expected, because now it's idomatic rails.
You still aren't going to get the new action with the same query params, but since you are taking those params and filling them in hidden fields on the form, they won't be lost when you try and fail to create. In any case, unless you do something to persist the values between requests, the POST to /attachments is going to wipe out the ery params.
Try this.
Replace
return false
With
redirect_to request.referrer || root_url
Note: root_url here is a catchall. Also this is Rails 4, I do not know if it also applies to Rails 3. Worth a try, though.
Debug ideas
First confirm a simple redirect_to root_url (or whatever name you use for your root) works in your controller
redirect_to root_url
Then, once redirect_to confirmed working, focus on getting the REST interface "request." information. There's a Rails 3 discussion here which may help you.
How to get request referer path?

How do I define a custom URL for a form confirmation page?

I am creating a basic product landing page with Rails in which users can enter their email address to be notified when the product launches. (Yes, there are services/gems etc that could do this for me, but I am new to programming and want to build it myself to learn rails.)
On successful submit of the form, I would like to redirect to a custom '/thanks' page in which I thank users for their interest in the product (and also encourage them to complete a short survey.)
Currently, successful submits are displayed at "/invites/:id/" eg "invites/3" which I do not want since it exposes the number of invites that have been submitted. I would like to instead redirect all successful submits to a "/thanks" page.
I have attempted to research "rails custom URLs" but have not been able to find anything that works. The closest I was able to find was this Stackoverflow post on how to redirect with custom routes but did not fully understand the solution being recommended. I have also tried reading the Rails Guide on Routes but am new to this and did not see anything that I understood to allow for creating a custom URL.
I have placed my thanks message which I would like displayed on successful form submit in "views/invites/show.html.haml"
My Routes file
resources :invites
root :to => 'invites#new'
I tried inserting in routes.rb:
post "/:thanks" => "invites#show", :as => :thanks
But I don't know if this would work or how I would tell the controller to redirect to :thanks
My controller (basically vanilla rails, only relevant actions included here):
def show
#invite = Invite.find(params[:id])
show_path = "/thanks"
respond_to do |format|
format.html # show.html.erb
format.json { render json: #invite }
end
end
# GET /invites/new
# GET /invites/new.json
def new
#invite = Invite.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
# POST /invites
# POST /invites.json
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
#format.js { render :action => 'create_success' }
format.json { render json: #invite, status: :created, location: #invite }
else
format.html { render action: "new" }
#format.js { render :action => 'create_fail' }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end
It would seem as if creating a standard URL for displaying a confirmation would be relatively straightforward. Any advice on how to achieve this would be appreciated.
I guess you want to redirect after your create action, which is executed when the form is submitted.
Just add redirect_to in the following way:
def create
#invite = Invite.new(params[:invite])
if #invite.save
...
redirect_to '/thanks'
else
...
redirect_to new_invite_path # if you want to return to the form submission page on error
end
end
I omitted some of the code for brevity.
In your routes add:
get '/thanks', to: "invites#thanks"
Add the thanks action to your invites controller:
def thanks
# something here if needed
end
And create a thanks.html.erb page in app/views/invites.
I would do get "/thanks" => "invites#thanks" in routes.rb and then add this in your controller:
def thanks
end
Then add a file app/views/invites/thanks.html.erb with your thank-you content.
You could create a route like this:
resources :invites do
collection do
get 'thanks'
end
end
This will also create a path helper called thanks_invites_path.
It will be at the invites/thanks path, but if you want it to be on/thanks, you could just do as Jason mentioned:
get "/thanks" => "invites#thanks", :as => :thanks
The as part will generate a helper to access that page: thanks_path.
You would need a extra action in the controller called thanks, and put whatever info you need inside, and also you will need a additional view called thanks.html.erb
Since you want everybody to go to that page after a successful submit, in your create action you would have:
format.html { redirect_to thanks_invites_path} (or thanks_path), what ever you choose, when you name the route you can check it with rake routes if it's okay, and whatever rake routes says, just add _path at the end.

Rails - Custom Validation that requires current_user information

I'm having a very difficult rails problem and i thought to ask for some help. The situation is like this :
I'm using restful authentication for my User model. Now, user has a field named 'gold' that is a numeric value. There is another model named Book that has been created using scaffolding.
What i want to do is simple, yet i cannot see a way of doing it. I want to add some validation where if the user's gold is not, let's say 100, they cannot create a new book entry(from the scaffolding standard view).
Now the problem is that i need current_user information in order to validate this from my model. I need that in order to get the user id and therefore get their gold amount as well. I cannot find a good way (if any) to do that.
Another thought was doing that from a controller. However, the standard "if #book.save" block does not really allow me to insert my own error messages (inside scaffold create) :
if not User.has_the_needed_gold(current_user, 100)
flash[:goldError] = 'You do not have the needed gold to create this book.'
#end
respond_to do |format|
if #book.save
flash[:notice] = 'Book was successfully created.'
format.html { redirect_to(#book) }
format.xml { render :xml => #book, :status => :created, :location => #book }
else
format.html { render :action => "new" }
format.xml { render :xml => #book.errors, :status => :unprocessable_entity }
end
end
Now, i cannot output that message and abort the save of the new book as well. I've tried adding my own error to base, but it was cleared out(after save i guess). I'm quite confused with the situation and i've been searching around for a couple of hours with no results.
If anybody can help with that, please do so, you would spare me lots of trouble :)
Thanx for reading !
You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.
Look the user up when validating. It's likely the user lookup will have been cached by ActiveRecord, so it's not a performance hit to do so. Try something like this:
class Book
validate :check_gold
def check_gold
user = User.find(self.user_id)
self.errors.add(:user_id => "#{user.name} doesn't have enough gold!") if user.nil? or (user.gold < 100)
end
end

Resources