Using collection_select view helper for objects in a HABTM relationship - ruby-on-rails

The models project and category are in a has_and_belongs_to_many relationship. The partial seen below is used on different views to show a dropdown-menu with all the available categories. The projects in the list below the dropdown-menu are shown according to the choice the user made in the dropdown-menu.
Besides other categories, there is a category named "Everything" that every project is member of. It's also the first entry in the category-db-table, because it got inserted in while loading the migrations into the database.
Right now there is no error, but regardless of which category I choose it reloads the page showing the "Everything" category.
Any idea what I need to change in the code mentioned below to make it work the way I want to? Thanks for your help!
Partial with dropdown-menu and project-list
<!-- category dropdown -->
<% form_for category_url(:id), :html => {:method => :get} do |f| %>
<label>Categories</label>
<%= f.collection_select(:category_ids , Category.find(:all), :id , :name) %>
<%= f.submit "Show" %>
<% end %>
<!-- project list -->
<ul class="projectlist">
<% #projects.each do |_project| %>
<li>
<%= link_to(_project.name, _project) %>
</li>
<% end %>
</ul>
Logoutput after choosing category with id 2 on the dropdown-menu
Processing ProjectsController#index (for 127.0.0.1 at 2009-02-20 17:26:10) [GET]
Parameters: {"commit"=>"Show", "http://localhost:3000/categories/id"=>{"category_ids"=>"2"}}
Category Model
class Category < ActiveRecord::Base
has_and_belongs_to_many :projects, :join_table => "categories_projects"
end
Categories Controller
class CategoriesController < ApplicationController
def show
#projects = Category.find(params[:id]).projects.find(:all)
respond_to do |format|
format.html # show.html.erb
end
end
end
Project Model
class Project < ActiveRecord::Base
has_and_belongs_to_many :categories, :join_table => "categories_projects"
end
Projects Controller
class ProjectsController < ApplicationController
def show
#projects = Project.find(:all)
#project = Project.find(params[:id])
respond_to do |format|
format.html # show.html.erb
end
end
def index
#projects = Project.find(:all)
respond_to do |format|
format.html # index.html.erb
end
end
end
part of 'rake routes' output
category GET /categories/:id {:controller=>"categories", :action=>"show"}

You're passing a parameter called :category_ids, but you're not accessing that anywhere.
form_for category_url(:id)
This will submit your form to the path /categories/1 or whatever id you're currently viewing. You're then using that :id for finding your category projects:
#projects = Category.find(params[:id]).projects.find(:all)
So you're just showing the same ones over again. Because it's a form, you're submitting a query with the :category_ids parameter:
POST /categories/1?category_ids=2
You could just change your Category.find to use the other parameter instead. But, normally to view category 2 you would just use the url /categories/2, where 2 is your :id parameter. You have two ids in that path, and you should decide how you want to resolve that.
One option is to use the categories_path for the form action, and change the collection_select :category_ids parameter to just :id:
/categories?id=2 # from the form
/categories/2 # from a link
But if you're just listing projects, I would move this logic into the projects controller (index action), so your URLs would look like:
/projects?category_id=2` # from the form
/categories/2/projects # from a link

Thanks Andrew, but I solved it myself this way:
I got rid of collection_select, changed to select, added the filter-action (with the according route in config/routes.rb) and now everything works as I expected.
...I'm trying to get an observer on the select-menu, that submits it's value to the filter-action as soon as the user changes the selection in the select-menu, but that's another story. ;-)
New partial with dropdown-menu and project-list
<% form_tag(filter_category_path(:id), :method => :post, :class => 'categories') do %>
<label>Categories</label>
<%= select_tag(:category, options_for_select(Category.all.map {|category| [category.name, category.id]}, #category.id)) %>
<%= submit_tag "Go" %>
<% end %>
<ul class="projects">
<% #projects.each do |_project| %>
<li>
<%= link_to(_project.name, _project) %>
</li>
<% end %>
</ul>
New categories controller
class CategoriesController < ApplicationController
def show
#category = Category.find(params[:id])
#projects = #category.projects.find(:all)
respond_to do |format|
format.html
end
end
def filter
#category = Category.find(params[:category])
#projects = #category.projects.find(:all)
respond_to do |format|
format.html
end
end
end

Related

How to refer back to associated index with link_to

Doing nested routes for the first time and I cannot figure out the link_to paths for the nested routes that I have. Using rails scaffold for each
resources :venues do
resources :events
end
I edited to have the following in the index.html.erb of the venue model
<%= link_to 'Events', venue_events_path(venue) %>
Which brings me to the correct index.html.erb of the events model (venues/1/events)
But I just can't seem to figure out the correct link_to path on the show page of event to bring me back to venues/1/events
With the default scaffold, its
<%= link_to 'Back', events_path %>
I have tried different paths like venue_events_path(venue) but rails keeps saying that it cannot find venue without an ID.
I am assuming that is because clicking on the default show link brings me to localhost:3000/events/1 instead of localhost:3000/venues/1/events/1
What would be the correct paths for nested attributes besides the difficulty that I am facing?
For nested routes I would use the following setup:
# routes.rb
resources :venues do
resources :events
end
# venues controller
class VenuesController < ApplicationController
def index
#venues = Venue.all
end
def show
#venue = Venue.find(params[:id])
end
end
# venues index.html.erb
<% #venues.each do |venue| %>
<%= link_to 'Venue', venue_path(venue) %>
<% end %>
# venues show.html.erb
# link to the events index page from the current venue
<%= link_to 'Events', venue_events_path(#venue) %>
# link to each individual event from the current venue
<% #venue.events.each do |event| %>
<%= link_to 'Event', venue_event_path(#venue, event)
<% end %>
# events controller
class EventsController < ApplicationController
def index
#venue = Venue.find(params[:venue_id])
#events = #venue.events
end
def show
#event = Event.find(params[:id])
end
end
# events index.html.erb
# link back to venue
<%= link_to 'Venue', venue_path(#venue) %>
# link to each event
<% #events.each do |event| %>
<%= link_to 'Event', venue_event_path(event.venue, event) %>
<% end %>
# events show.html.erb
# link back to events index
<%= link_to 'Events', venue_events_path(#event.venue) %>
# link back to venue show
<%= link_to 'Venue', venue_path(#event.venue) %>
Keep in mind that this will only work if your events have an venue id. You said you have in your event model: belongs_to :venue, optional: true the optional true make the venue_id not required for an event. If that is what you want then nested routes doesn't really makes sense, because an NOT nested event is not going to be linked nested.

Multiple objects with one form and permitting parameters

My rails app has a games model, and each game has multiple players. When a game is created, a set number of players are created with a default name like so:
def create
#game = Game.new(game_params)
#game.player_number.times do
#game.players << Player.new(name: 'Santa')
end
if #game.save
redirect_to action: "players", id: #game.id
else
render 'new'
end
end
The redirect takes the user to a page that has a form with inputs for each player's name. The actions associated with this page are:
def players
#game = Game.find(params[:id])
end
def playersUpdate
#game = Game.find(params[:id])
puts player_params
if #game.players.update(player_params)
redirect_to #game
else
render 'players'
end
end
private
def player_params
params.require(players: [:name])
end
The editing page itself is:
<h2> Edit Players </h2>
<%= form_tag({:action => 'playersUpdate'},{:id => #game.id}) do %>
<%= #game.players.count %>
<% #game.players.each.with_index do |player,index| %>
<%= fields_for "players[#{index}]", player do |pl| %>
<div>
<%= pl.label :name %><br>
<%= pl.text_field :name %><br>
<%= pl.hidden_field :id %>
</div>
<% end %>
<% end %>
<div>
<%= submit_tag %>
</div>
<% end %>
Here's the routes.rb:
Rails.application.routes.draw do
get 'welcome/index'
resources :games do
collection do
match "/:id/players" => "games#players", :via => :get
match "/:id/players" => "games#playersUpdate", :via => :post
end
end
root 'welcome#index'
end
I get an error:
param is missing or the value is empty: {:players=>[:name]}
And I'm at a loss for what I could be missing. Any tips?
Here are the parameters being passed in, George is the name I'm trying to edit in, all others default to 'Santa':
Processing by GamesController#playersUpdate as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"wNwt9v2ckO/Bl8YGr/a2CDCjSsRec30E51VjZ/Qv2i5BgEnzVbH5M9DsrVfCxdLusS4Ue6Mq+aPSFOiA4K5jJg==", "players"=>{"0"=>{"name"=>"George", "id"=>"122"}, "1"=>{"name"=>"Santa", "id"=>"123"}, "2"=>{"name"=>"Santa", "id"=>"124"}, "3"=>{"name"=>"Santa", "id"=>"125"}}, "commit"=>"Save changes", "id"=>"22"}
You are not breaking any new ground here, and Rails has standard ways to do all of this. But it's easy to get "off the Rails" and make it harder than it needs to be.
Conceptually, stop thinking about updating a bunch of Players, and start thinking about updating a Game that happens to have some Players. The docs are surprisingly helpful here.
Let's go first to your Game model. You'll need to tell it that it's OK to update nested attributes for players, like this:
# models/game.rb
model Game < ApplicationRecord
has_many :players
accepts_nested_attributes_for :players
end
Your view is generating parameters that are not quite standard. Again, let Rails do the work for you. You don't need hidden fields or each_with_index. Since we're in Rails 5, let's use the new form_with helper, and we'll let fields_for do its job without our trying to tell it how to index:
# views/games/edit_players.html.erb
<h2> Edit Players </h2>
<%= form_with(model: game, local: true) do |form| %>
<div>
Game name: <%= form.text_field :name %>
</div>
<%= form.fields_for :players do |player_fields| %>
<div>
Player name: <%= player_fields.text_field :name %><br>
</div>
<% end %>
<div>
<%= form.submit %>
</div>
<% end %>
This will generate params that look something like this:
Parameters: {"game"=>
{"name"=>"Risk",
"players_attributes"=>
{"0"=>{"name"=>"Abel", "id"=>"1"},
"1"=>{"name"=>"Baker", "id"=>"2"},
"2"=>{"name"=>"Charlie", "id"=>"3"}}},
"commit"=>"Update Game",
"id"=>"1"}
Now you don't even need a custom update endpoint. Just use your standard GamesController#update action:
# controllers/games_controller.rb
class GamesController < ApplicationController
...
def edit_players
#game = Game.find(params[:id])
end
def update
#game = Game.find(params[:id])
if #game.update(game_params)
redirect_to #game
else
render :edit
end
end
private
def game_params
params.require(:game).permit(:name, players_attributes: [:id, :name])
end
end
Finally, your routes file is confusing because you are using collection (which doesn't expect an :id) instead of member. The routes file should look something like this:
# routes.rb
Rails.application.routes.draw do
resources :players
resources :games do
member { get :edit_players }
end
end

Category Names Displaying Within View In Rails

Trying to do: I have an application that has 3 business categories: Business Development, Business Financial, And Business Admin.
I have an index view, localhost:3000/categories, which displays links to all of the above business categories.
They link to the new action, and the new view. Within that view, I have the following form code:
New.html.erb
<div align="center">
<h1>What are your important <%= #category.bizdev %> Action items?</h1>
<%= form_for #category do |f| %>
<p>
<p>Store Answer Below:</p>
<%= f.text_field :name, :size => 40, :style => 'height: 40px' %>
</p>
<p>
<%=f.submit 'Save action item' %>
</p>
<% end %> </div>
On the line: <h1>What are your important <%= #category.bizdev %> Action items?</h1>
I am trying to have the line list 1 of the 3 business categories (Business Development, Business Financial, Business Admin).
I've tried to create relationships between models: name, and category.
I cannot get the respective business category to display on the new view, for the form. Then, I need the form to submit the data to the respective category view (which I've been trying to do).
Categories controller:
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def new
#category = Category.new
#bizdev = Name.new
Name.#bizdev = "Business Development"
end
def create
#category = Category.new(category_params)
#category.save
redirect_to facilitates_path
end
def show
#category = Category.find(params[:id])
end
def destroy
end
private
def category_params
params.require(:category).permit(:answer)
end
end
Name Model
class Name < ActiveRecord::Base
belongs_to :category
end
CategoriesHelper
module CategoriesHelper
def bizdev
bizdev = Name.new
Name.bizdev = "Business Development"
end
end
Category Model
class Category < ActiveRecord::Base
has_many :names
end
Index View
<h1>Select A Business Category To Begin Identifying Action Items</h1>
<ol><li><%= link_to 'Business Admin', 'new' %></li><br><br>
<li><%= link_to 'Business Development/Marketing', 'new' %></li><br><br>
<li><%= link_to 'Financial', 'new' %></li>
</ol>
<%= link_to 'Store random action items', new_facilitate_path %><br><br>
<%= link_to 'See a list of already stored action items', facilitates_path %>
Routes.rb
Rails.application.routes.draw do
resources :facilitates
resources :categories
root 'categories#index'
get 'show' => 'facilitates#show'
get 'index' => 'categories#index'
get 'new' => 'categories#new'
get 'bizadmstor' => 'categories#bizadmstor'
get 'bizdevstor' => 'categories#bizdevstor'
get 'bizfinstor' => 'categories#bizfinstor'
get 'bizadmshow' => 'categories#bizadmshow'
get 'categories/show' => 'categories#show'
get 'categories/new' => 'categories#new'
This Name.#bizdev obviously wrong. I guess you are trying to do next: #category.names << #bizdev.
Helper also incorrect:
bizdev = Name.new
bizdev.name = "Business Development" # i guess you have name property of this model
In general - Name is not an Instance of the class, it's class itself. And you can define its attributes, you can do this only for some instance of a class, like bizdev in this example.
In your example, you don't need a class Name, you need a property name for the class Category. Or if you what that it should be a class, also it should have property name, to return it. This #category.bizdev will return a class, not some value. Should be #category.bizdev.name or #category.bizdev if bizdev is a property.
Figured it out myself - created, in Category model:
def name
"Business Development"
end
And then in view, called: #category.name
So - I had to create a method within my category model, called name, that simply writes "Business Development." Then, I called it via: #category.name in the new view.

Rails - updating div w/ Ajax and :remote => true

I've followed this Railscast on submitting a form via Ajax and updating a div without reloading the page, but I'm having trouble with one portion of it.
Ryan has $("#products").html("<%= escape_javascript(render(#products)) %>"); in an index.js.erb file to update the #products div when the form is submitted. I'm struggling to understand whether an instance variable like #products is relevant for my situation, or if I can simply substitute it for a URL.
I'm trying to do the same thing as Ryan in this screencast, but instead of display search results I just want to display the updated value.
In show.html.erb I have:
<% #project.project_todos.order("created_at DESC").where(:status => false).each do |todo|%>
<%= form_for todo, :remote => true, :"data-replace" => "#dueon" do |f| %>
<%= f.text_field :due %>
<%= f.submit :class => "primary", :value => "Add" %>
<% end %>
<div id="dueon">
<%= render "duedate", :todo => todo %>
</div>
<% end %>
The partial _duedate.html.erb has one line in it: <%= todo.due %>
So in my index.js.erb I currently have this: $("#dueon").html("<%= escape_javascript(render("_duedate")) %>"); but it's clearly not working. Do I have to use a variable here in place of the _duedate? And if so, how would I set this up in the controller? I mean what does the variable have represent?
Also, for what it's worth, the partial is rendering correctly and displaying the todo.due value...it's just not updating when I submit the form.
ProjectsController:
def show
#project = Project.find(params[:id])
# Display the form to create a new todo
#project_todo = ProjectTodo.new
respond_to do |format|
format.html # show.html.erb
format.json { render :json => #project }
end
end
Try this
in you controller action , (say sample_action)
def sample_action
#todos = #your code
respond_to do |format|
format.js
end
end
and you have a sample_action.js.erb
$("#dueon").html("<%= raw escape_javascript(render(:partial => 'duedate')) %>")
then inside the partial, you have access to the new #todos instance variable
HTH
I will answer you separately as I believe your entire setup should be little change (IMO, this might not be the case)
I think you should have a todos controller with a project belongs to it,
--- models ----------------
class Project < ActiveRecord::Base
has_many :todos
end
class Todo < ActiveRecord::Base
belongs_to :project
end
---- routes ---------------
resources :projects do
resources :todos do
end
end
---- controllers ----------
class ProjectsController < ApplicationController
end
class TodosController < ApplicationController
def new
#project = Project.find(params[:project_id])
#todos = #project.todos.build
end
end
in your view (views/todos.html.erb)
<%= #project.name %>
<%= form_for([#Project, #todos]) do |f| %>
#todo form code
<% end%>
As per the relation, project has many todos, its always clear to show the project details in the todo add screen, rather than allowing users to add new todos from project screen.
and again, this is my personal view, feel free to ask any questions :)

rails restful select_tag with :on_change

So I'm finally starting to use rest in rails.
I want to have a select_tag with product categories and when one of the categories is selected I want it to update the products on change.
I did this before with
<% form_for :category, :url => { :action => "show" } do |f| %>
<%= select_tag :id, options_from_collection_for_select(Category.find(:all), :id, :name),
{ :onchange => "this.form.submit();"} %>
<% end %>
however now it doesn't work because it tries to do the show action.
I have two controllers
1) products
2) product_categories
products belongs_to product_categories with a has_many
How should I do this.
Since the products are listed on the products controller and index action should I use the products controller or should I use the product_categories controller to find the category such as in the show action and then render the product/index page.
But the real problem I have is how to get this form or any other option to work with restful routes.
First routes - you need to define product as resource, so you would have helper methods: edit_product_path(#product), new_product_path and so on:
# routes.rb
map.resource :products
Then controller - standard resource controller:
# products_controller.rb
def edit
#product = Product.find(params[:id])
end
def update
#product = Product.find(params[:id])
if #product.update_attributes(params[:product])
flash[:notice] = "Product updated"
redirect_to #product
else
render :edit
end
end
And now view - it's easier to user form_for to build form for specific object (new or existing). For new object it points by default to #create action on controller for this resource and for existing points to #update. You can always override this paths if needed:
# products/new.html.erb
<% form_for #product do |f| %>
<%= f.select :product_category, Category.all, {}, {:onchange => "this.form.submit();" %>
<% end %>

Resources