Im trying to add service details for a vehicle. On the show page for my vehicle I've put a button which links to add new service details
<%= link_to 'Add Service Details', {:controller => :services, :action => :new, :vehicle_id => #vehicle.id}, {class: "btn btn-secondary"} %>
The url will show the vehicle_id like the following
http://localhost:3000/services/new?vehicle_id=3
on the _form.html.erb for services I've got
<div class="field">
<%= form.label :vehicle_id %>
<%= form.text_field :vehicle_id, id: :service_vehicle_id %>
</div>
In the controller I've got
def create
#vehicle = Vehicle.find(params[:vehicle_id])
#service = #vehicle.service.create(service_params)
redirect_to #service, notice: 'Service was successfully created.'
end
and
def new
#vehicle = Vehicle.find(params[:vehicle_id])
#service = Service.new
end
In my model I've defined the relationship as
Vehicle model
has_many :service
Service model
belongs_to :vehicle
My routes file looks like
resources :vehicles
resources :services
Can someone please help me use the vehicle_id and save it as a foreign key. I'm not sure if the above is the right approach but I'd like it so when the user views a vehicle they've got a button which allows them to add service details.
The user go to
http://localhost:3000/services/new?vehicle_id=3
in your controller the following code run
def new
#Serch for a vehicle
#vehicle = Vehicle.find(params[:vehicle_id])
# Create a Service object with no vehicle_id set
#service = Service.new
end
Like vehicle_id is not set then
<%= form.text_field :vehicle_id, id: :service_vehicle_id %>
should genarate a text field without value, when you submit the form
def create
# Here must throw an error
#vehicle = Vehicle.find(params[:vehicle_id])
#service = #vehicle.service.create(service_params)
redirect_to #service, notice: 'Service was successfully created.'
end
Go to the new action set the vehicle id
def new
#Serch for a vehicle
#vehicle = Vehicle.find(params[:vehicle_id])
# Create a Service object with no vehicle_id set
#service = Service.new
# Add vehicle id to service
#service.vehicle_id = #vehicle.id
end
If what you want is a form that you can place on show action of a vehicule to create a new service than what you want is just your run of the mill nested resource:
resources :vehicules do
resources :services, shallow: true
end
This will setup the routes so that you create a new service by POST'ing to /vehicules/:vehicule_id/services. Note that this puts the parent id in the path so that you don't need to send it as a hidden input.
For the form you just do:
<%= form_for([#vehicule, #service || Service.new]) do |f| %>
# inputs go here
<% end %>
# or with Rails 5 'form_with'
# form_with(model: #vehicule, url: [#vehicule, #service || Service.new])
You then setup the controller:
class ServicesController < ApplicationController
before_action :set_vehicule, only: [:new, :create, :index]
def new
#service = #vehicule.services.new
end
def create
#service = #vehicule.services.new(service_params)
if #service.save
# ...
else
# ...
end
end
def index
#services = #vehicule.services
end
# ...
private
def set_vehicule
#vehicule = Vehicule.find(params[:vehicule_id])
end
def service_params
# ...
end
end
Related
I want to pass params from event/id(show page) to my order_controller.
I use simple_form_for to pass event.id and promocode that input by user
#event.show.html.haml
= simple_form_for order_url, url: orders_path(#event, :promocode), method: :post do |f|
= f.hidden_field :event_id, params: {id: #event.id}
= f.input :promocode, value: :promocode, class: 'form-control', placeholder: "Enter your PromoCode"
= f.submit 'APPLY PromoCode'
IDK if a need hidden_field to pass event_id
#order_controller
class OrdersController < ApplicationController
before_action :order, only: %i[show]
def index
#orders = Order.all.order(created_at: :desc).page(params[:page]).per(5)
end
def show; end
def create
#order = Order.create(title: event.title, user_id: current_user.id, event_id: event.id, order_amount: event.price, order_currency: event.currency)
if !promo.nil?
redirect_to_order
elsif #order.save
redirect_to checkout_create_path(id: #order.id)
else
redirect_to event, alert: 'Something went wrong, try again later'
end
end
def redirect_to_order
promo_validate
order_amount_promo_code = #order.order_amount - promo.promo_code_amount
order.update(order_amount: order_amount_promo_code)
redirect_to #order
end
def promo_validate
if promo.present? && promo.promo_code_amount.positive? && promo.promo_code_currency == event.currency
promo.update(order_id: #order.id)
else
redirect_to event, alert: "This PromoCode is invalid or Your PromoCode Currency doesn't match with Event"
end
end
private
def promo
#promo ||= PromoCode.find_by(uuid: params[:promocode])
end
def event
#event ||= Event.find(params[:id])
end
def order
#order ||= Order.find(params[:id])
end
def order_params
params.require(:order).permit(:title, :event_id, :promocode, :event)
end
end
I'm using methods def event and def promo to take this params from view.
Also my routes look like this.
resources :events
resources :orders
I would nest the route:
resources :events do
resources :orders, shallow: true
end
This creates an explicit relationship between the two resources that can be seen by just looking at the URL. To create a order tied to an even you send a POST request to /events/:event_id/orders.
class EventsController
def show
# ..
#order = #event.orders.new
end
end
= simple_form_for [#event, #order] do |f|
= f.input :promocode, value: :promocode, class: 'form-control', placeholder: "Enter your PromoCode"
= f.submit 'APPLY PromoCode'
class OrdersController < ApplicationController
# POST /events/:id/orders
def create
#event = Event.find(params[:event_id])
#order = #event.orders.new(title: #event.title, user: current_user order_amount: #event.price, order_currency: #event.currency)
begin
#promo = PromoCode.find_by!(uuid: params[:order][:promocode])
rescue ActiveRecord::RecordNotFound
#order.errors.add(:promocode, 'is invalid')
end
if #order.save
redirect_to checkout_create_path(id: #order.id)
else
redirect_to #event, alert: 'Something went wrong, try again later'
end
end
# ...
end
Other then that your handling of promo codes is very iffy. Instead of monkying around and deducting the rebate from the "amount" by updating the record you should store both the original sales price and the rebate and then calculate the total at checkout - which should also be stored separately. Not doing so amounts to pretty dismal record keeping and might get you in trouble - when it comes to money always play it safe.
So I am in the process of setting up a forum and everything is setup/working well except for my replies are not appearing on the thread "show" page. After checking the rails console, I see they are saving but the user_id and discussion_id are no. The user_id is always nil and the discussion_id is always 0. The discussion threads were easier to setup but with having these replies, I obviously seem to be having an issue. Here are my snippets of code:
class PostsController
# ...
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
def create
#post = #discussion.post.new(create_params) do |post|
post.user = current_user
end
if #post.save
redirect_to #discussion, notice: "It has been posted!"
else
render :new
end
end
def destroy
#post = #discussion.posts.find(params[:id])
#post.destroy
flash.notice = "Deleted"
redirect_to discussion_path(#discussion)
end
private
def create_params
params.require(:post).permit(:reply)
end
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
class DiscussionsController
def show
#discussion = Discussion.friendly.find(params[:id])
#post = Post.new
render :layout => 'discussion'
end
end
Partial rendered to reply:
<h2>Reply</h2>
<%= form_for [ #discussion, #post ] do |f| %>
<p>
<%= f.label :reply, "Reply" %><br/>
<%= f.text_field :reply %>
</p>
<p>
<%= f.submit 'Submit' %>
</p>
<% end %>
Partial rendered to show replies in on discussion page:
<h3><%= post.user.first_name %></h3>
<%= post.reply %>
Posted: <%= post.created_at.strftime("%b. %d %Y") %></p>
<p><%= link_to "Delete Comment", [post.discussion, post], data: {confirm: "Are you sure you wish to delete?"}, method: :delete, :class => "post_choices" %></p>
Just want to mention that I also have the correct associations between the three models (User, Discussion, Post). If there is more code needed, please let me know. I appreciate it very much for any information that may be helpful =)
Joe
EDIT
class User < ActiveRecord::Base
has_many :articles
has_many :discussions
has_many :posts
# ...
end
class Discussion
belongs_to :user
has_many :posts
extend FriendlyId
friendly_id :subject, use: :slugged
end
class Post
belongs_to :discussion
belongs_to :user
end
I could post the entire user model if needed but its all validations/devise aspects =P The other two I listed all of the contents in the models.
Edit 2
Thanks to Max, the user_id returns correctly in the console but still not the discussions. Going go dig around a bit more with the recent changes to see what else =)
There are a few issue you need to deal with.
First you should ensure that Devise is actually authorizing your controller action.
class PostsController < ApplicationController
before_filter :authenticate_user!
end
Otherwise current_user will return nil if there is no signed in user. And I'm
guessing that you do not want un-authenticated users to be able to create posts.
Also if you have a nested route you most likely want to check that the discussion actually
exists before trying to add posts.
class PostsController
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
private
# Will raise an ActiveRecord::NotFoundError
# if the Discussion does not exist
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
When you are creating resources be careful not to query the database needlessly.
This especially applies to CREATE and UPDATE queries which are expensive.
def create
#post = Post.create(post_params) # INSERT INTO 'users'
#post.discussion_id = params[:discussion_id]
#post.user = current_user
#post.save # UPDATE 'users'
flash.notice = "It has been posted!"
redirect_to discussions_path(#post.discussion)
end
Also you are not even checking if the record was created successfully.
So lets put it all together:
class PostsController
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
def new
#post = #discussion.post.new
end
def create
# new does not insert the record into the database
#post = #discussion.post.new(create_params) do |post|
post.user = current_user
end
if #post.save
redirect_to #discussion, notice: "It has been posted!"
else
render :new # or redirect back
end
end
def destroy
#post = #discussion.posts.find(params[:id])
#post.destroy
flash.notice = "Deleted"
redirect_to discussion_path(#discussion)
end
private
def create_params
# Only permit the params which the user should actually send!
params.require(:post).permit(:reply)
end
# Will raise an ActiveRecord::NotFoundError
# if the Discussion does not exist
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
New to Rails. New to OOP. I have a client and action_item model. An action item (a todo) has many and belongs to many clients. A client, has many action items. Essentially: A user, creates TODO's, from client pages.
User: creates a client (Crayola LLC, for ex) with crud.
User is then on the Client's show page (Crayola LLC's show page).
My question is, HOW TO have: User to be able to create an action item, for that client. Example: Call Crayola, to sell them an upgrade).
Created join table called action_items_clients, with foreign keys client_id, and action_item_id. Ran migration. Just have no idea how to facilitate creation of action items FOR clients. As it stands, action items can be created without clients. That's simple crud. This is where my novice understanding of rails hits roadblocks.
Action Items Controller:
class ActionItemsController < ApplicationController
def index
#action_items = ActionItem.all
end
def new
#action_items = ActionItem.new
end
def create
#action_item = ActionItem.new(action_items_params)
if #action_item.save
redirect_to(:action => 'show', :id => #action_item.id)
#renders client individual page
else
redirect_to(:action => 'new')
end
end
def edit
#action_item = ActionItem.find(params[:id])
end
def update
#action_item = ActionItem.find(params[:id])
if #action_item.update_attributes(action_items_params)
redirect_to(:controller => 'action_items', :action => 'show', :id => #action_item.id)
flash[:notice] = "Updated"
else
render 'new'
end
end
def show
#action_item = ActionItem.find(params[:id])
end
def action_clients
#action_clients = ActionItem.Client.new
end
def delete
#action_items = ActionItem.find(params[:id])
end
def destroy
#action_items = ActionItem.find(params[:id]).destroy
redirect_to(:controller => 'action_items', :action => 'index')
end
private
def action_items_params
params.require(:action_item).permit(:purpose, :correspondence_method, :know_person, :contact_name_answer, :additional_notes)
end
end
Clients controller
class ClientsController < ApplicationController
def index
#clients = Client.all
end
def new
#client = Client.new
end
def create
#client = Client.new(clients_params)
if #client.save
redirect_to(:action => 'show', :id => #client.id)
#renders client individual page
else
redirect_to(:action => 'new')
end
end
def edit
#client = Client.find(params[:id])
end
def update
#client = Client.find(params[:id])
if #client.update_attributes(clients_params)
redirect_to(:action => 'show', :id => #client.id)
else
render 'edit'
end
end
def show
#client = Client.find(params[:id])
end
def delete
#client = Client.find(params[:id])
end
def destroy
#client = Client.find(params[:id]).destroy
redirect_to(:controller => 'clients', :action => 'index')
end
private
def clients_params
params.require(:client).permit(:name)
end
end
Show page for each client:
<div align="center"><h1> <%= #client.name %> </h1></div>
<ol><li><%= link_to('Enter Definition Mode', :controller => 'action_items', :action => 'new', :id => #client.id) %></br></br></li>
<li><%= link_to('Back to client List', :controller => 'clients', :action => 'index') %> </li></br>
</ol>
The way I would do this is setup your routes so that action_items are nested under the client, something like so:
# /clients/13/action_items
resources :clients do
resources :action_items
end
Or if the user logging in is a client or only has one client, then you could skip that, and just have resources :action_items.
Then if you direct a user to /clients/13/action_items, then they will hit action_items#index, and params[:client_id] will be set to 13. You can use this to scope the action_items throughout that controller.
As long as you have the relationships setup between Client and ActionItem setup:
class Client < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :action_items
end
class ActionItem < ActiveRecord::Base
has_and_belongs_to_many :clients
end
It is probably also good to scope that to the currently logged in user:
class User < ActiveRecord::Base
has_many :clients
end
but it depends on how you want things to work. This is probably how I'd structure things:
class ActionItemsController < ApplicationController
before_action :get_client
def index
#action_items = #client.action_items.all
end
def new
#action_items = #client.action_items.new
end
def create
#action_item = #client.action_items.new(action_items_params)
if #action_item.save
redirect_to(:action => 'show', :id => #action_item.id, :client_id => #client.id)
else
redirect_to(:action => 'new')
end
end
# and other actions....
private
def get_client
#client = current_user.clients.find(params[:client_id])
end
end
EDIT (to address some commented questions):
If the action_items aren't always scoped to a client, they can live under both a nested and an un-nested route at the same time:
# /action_items
resources :action_items
resources :clients do
# /clients/13/action_items
resources :action_items
end
Then the before_action can be a bit more generic to set the owner to either the client, or the user itself (as long as User also has_and_belongs_to_many :action_items):
class ActionItemsController < ApplicationController
before_action :get_owner
def index
#action_items = #owner.action_items.all
end
# ... other stuff
private
def get_owner
if params[:client_id].present?
#owner = current_user.clients.find(params[:client_id])
else
#owner = current_user
end
end
end
Your redirects will probably need to take into account whether they came from a nested page or not, so you might have some logic like this around them:
def destroy
item = #owner.action_items.find(params[:id])
item.destroy
if params[:client_id]
redirect_to client_action_items_path(params[:client_id])
else
redirect_to action_items_path
end
end
Your link_tos will also have to change similarly, here's a link to the above destroy action:
<% if params[:client_id].present? %>
<%= link_to 'Delete action item', client_action_item_path(params[:client_id], #action_item), :method => 'delete' %>
<% else %>
<%= link_to 'Delete action item', #action_item, :method => 'delete' %>
<% end %>
I have Project and ProjectSetting models with following associations:
class Project < ActiveRecord::Base
has_one :project_setting
end
class ProjectSetting < ActiveRecord::Base
belongs_to :project
end
In projects_controller I have:
def show
#project = Project.find(params[:id])
#project_setting = #project.project_setting
end
So I'm using #project_setting form in #project show page and I need to update #project_setting from this page.
In project_settings_controller I have:
def update
#project = Project.find(params[:id]) #problem is here
#project_setting = #project.project_setting
if #project_setting.update_attributes(params[:project_setting])
respond_to do |format|
format.html { redirect_to project_path(#project) }
format.js
end
end
end
But #project variables in these controllers aren't the same:
In projects_controller#show it is Project with ID 26 and in project_settings_controller#update it finds Project with ID 1
So I need to pass #project variable from projects_controller#show to project_settings_controller#update.
Thanks for any help!
In your show.html.erb you can pass the variables back to any controller. For example
<%= link_to "Update project setting",
:controller => "project_settings",
:action => "update",
:project => #project %>
will send the parameter "project" filled with the #project variable.
If you are in a form tag, you can send the variable with a hidden field tag:
<% hidden_field_tag("project", #project) %>
I hope, this helps.
params[:id] in project_settings_controller contained #project_setting.id
If you want to get #project.id from params, you should to write in routes.rb nested path:
resources :projects do
resources :project_settings
end
And then project.id is available in params[:project_id].
Example in rails_guides
My application has two associated models: Magazine and Article:
class Magazine < ActiveRecord::Base
has_one :article
end
class Article < ActiveRecord::Base
belongs_to :magazine
validation_presence_of :title
end
From the Magazine show page I can create a new Article, so my routes.rb is configured like:
resources :magazines, :shallow => true do
resources :articles
end
and in the Magazine show page I have the link "New article", like:
<%= link_to 'New article', new_magazine_article_path(#article)
and an article helper to pass correct parameters to the form_for:
module ArticlesHelper
def form_for_params
if action_name == 'edit'
[#article]
elsif action_name == 'new'
[#magazine, #article]
end
end
end
so I can use Article form_for like:
<%= simple_form_for(form_for_params) do |f| %> ...
The ArticlesController methods for new and create are:
respond_to :html, :xml, :js
def new
#magazine = Magazine.find(params[:magazine_id])
#article = Article.new
end
def create
#magazine = Magazine.find(params[:magazine_id])
#article = #magazine.build_article(params[:article])
if #article.save
respond_with #magazine # redirect to Magazine show page
else
flash[:notice] = "Warning! Correct the title field."
render :action => :new
end
end
The problem occurs when there is a validation error with the title attribute, and the action new is rendered. In this moment I get the message: undefined method `model_name' for NilClass:Class in the first line of form_for. I think it is because the #magazine parameter passed in the helper.
How could I solve this problem withou use redirect_to ? (I want to mantain the other attributes that were filled in the form .)
Your form_for_params method is returning nil, because the action_name is set to 'create', not 'new' or 'edit'.
Try this:
elseif action_name == 'new' or action_name == 'create'
[#magazine, #article]