Rails: How to review content before submit/save? - ruby-on-rails

I know it is simple but I can't get my head around a solution.
It is a job board site. Lets say it's functionality similar to this site. When a user fill all required information and click "To next step" or "Preview", another page loads with all filled data. That page is similar to the final page when data is saved.
When user on preview page, it can go forward and submit the page (in this case it will be saved to DB). Or, click back to Edit the job.
I tried the following::
Within _form.html.erb I added a preview button
<%= f.submit "Preview", :name => 'preview' %>
Within JobControllers I altered create method
def create
if params[:preview]
#job = Job.new(jobs_params)
render 'jobs/preview'
else
#job.save
end
end
Created a Preview view /jobs/preview.html.erb
Now I have 2 problems.
1- Within my preview page, I have an edit button like so: <%= link_to "Edit Job", edit_job_path(#job) %>. But I have an error because I can't find #job. Error says: No route matches {:action=>"edit", :controller=>"jobs", :id=>nil} missing required keys: [:id]
SOLUTION Changed like to <%= link_to 'Back to edit', 'javascript:history.go(-1);' %>
2- How I would submit and add to my DB all information on preview page?
Thank you.

Once I've given a similar task. What I've done is to save records, but not to publish. In my index (resource listing) action of relevant controller, I only fetch published records. Also show action prechecks if that record's published attribute is set to true.
What was my model/controllers looked like before
#model
class Book < ActiveRecord::Base
...
scope :active, -> { where(published: true).some_other_queries }
self.active?
(published && some_other_requirements)
end
...
end
#controller
def index
#books = Book.active
...
end
def show
if #book.active?
render 'show'
...
else
...
end
end
First added a secret key for previews.
#model
def secret
#some custom random key generation
# e.g. Digest::MD5.hexdigest("#{id}_#{ENV['RAILS_SECRET']}")
end
Then added preview action to controller
def preview
# i don't check if the record is active.
# also added a security layer, to prevent irrelevant guys to view
# that record
if #book.secret == params[:secret]
render 'show'
else
...
end
end
In dashboard
...
= link_to "Preview", preview_book_path(book, secret: book.secret)
...
then added a member route
#routes
resources :books do
get :preview, on: :member
end

When I have to do something like this what I normally do is create a review table in my app. This table looks just like the table that is going to saving to.
When they press the "Approved" or "Save" button just populate the new table with the proper data.
I like to create a routes to handle this
resources :something do
match 'move_to_something_else' => 'somethings#move_to_something_else', as: :move_to_something_else, via: :all
end
Now on the controller we can do the following:
def move_to_something_else
#something = Something.find(params[:id])
#something_else = SomethingElse.new
#something_else.name = #something.name
....
#something_else.save
redirect_to something_else_path(#something_else)
end
Alternative you could add a state to your table with the default value of 'draft'
# config/routes.rb
resources :something do
match 'published' => 'somethings#published', as: :published, via: :all
end
# Controller
def published
#something = Something.find(params[:id])
#something.state = 'published'
#something.save
redirect_to something_path(#something)
end

Related

Rails Need help to capture form field in model from product view

I need to capture a field added by a user in a form_for, inside the product show page.
My product.rb model as follows:
belongs_to :user
has_many :complaints
My complaint.rb model as follows:
belongs_to :product
belongs_to :user
My user.rb model as follows:
has_many :products
My product controller is a basic controller with all the new, create, edit, update actions and all the routes are good.
User looks at the product show page like this, and it's all good
http://localhost:3000/products/1
My goal is to create a complaint from the product show page, when user views the specific product. So I have created a complaints_controller.rb to capture all the details of the product, and create a complaint. I have an issue with capturing the complaint_number which is a field inside the complaints table.
Here is my form inside the product show page
<%= form_for([#product, #product.complaints.new]) do |f| %>
<%= f.number_field :complaint_number, placeholder: "Enter complaint number you were given" %>
<%= f.submit 'Complaint' %>
<% end %>
Here is my complaints_controller.rb
Goal is to capture the complaint_number fields and run the make_complaint method to create a complaint and populate rest of the fields in the newly created row of the complains table.
class ComplaintsController < ApplicationController
before_action :authenticate_user!
def create
# Will Get product_id from the action in the form in product show page.
product = Product.find(params[:product_id])
# This complaint_number does not seem to work
complaint_number = product.complaints.find_by(complaint_number: params[:complaint_number])
# Now I want to run a make_complaint method and pass the product and the complaint number. This fails, I can't capture the complaint_number in the form from user input.
make_complaint(product, complaint_number)
redirect_to request.referrer
end
private
def make_complaint(product, complaint_number)
complaint = product.complaints.new
complaint.title = product.title
complaint.owner_name = product.user.name
complaint.owner_id = product.user.id
# Note: complaint_number and current_complaint are a fields in the Orders table
# Note:
complaint.current_complaint = complaint_number
if complaint.save
flash[:notice] = "Your complaint has been sent!"
else
flash[:alert] = complaint.errors.full_messages
end
end
end
For routes I have added resources :complaint, only: [:create] inside the resources of products to get products/:id/complaints
My routes.rb is like this
Rails.application.routes.draw do
get 'products/new'
get 'products/create'
get 'products/edit'
get 'products/update'
get 'products/show'
root 'pages#home'
get '/users/:id', to: 'users#show'
post '/users/edit', to: 'users#update'
resources :products do
member do
delete :remove_image
post :upload_image
end
resources :complaint, only: [:create]
end
devise_for :users, path: '', path_names: { sign_in: 'login', sign_up: 'register', sign_out: 'logout', edit: 'profile' }
Your form has complaint_quantity:
<%= form_for([#product, #product.complaints.new]) do |f| %>
<%= f.number_field :complaint_quantity, placeholder: "Enter complaint number you were given" %>
<%= f.submit 'Complaint' %>
<% end %>
Your controller has complaint_number:
complaint_number = product.complaints.find_by(complaint_number: params[:complaint_number])
If you check your params from the server log, I bet you'll see the value you are looking for is coming across as complaint_quantity and not complaint_number.
UPDATE
With the form misspelling corrected, the error persists, so let's check into more areas:
complaint_number = product.complaints.find_by(complaint_number: params[:complaint_number])
So, break that down:
1. What does params actually include?
Is :complaint_number being submitted from the form?
If not, the form still has an error somewhere.
2. Does product.complaints actually include a complaint that could be matched by complaint_number?
I don't know your data structure well enough to tell, but it looks to me like you might actually want to do:
Complaint.find_by(complaint_number: params[:complaint_number])
instead of:
products.complaints.find_by(complaint_number: params[:complaint_number])
UPDATE #2
You know the problem is with your params.
I'm confident you aren't accessing your params correctly since you are using a nested form:
form_for([#product, #product.complaints.new])
Should mean your params are structured like { product: { complaint: { complaint_number: 1234 }}}
So params[: complaint_number] is nil because it should really be something like params[:product][:complaint][:complaint_number]
Please look at your server log in your terminal right after you submit the form to see the structure of your params. Or insert a debugger in the controller action and see what params returns.
ALSO, Instead of accessing params directly, you should whitelist params as a private method in your controller.
Something along these lines:
private
def product_complaint_params
params.require(:product).permit(:id, complaint_params: [ :complaint_number ])
end
See this: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html

How could I add to a table from from a different model HTML page?

I'm new to rails. I have a table TeamMemberships that has a foreign key to Students and a foreign key to Teams. I want to be able to have a link in the show.html.erb page for a specific team that redirects to a form that lets me add a row to TeamMemberships in which the Team foreign key is the team whose show.html.erb page the link was clicked on and the Student foreign key would be determined by the form. Is this possible?
Thanks!
Add a new resources in your routes.rb file like so:
Rails.application.routes.draw do
resources :teams do
resources :team_memberships
end
end
Which will create the CRUD routes:
team_team_memberships GET /teams/:team_id/team_memberships(.:format) team_memberships#index
POST /teams/:team_id/team_memberships(.:format) team_memberships#create
new_team_team_membership GET /teams/:team_id/team_memberships/new(.:format) team_memberships#new
edit_team_team_membership GET /teams/:team_id/team_memberships/:id/edit(.:format) team_memberships#edit
team_team_membership GET /teams/:team_id/team_memberships/:id(.:format) team_memberships#show
PATCH /teams/:team_id/team_memberships/:id(.:format) team_memberships#update
PUT /teams/:team_id/team_memberships/:id(.:format) team_memberships#update
DELETE /teams/:team_id/team_memberships/:id(.:format) team_memberships#destroy
What is interesting you here is the new_team_team_membership GET /teams/:team_id/team_memberships/new(.:format) that you can call using the named route new_team_team_membership.
You will be able to pass the team ID for which you'd like to create the TeamMembership in the route with new_team_team_membership_path(#team.id).\
Regarding the student_id you will pass it in the HTTP POST request body.
Here is how to create the link from the team show page to the TeamMembership form:
<%= link_to 'Add a student', new_team_team_membership_path(#team.id) %>
You will have to create the TeamMembershipsController and the new action. The action should initialise the #new_team_membership variable that will be used by the form and load the student list like so:
class TeamMembershipsController < ApplicationController
def new
#new_team_membership = TeamMembership.new(team_id: params[:team_id])
#students = Student.all
end
end
Then you have to create the app/views/team_memberships/ folder and add the new.html.erb file with the following form:
<%= form_for #new_team_membership do |f| %>
<%= f.select :student_id, options_for_select(#students.collect{ |student| [student.name, student.id]) %>
<%= f.submit %>
<% end %>
Then in your TeamMembershipsController controller, you have to define the create action that will receive in parameter the team_id, and from the form the selected student_id. You will then be able to create the TeamMembership instance.
Update
Regarding the create action of the TeamMembershipsController controller, here is how it should be done:
class TeamMembershipsController < ApplicationController
def new
# ...
end
def create
# The form should have embedded all the params within a key with the model's name
# like `:team_membership`.
student = Student.find(params[:team_membership][:student_id])
#team_membership = TeamMembership.new(
team_id: params[:team_id],
student: student
)
if #team_membership.save
redirect_to :team_path(params[:team_id])
else
# Reload again the student as we will show errors.
# The student dropdown would be empty.
#students = Student.all
# Render again the new form
render :new
end
end
end

How to display Devise account details on a profile page

Using Devise I would like to display User account information such as profile name, first & last name etc on another page called profile page within my rails application.
I have created a controller called profiles with a view called profile/show
In the controller have added the below code
def show
#user = User.find_by_profile_name(params[:id])
if #user
render action: :show
else
render file: 'public/404', status: 404, formats: [:html]
end
end
end
In the view profiles/show I have the following code
<%= #user.profile_name %>
and the route is get 'profiles/show'.
My issue is when I do all of the above the profile name of the user still does not display on the profile page? There are no errors that come up it just doesn't display. I am not sure what code I am missing. I have checked the console and the user does have a profile name save to that ID and this is also in the devise account settings. So I am not sure how to get this information to display?
In Rails you would usually set it up like follow to take leverage of convention over configuration:
# config/routes.rb
resources :users, only: [:show, :index]
# app/models/user.rb
class User < ActiveRecord::Base
# ...
def self.find_by_uid!(uid)
User.find_by!("profile_name = :p OR id = :p", p: uid)
end
end
# app/controllers/users_controller.rb
class UsersController
# GET /users/:id
def show
#user = User.find_by_uid!(params[:id])
# Rails does the magic.
end
# GET /users
def index
#users = User.all
end
end
<%- # app/views/users/show.html.erb -%>
<h1><%= #user.profile_name %></h1>
The only special part here is that in the user model we create a class method which will query by id or profile_name. The reason that this is important is that it lets you use link_to(#user) and redirect_to(#user) as expected.
Which is also why we use resources :users. When the route name and the model line up the Rails polymorphic route handlers are able to do their job. If you want to use /profiles thats fine but never /profiles/show - including the action in the route defeats the whole purpose of REST.
The show action will render users/show.html.erb by default. So you rarely need to explicitly render in your controller.
render action: :foo
is only used when you want to render a template with the same name as another action, its usually used as follows:
def create
#something = Something.new
if #something.save
redirect_to(#something)
else
render action: :new # renders views/something/new.html.erb
end
end
If you want to explicitly render a template you would do render :foo or render "foo/bar".
And when you use find or find_by! it will raise an exception if the record is not found which by default will render the static 404 template. Reproducing this error handling in your actions is not very desirable since it violates the DRY pinciple.

How can I determine which redirect to make in Rails?

I have two paths that my application can take.
Path 1: Is editing a valid record.
User goes to persons#show
User clicks edit to go to persons#edit
After update leads back to persons#show
Path 2: Is editing an invalid record.
User goes to persons#invalid_records
User clicks edit to go to persons#edit
After update (if it succeeds) leads back to persons#show
How can I have Path 2 end up back at persons#invalid_records instead of persons#show?
edit posting routes as requested:
resources :persons do
scope module: :persons do
resources :notes
resources :reports
end
collection do
match 'invalid_records' => 'persons#invalid_records', via [:get], as :invalid_records
end
member do
get 'transactions'
end
end
So I wanted to post an answer to help get anyone started that might be confused. Thanks to #MaxWilliams for the idea.
I added the following...
<%= link_to "Edit", edit_person_path(record, :invalid_record => true) %>
along with this hidden field on my _form.html.erb
<%= f.hidden_field :invalid_record, :value => params[:invalid_record] %>
Finally on the persons_controller.rb.
def update
respond_to do |format|
if #person.update(person_params)
if params[:person][:invalid_record] == true
#...
else
#...
end
else
# ...
end
end
end

Rails root path based on user intput

I need to let the user select a root path (or a get) to be one of many pages he has created. That means, i want to be able to created the page "fantastic page" and set it to be the front page (http://example.com/).
Is this at all possible? I have tried sending the user to a fixed action and redirecting the user to the selected action but that leaves me with a route like this: http://example.com/page/8
My desired result is: http://example.com/
(while the page is the one selected by the user).
Any help is appreciated :)
Edit: I need to clarify. I want the user to be able to select a page or a photo. That means I need the user to be able so select both the controller and the action.
Something like this:
constraints(Subdomain) do
get "/" => '#{user_selected_controller}#{user_selected_action}/#{:id}'
end
The code above is completely wrong. But i think it illustrates what i want to accomplish.
You should explicitly render the page view from the controller.
config/routes.rb
root to: 'home#index'
controllers/home_controller.rb:
def index
#page = #get fantastic page
render 'pages/page', page: #page
end
See http://guides.rubyonrails.org/layouts_and_rendering.html#rendering-an-action-s-template-from-another-controller.
I Solved this problem not through routing but by creating a polymorphic association to both Pages and Galleries with the users table. Like this:
First create polymorphic association
# In my page model
has_one :user, :as => :home
# In my Gallery model
has_one :user, :as => :home
# In my User model
belongs_to :home, polymorphic: true
Then i used this as my controller in the users_controller:
def home
# Multitenant rails app. This refers to user who owns the subdomain visited.
#user = current_tenant
# This is not optimal, results in two queries.
if #user.home_type.classify.constantize.exists?(#user.home_id)
# This is the same as #page = Page.find(params[:id])
instance_variable_set("##{#user.home_type.downcase}", #user.home_type.classify.constantize.find(#user.home_id))
# this is the same as render 'pages/show', layout: false
render "#{#user.home_type.downcase.pluralize}/show", layout: false
else
redirect_to photos_path
end
end
My routes look like this:
# Set a page as the home screen
post 'pages/set_home/:id' => 'pages#set_home', as: :set_home_page
# User selected Home screen
get "/" => 'users#home'
Setting a home page:
# In pages_controller
def set_home
#user = current_user
#user.home = #page
if #user.save
redirect_to pages_path, notice: "Home screen set to #{#page.title}"
else
redirect_to pages_path, alert: "An error occured. Home not set."
end
end
This way i could load the relevant resource through the users.home variable, while using a normal route to the users controller home action.
This might be an inefficiently solution, but its the only one i could think of. Please feel free to suggest improvements :)

Resources