Working with nested routes and associations. I have a partial which creates a tenant, but after the creation it stays with the form rendered and the url changes to /tenants. Desired behavior is that it needs to redirect_to the show page. Routes are as follows:
Rails.application.routes.draw do
devise_for :landlords
authenticated :landlord do
root "properties#index", as: "authenticated_root"
end
resources :tenants
resources :properties do
resources :units
end
root 'static#home'
end
So far the properties and units work (and the landlord) Issue is with Tenants. Originally I had Tenants nested under units, but had issues there as well. Partial looks like this:
<%= form_for #tenant do |f| %>
<%= f.label "Tenant Name:" %>
<%= f.text_field :name %>
<%= f.label "Move-in Date:" %>
<%= f.date_field :move_in_date %>
<%= f.label "Back Rent Amount:" %>
$<%= f.text_field :back_rent %>
<%= f.button :Submit %>
<% end %>
<%= link_to "Cancel", root_path %>
Tenants Controller looks like this:
before_action :authenticate_landlord!
#before_action :set_unit, only: [:new, :create]
before_action :set_tenant, except: [:new, :create]
def new
#tenant = Tenant.new
end
def create
#tenant = Tenant.new(tenant_params)
if #tenant.save
redirect_to(#tenant)
else
render 'new'
end
end
def show
end
def edit
end
def update
if #tenant.update(tenant_params)
redirect_to unit_tenant_path(#tenant)
else
render 'edit'
end
end
def destroy
end
private
def set_property
#property = Property.find(params[:property_id])
end
def set_unit
#unit = Unit.find(params[:unit_id])
end
def set_tenant
#tenant = Tenant.find(params[:id])
end
def tenant_params
params.require(:tenant).permit(:name, :move_in_date, :is_late, :back_rent, :unit_id)
end
end
Models have associations:
class Tenant < ApplicationRecord
belongs_to :unit, inverse_of: :tenants
end
class Unit < ApplicationRecord
belongs_to :property, inverse_of: :units
has_many :tenants, inverse_of: :unit
end
Lastly the show#tenants in rake routes is:
tenant GET /tenants/:id(.:format) tenants#show
I have extensively searched for this topic, but haven't had any success. Any help is appreciated. Rails 5.1
The route you are showing near the end of your question:
tenant GET /tenants/:id(.:format) tenants#show
is not the tenants index; it is the individual tenants/show route. You can tell this because it includes :id, which means it will show you a specific tenant having that id.
Try running rake routes again. The index route should look like this:
tenants GET /tenants(.:format) tenants#index
If you want to return to the tenants index after creating or updating a Tenant record, then you need to specify that path in your TenantsController. In both the #create and #update actions, your redirect line (after if #tenant.save and if #tenant.update, respectively) should read:
redirect_to tenants_path
That will take you to the TenantsController, #index action.
In the alternative, if you want to return to the individual tenant show page, then instead change both of those redirects in the TenantsController in both the #create and #update actions to:
redirect_to tenant_path(#tenant)
That will take you to the TenantsController, #show action for the current #tenant.
Related
I have Categories (Parents) within which are listed Products (Children).
I want to be able to create a new Product directly from the navbar, anywhere in the app and then, during the creation, assign it to a Category.
However, I get the present error:
NoMethodError in Products#new
Showing /Users/istvanlazar/Mobily/app/views/products/new.html.erb where line #9 raised:
undefined method `products_path' for #<#<Class:0x00007febaa5aec98>:0x00007febae0f9e38>
Did you mean? product_show_path
## product_show_path is a custom controller that has nothing to do with this one,
enabling show and clean redirection from newest_products index bypassing categories nesting.
Extracted source (around line #9):
9 <%= form_for [#product] do |f| %>
10 <div class="form-styling">
11 <div>
12 <%= f.label :name %>
My App works as such:
Models
class Category < ApplicationRecord
has_many :products, inverse_of: :category
accepts_nested_attributes_for :products
validates :name, presence: true
end
class Product < ApplicationRecord
belongs_to :user, optional: true
belongs_to :category, inverse_of: :products
validates :category, presence: true
end
Routes
get 'products/new', to: 'products#new', as: 'new_product'
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :edit]
# resources :products, only: [:index, :show, :new, :edit]
end
Controllers
class ProductsController < ApplicationController
before_action :set_category, except: :new
def index
#products = Product.all
#products = policy_scope(#category.products).order(created_at: :desc)
end
def show
#product = Product.find(params[:id])
end
def new
#product = Product.new
#product.user = current_user
end
def create
#product = Product.new(product_params)
#product.user = current_user
if #product.save!
redirect_to category_product_path(#category, #product), notice: "Product has been successfully added to our database"
else
render :new
end
end
private
def set_category
#category = Category.find(params[:category_id])
end
def product_params
params.require(:product).permit(:name, :price, :description, :category_id, :user, :id)
end
end
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def show
#category = Category.find(params[:id])
end
def new
# Non-existant created in seeds.rb
end
def create
# idem
end
def edit
# idem
end
def update
# idem
end
def destroy
# idem
end
private
def category_params
params.require(:category).permit(:name, :id)
end
end
Views
# In shared/navbar.html.erb:
<nav>
<ul>
<li>Some Link</li>
<li>Another Link</li>
<li><%= link_to "Create", new_product_path %></li>
</ul>
</nav>
# In products/new.html.erb:
<%= form_for [#product] do |f| %>
<div class="form-styling">
<div>
<%= f.label :name %>
<%= f.text_field :name, required: true, placeholder: "Enter product name" %>
<%= f.label :price %>
<%= f.number_field :price, required: true %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div>
<%= f.label :category %>
<%= f.collection_select :category_id, Category.order(:name), :id, :name, {prompt: 'Select a Category'}, required: true %>
</div>
<div>
<%= f.submit %>
</div>
</div>
<% end %>
Do you have any idea of where it went wrong? Or is it impossible to Create a Child before assigning it to a Parent..?
Thanks in advance.
Best,
Ist
You haven't defined any route to handle your new product form's POST. You've defined the new_product path, but this arrangement is breaking Rails' conventions and you're not providing a work-around.
You could define another custom route, e.g. post 'products', to: 'products#create', as: 'create_new_product' and then set that in your form like form_for #product, url: create_new_product_path do |f|.
However, you should consider changing the structure so that product routes are not nested under categories. Think twice before breaking conventions this way.
Edit: I misread the intention, ignore this. Go with Jeremy Weather's answer.
Or is it impossible to Create a Child before assigning it to a Parent..?
With the way you have your relationships and validations set up: yes, it is. A Product requires a Category, through the validates :category, presence: true. And if you're on Rails 5+, the association will already be required by default (meaning that validates is actually redundant). Meaning if you try to create a Product without a Category, it will fail, and the Product will not be saved to the database.
With that constraint in place, here's what I recommend trying:
Remove the custom /products/new route
remove get 'products/new', to: 'products#new', as: 'new_product'
Add :new and :create routes to allow product creation nested under categories
Add :create and :new, to the :only array to the resources :products nested under resources :categories
Move the file views/products/new.html.erb to views/categories/products/new.html.erb (creating a new categories directory in views if necessary).
In that file, alter the form_for so that the form is POSTing to the nested :create route:
form_for [#category, #product] do |f|
Visit the URL /categories/[insert some Category ID here]/products/new, filling out some data, and see if that works.
Your routes.rb should look like this:
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :new, :create, :edit]
end
I am building a project management app.
My System is like- A project has many features, A feature has many tasks.
And the routes.rb is defined as
resources :projects do
resources :features do
resources :tasks
end
end
The first level , that is project to feature is working fine for new feature form , but when I try to implement new tasks form as ->
<%= form_for([#feature, #feature.tasks.build], class: "form-group row") do |form| %>
<%= form.label :name %>
<%= form.text_field :name, required:true, class: "form-control" %>
<%= form.submit class: "btn btn-primary m-2" %>
<% end %>
Now its showing error as
Here is my models->
class Task < ApplicationRecord
belongs_to :feature
end
class Feature < ApplicationRecord
belongs_to :project
has_many :tasks
end
And the controller for tasks looks like->
class TasksController < ApplicationController
before_action :set_feature
def new
#task = #feature.tasks.new
end
def create
#task = #feature.tasks.new(task_params)
if #task.save
redirect_to project_features_path
else
render :new
end
end
def edit
end
def update
if #task.update(task_params)
redirect_to project_feature_tasks_path
else
render :edit
end
end
def complete
#task.update(completed: true)
redirect_to project_feature_tasks_path
end
def destroy
#feature.task.destroy
redirect_to project_feature_tasks_path
end
private
def set_feature
#feature = Feature.find(params[:feature_id])
end
def task_params
params.require(:task).permit(:name,:completed, :project_id,:feature_id)
end
end
Any help is very much appreciated - I am stuck with this error for days now.
If you try running $ rails routes you can see why your current routes are failing you.
Prefix Verb URI Pattern Controller#Action
project_feature_tasks GET /projects/:project_id/features/:feature_id/tasks(.:format) tasks#index
POST /projects/:project_id/features/:feature_id/tasks(.:format) tasks#create
new_project_feature_task GET /projects/:project_id/features/:feature_id/tasks/new(.:format) tasks#new
edit_project_feature_task GET /projects/:project_id/features/:feature_id/tasks/:id/edit(.:format) tasks#edit
project_feature_task GET /projects/:project_id/features/:feature_id/tasks/:id(.:format) tasks#show
PATCH /projects/:project_id/features/:feature_id/tasks/:id(.:format) tasks#update
PUT /projects/:project_id/features/:feature_id/tasks/:id(.:format) tasks#update
DELETE /projects/:project_id/features/:feature_id/tasks/:id(.:format)
You would have to call:
form_for([#project, #feature, #feature.tasks.build], ...) do |form|
A better idea is to unnest the routes. You can do this by using the shallow option:
resources :projects do
resources :features, shallow: true do
resources :tasks
end
end
Or you can do it like so if you for some reason want to keep the member routes (show, edit, update, destroy) for features nested:
resources :projects do
resources :features
end
resources :features, only: [] do
resources :tasks
end
I have set up a polymorphic association between Event, Meeting, and TaskList such that:
An Event can have many TaskLists, A TaskList can belong to one Event.
A Meeting can have many TaskLists, A TaskList can belong to one Meeting.
This works and I can create task_lists from the view of these models. However my problem is that I want to be able to add TaskItem to each TaskList such that:
A TaskList can have many TaskItems, A TaskItem can belong to one TaskList.
I'm having trouble routing the form for the creation of a task item. I've created a "_form.html.erb" for this and am rendering it from the view of the task_item. I'm using the form below for this, at the moment from the events view, which shows the form fine but throws the routing error "No route matches [POST] "/events/3/task_lists/new.3" when clicked on submit.
_form.html.erb
<%= form_for TaskItem.new, url: new_polymorphic_path([#listable, #task_list, #task_item]) do |f| %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I have also tried setting it up like below which doen't even show the form throwing the error "First argument in form cannot contain nil or be empty"
<%= form_for #task_item do %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Models
class TaskList
belongs_to :listable, polymorphic: true
has_many :task_items
end
class TaskItem
belongs_to :task_list
end
class Event
has_many :task_lists, as: :listable
end
class Meeting
has_many :task_lists, as: :listable
end
Routes (added :show to task_lists, only: , as my link wouldnt work otherwise.)
concern :has_task_lists do
resources :task_lists, only: [:new, :index, :create, :show]
end
resources :events, :meetings, concerns: [:has_task_lists]
resources :task_lists, except: [:new, :index, :create] do
resources :task_items
end
task_items_controller (want it to redirect to the page item was created from, which is the show view for task_list)
def create
#task_item = #task_list.task_items.new(task_item_params)
if #task_item.save
redirect_to #task_list, notice: "Task Item Created"
else
render :new
end
end
task_lists_controller
before_action :load_listable
def show
#task_list = #listable.task_lists.find(params[:id])
end
def load_listable
klass = [Event, Meeting].detect { |c| params["#{c.name.underscore}_id"]}
#listable = klass.find(params["#{klass.name.underscore}_id"])
end
One of the key issues here is that task_items are nested a level too deep.
Rule of thumb: resources should never be nested more than 1 level
deep. A collection may need to be scoped by its parent, but a specific
member can always be accessed directly by an id, and shouldn’t need
scoping.
- Jamis Buck
So to untangle this we would declare the routes like so:
concern :has_task_lists do
resources :task_lists, only: [:new, :index, :create]
end
resources :events, :meetings, concerns: [:has_task_lists]
resources :task_lists, except: [:new, :index, :create] do
resources :task_items
end
The routing concern lets us re-use the nested routes.
Since we have gotten rid of the extra "parent" we can simplify the form:
form_for([ #task_list, #task_item || #task_list.task_items.new ])
#task_item || #task_list.task_items.new will prevent errors if you embed the form in another controller / action.
Added
You actually have to create the resource scoped to the parent. Just doing form_for([#a, #b]) does not automatically associate the records - it just generates the url and sets the form method (POST or PATCH).
class TaskItemsController < ApplicationController
before_action :set_task_list
def new
#task_item = #task_list.task_items.new
end
def create
#task_item = #task_list.task_items.new(task_item_params)
if #task_item.save
redirect_to task_list_path(#task_list), notice: "Task Item Created"
else
render :new
end
end
private
def set_task_list
#task_list = TaskList.find(params[:task_list_id])
end
# ...
end
When you call #task_list.task_items.new you are calling the .new method on the collection - so the new TaskItem will have the task_list_id column set.
Note that you can also write redirect_to #task_list and rails will figure the rest out. If you need to redirect to a nested resource such as the newly created item you would do:
redirect_to [#task_list, #task_item]
I have two user types: Artist and Fan. I want Fans to be able to follow Artists. So far following them does not work, but unfollowing does. I have create and destroy set up the same way, but can't seem to get it to work. I get the error Couldn't find Artist without an ID when trying to create a Relationship. Anyway I can find the Artist's ID?
Code below:
relationships_controller.rb
class RelationshipsController < ApplicationController
before_action :authenticate_fan!
def create
#relationship = Relationship.new
#relationship.fan_id = current_fan.id
#relationship.artist_id = Artist.find(params[:id]).id #the error
if #relationship.save
redirect_to (:back)
else
redirect_to root_url
end
end
def destroy
current_fan.unfollow(Artist.find(params[:id]))
redirect_to (:back)
end
end
artists_controller.rb
def show
#artist = Artist.find(params[:id])
end
artists/show.html.erb
<% if fan_signed_in? && current_fan.following?(#artist) %>
<%= button_to "unfollow", relationship_path, method: :delete, class: "submit-button" %>
<% elsif fan_signed_in? %>
<%= form_for(Relationship.new, url: relationships_path) do |f| %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
<% end %>
models/fan.rb
has_many :relationships, dependent: :destroy
has_many :artists, through: :relationships
belongs_to :artist
def following?(artist)
Relationship.exists? fan_id: id, artist_id: artist.id
end
def unfollow(artist)
Relationship.find_by(fan_id: id, artist_id: artist.id).destroy
end
models/artists.rb
has_many :relationships, dependent: :destroy
has_many :fans, through: :relationships
belongs_to :fan
routes.rb
resources :relationships, only: [:create, :destroy]
Basically, you need to send artist_id to the action. Change your form to this. There is a lot of refactoring required but this one will work for you:
<%= form_for(Relationship.new, url: relationships_path) do |f| %>
<%= hidden_field_tag :artist_id, #artist.id %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
In controller, you can access it like:
#relationship.artist_id = Artist.find(params[:artist_id]).id
I would consider solving this with a nested route instead:
resources :artists, shallow: true do
resources :relationships, only: [:create, :destroy]
end
this will create these routes in addition to the regular CRUD routes for artist:
Prefix Verb URI Pattern Controller#Action
artist_relationships POST /artists/:artist_id/relationships(.:format) relationships#create
relationship DELETE /relationships/:id(.:format) relationships#destroy
Notice that we use shallow: true which scopes the create route under /artists/:artist_id but not the destroy route.
You can then change your form:
<%= form_for(Relationship.new, url: artist_relationships_path(artist_id: #artist)) do |f| %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
And your controller:
class RelationshipsController < ApplicationController
before_action :authenticate_fan!
def create
current_fan.relationships.build(artist: Artist.find(params[:artist_id]))
if #relationship.save
redirect_to(:back)
else
redirect_to root_url # makes more sense to redirect back to #artist ?
end
end
def destroy
#relationship = current_fan.relationships.find(params[:id])
#relationship.destroy
redirect_to(:back)
# or redirect back to the artist page.
# redirect_to #relationship.artist
end
end
Notice how we also refactor the destroy action - You never want to have a route with an :id param which points to a completely different resource. Thats just poor REST design. We don't even need to know the artist ID if we know the id of a relationship. Instead here the ID refers to the Relationship resource.
To create a link to destroy the relationship you would do:
<%= link_to 'Unfollow', relationship_path(#relationship), method: :delete %>
And lets get rid of the Fan#unfollow method.
While we are at it we can fix the Fan#following? method.
def following?(artist)
relationships.exists?(artist: artist)
end
By using the relationship (in the ActiveRecord sense of the word!) instead of querying the Relationship model directly you can use eager loading to avoid additional queries and also you don't have to specify the fan.
I have 3 models:
event
vendor
vendor_relationship
Every event has multiple vendors through that relationship.
Now I want to create a form at /events/1/add_vendors which creates the relationship AND creates the vendor model.
How would I go about doing this?
Thanks for the help!
ensure that you're Event model looks something like this:
attr_accessible :vendor_relationships, :vendor_relationships_attributes
has_many :vendor_relationships
has_many :vendors, :through => :vendor_relationships
accepts_nested_attributes_for :vendor_relationships
your VendorRelationship model looks something like this:
attr_accessible :vendors, :vendors_attributes
has_many :vendors
accepts_nested_attributes_for :vendors
in your EventController:
#event = Event.new(params[:event])
and in your create view, something like:
<% form_for Event.new do |f| %>
<%= f.text_field, :field_for_the_event %>
<% f.fields_for :vendor_relationships do |rf| %>
<%= rf.text_field, :price_maybe? %>
<% rf.fields_for :vendor do |vf| %>
<%= vf.text_field, :name_and_so_on %>
<% end %>
<% end %>
<% end %>
That's one way. Another, probably better user experience would be to allow for selection of vendor from existing vendors or create new. Create new would ajax creation to the VendorController, and on creation, return the vendor's information to the form. Saving the relationship would ajax a call to create the vendor_relationship and display the result.
Hope that sends you down the right direction.
# routes.rb
resources :events do
resources :vendors, :path_names => { :new => 'add_vendors' }
end
# vendors_controller.rb
before_filter :load_event
before_filter :load_vendor, :only => [:edit, :update, :destroy]
def load_vendor
#vendor = (#event ? #event.vendors : Vendor).find(params[:id])
end
def load_event
#event = params[:event_id].present? ? Event.find(params[:event_id]) : nil
end
def new
#vendor = #event ? #event.vendors.build : Vendor.new
...
end
def create
#vendor = #event ? #event.vendors.build(params[:vendor]) : Vendor.new(params[:vendor])
...
end
def edit
...
end
def update
...
end
def destroy
...
end
# View form
<%= form_for([#event, #vendor]) do |f| %>
...
<% end %>