How to load all associations across all models - ruby-on-rails

I'm making a simple website to learn RoR. It allows users to post reviews for movies, but I want to implement a link in my root view that shows ALL of the reviews in the database. How do I do that?
I want to be able to <%= link_to 'Reviews', reviews_path %> but my reviews#index URI pattern is /movies/:movie_id/reviews
What do I put in my reviews model in order to extract all the reviews in the database?
My reviews controller:
class ReviewsController < ApplicationController
def create
#movie = Movie.find(params[:movie_id])
#review = #movie.reviews.create(review_params)
redirect_to movies_path(#movie)
end
private
def review_params
params.require(:review).permit(:email, :comment)
end
end
Review model:
class Review < ApplicationRecord
belongs_to :movie
end
And my root view:
<h3>All Reviews:</h3>
<!-- put link here -->
<h3>Sort By:</h3>
<p>
<%= link_to 'Release', movies_path(:sort_param => "release") %>
<%= link_to 'Title', movies_path(:sort_param => "title") %>
</p>
<h1>Popular Movies</h1>
<% #movies.each do |m| %>
<h2>Title</h2>
<%= m.title %>
<%= m.release %>
<%= link_to 'Show', movie_path(m) %>
<% end %>
Edit: I looked at this solution but I'm not sure where to put that code. I tried to put it in the Review's index method but I got an error:
Couldn't find all Reviews with 'id': (all, {:order=>"created_at DESC", :limit=>10})

In your routes.rb
resources :reviews, only: [:index]
reviews_controller.rb
If you want to display all the reviews from any movie, in most recent order, you could do:
class ReviewsController < ApplicationController
def index
#reviews = Review.order('created_at DESC').limit(10)
end
def create
...
end
end

You can add a route to reviews only in your routes.rb file.
resources :reviews, only: [:index]

Related

Rails not directing to show page after user submit

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.

How do I make my objects less messy?

My project has three main parts to it:
Pages (similar to articles)
Categories (the pages have a category associated with them)
Tags (each Page can have several different tags)
I have a sidebar which uses #categories to pull through a list of all the current categories in my project:
<div class="col-md-3">
<p class="lead">Categories</p>
<div class="list-group">
<%= link_to 'All articles', pages_path(#page), :class => 'list-group-item' %>
<% #categories.each do |category| %>
<%= link_to category.name, category_path(category), :class => 'list-group-item' %>
<% end %>
</div>
</div>
But currently I need to include
#categories = Category.all
In my index and show actions in my controllers for both pages and categories so that the sidebar loads (I only use the sidebar in these two parts of the project).
Is there an easier way of doing this than including the above code in every action in the controller?
Also with the tags controller after creating a page and going to the tag's show page to view any pages associated with those tags, I get an error saying 'Couldn't find page with 'id'=2.
class TagsController < ApplicationController
def index
#tags = Tag.all
end
def show
#tag = Tag.find(params[:id])
#page = Page.find(params[:id])
#categories = Category.all
end
-
<% #tag.pages.each do |page| %>
<div class="thumbnail">
<div class="text">
<article class="clearfix">
<%= link_to page.title, url_for_page(page), class: "h1" %>
<p class="pull-right"><span class="glyphicon glyphicon-time"></span> Posted on <%= page.created_at.to_formatted_s :long %></p>
<hr />
<%= page.body.html_safe %>
<hr />
<div class="btn-group btn-group-xs" role="group" aria-label="...">
<% page.tags.each do |tag| %>
<%= link_to tag.name, tag_path(tag), class: "btn btn-info" %>
<% end %>
</div>
</article>
</div>
</div>
<% end %>
Anyone got any ideas? Any help would be greatly appreciated :)
Thanks!
Update:
Routes file:
Rails.application.routes.draw do
resources :categories
resources :pages
resources :tags
Models:
Category.rb
class Category < ActiveRecord::Base
has_many :pages
Page.rb
class Page < ActiveRecord::Base
include Bootsy::Container
belongs_to :category
has_many :taggings
has_many :tags, through: :taggings
def tag_list
self.tags.collect do |tag|
tag.name
end.join(", ")
end
def tag_list=(tags_string)
tag_names = tags_string.split(", ").collect{ |s| s.strip.downcase }.uniq
new_or_found_tags = tag_names.collect { |name| Tag.find_or_create_by(name: name) }
self.tags = new_or_found_tags
end
end
Tag.rb
class Tag < ActiveRecord::Base
include Bootsy::Container
has_many :taggings
has_many :pages, through: :taggings
def to_s
name
end
end
Tagging.rb
class Tagging < ActiveRecord::Base
include Bootsy::Container
belongs_to :tag
belongs_to :page
end
You could add a before_action for the controller limited to only index and show actions, as below:
class TagsController < ApplicationController
before_action :load_categories, only: [:index, :show]
# Your code
private
def load_categories
#categories = Category.all
end
end
That will load the categories into the instance variable for both the index and show actions.
For the error you're getting, if I'm reading it right you have nested routes? You need to be getting the correct ID for the tag, which is :tag_id:
def show
#tag = Tag.find(params[:tag_id])
#page = Page.find(params[:id])
#categories = Category.all
end
You were getting :id for both. If that doesn't work, are your routes nested? If not post your routes and I'll update the answer.
You can DRY out your Controller to set page and categories in a callback. But take care: you may omit the categories query in the categories#index action, but your page#... actions may skip the callback at all skip_before_action set_page or better overwrite the method and use a correct handling.
class ApplicationController
before_action set_page, only: [:index, :show]
before_action set_categories, only: [:index, :show]
private
def set_page
#page = Page.find params[:page_id]
end
def set_categories
#categories = Category.all
end
end

Couldn't find User without an ID, rails 4

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.

Voting for nested objects using the Acts As Votable gem

I have a Restaurant model that has_many :dishes, through: dish_categories. I found a post that shows how to write the view code necessary to get things going for the Acts As Votable gem. My situation differs being that the dish model is the nested resource that's being voted upon.
I tried translating the provided code but to no avail. At this point should I create a new controller for dishes and place the votable actions there? If so how would I setup my route so I can accomplish this on my restaurant's show page?
Models
class Restaurant < ActiveRecord::Base
has_many :dish_categories, dependent: :destroy
has_many :dishes, through: :dish_categories
end
class DishCategory < ActiveRecord::Base
belongs_to :restaurant
has_many :dishes, dependent: :destroy
delegate :name, to: :dish_category, prefix: "category"
delegate :restaurant, to: :dish_category
end
class Dish < ActiveRecord::Base
belongs_to :dish_category
end
Restaurants Controller
...
def upvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.liked_by current_user
redirect_to #restaurant
end
def downvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.disliked_by current_user
redirect_to #restaurant
end
...
Routes
resources :restaurants do
member do
put "upvote", to: "restaurants#upvote"
put "downvote", to: "restaurants#downvote"
end
end
Restaurants - Show View
...
<% #restaurant.dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<b><%= dish.name %></b>
<%= link_to "Upvote", like_restaurant_path(dish), method: :put %>
<%= link_to "Downvote", dislike_restaurant_path(dish), method: :put %>
</div>
<% end %>
A number of things needed to be done to get this to work. The first order of business was moving my controller action to my dishes controller. I also added two more actions: unlike and undislike for toggle functionailty.
NOTE: Logic for authenticating non-registered for users to liking/disliking dishes would still need to be written but this should help get you started.
Dishes Controller
class DishesController < ApplicationController
before_action :load_restaurant_and_dish, only: [:like, :unlike, :dislike, :undislike]
def like
#dish.liked_by current_user
redirect_to #restaurant
end
def unlike
#dish.unliked_by current_user
redirect_to #restaurant
end
def dislike
#dish.disliked_by current_user
redirect_to #restaurant
end
def undislike
#dish.undisliked_by current_user
redirect_to #restaurant
end
private
def load_restaurant_and_dish
#dish = Dish.find(params[:id])
#restaurant = #dish.restaurant
end
end
Next was configuring my routes to correspond with my restaurant and dish models:
Routes
resources :restaurants do
resources :dishes, only: [:like, :unlike, :dislike, :undislike] do
member do
put "like", to: "dishes#like"
put "unlike", to: "dishes#unlike"
put "dislike", to: "dishes#dislike"
put "undislike", to: "dishes#undislike"
end
end
end
I ended up refactoring my show view and created a few partials to reduce clutter now that there's a little bit of logic involved:
Restaurants - Show View
...
<%= render "restaurants/dish_partials/dishes" %>
...
Dishes Partial
<% #dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<span><b><%= dish.name %></b></span>
<%= render "restaurants/dish_partials/like_toggle", dish: dish %>
</div>
<% end %>
Like Toggle Partial
<% if current_user.liked? dish %>
<%= link_to "Unlike", unlike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Like", like_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>
<% if current_user.disliked? dish %>
<%= link_to "Undislike", undislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Dislike", dislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>

How can I implement a 'create' action without 'new' action

How can I best implement this feature: As an admin, I can assign a Resident Manager to a Hall.
I have a User model with a namespace routing for the admin -I intend on having another namespace routing that would hold the functions of the RM-
I have a Hall model.
Since its a many-many relationship between the above to models, I have a Management join model which contains only user_id and hall_id columns.
I know implementing the above feature, entails creating a new record in the management table but I don't know how to do it. I didn't think using a form (management#new) would solve this because the admin should not know the user_ids/hall_ids...
BELOW IS WHAT I HAVE TRIED TO DO BUT I CAN'T GET IT RIGHT
When the admin gets to the user index page, s/he should see a link for Hall Assignment for each user. This link leads to the management show page for that particular user, which would show the list of halls assigned to that user and also the show all the other remaining halls that isn't assigned to the user. So, either clicking an ADD button or on the hall's name should add it to that user's assigned halls list which is on the same page.
Management#show page
<h2><%= #user.email %>'s assigned halls</h2>
<% #user.managements.each do |management| %>
<%= management.hall.name %>
<% end %>
<p> HALL LISTS </p>
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add" %> </li>
<% end %>
</ul>
Here's is my Management controller
class Admin::ManagementsController < ApplicationController
def index
#managements = Management.all
end
def show
#user = User.find(params[:id])
#halls = Hall.all
end
def create
#management = Management.create(managements_params)
redirect_to admin_management_path
end
private
def managements_params
params.
require(:management).
permit(user_id: params[:user_id], hall_id: params[:hall_id])
end
end
And here's a piece of what my routes file looks like:
namespace :admin do
resources :users, only: [:index, :update]
resources :halls, only: [:index, :new, :create]
resources :managements, only: [:index, :new, :create, :show] do
resources :halls, only: [:index]
end
end
Your "add" button is just a mini form (with mostly hidden fields). You can instead just make it an actual form (with the submit-button having the text "Add") and the id-values filled in from the item on the page... it just points to the same routes that you'd normally point the form that you'd find in the new template.
if you want more detail, then show us the code that you have written (rather than a verbal description of it).
Edit:
Ok, so you'd put a button on the page like this
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add", managements_path(management: {user_id: #user.id, hall_id: hall.id}, method: :put ) %> </li>
<% end %>
</ul>
Notice the managements_path - you might need to check that that routing is correct (check it against what is in rake routes). Note that you're passing in the user id and the hall id, and that you must set the method to "put" on the button.
First things first -
How can I implement a 'create' action without 'new' action
It's relatively simple to do - you will need a create action somewhere, but a new action is just a way to build the respective ActiveRecord object for your controller.
If you do this in another action, you just have to make sure you point the form to the correct create action (and that create action to redirect back to
--
New / Create
Here's how you could handle the new / create actions in different controllers, as an example for you:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def index
#hall = Hall.new
end
end
#app/controllers/halls_controller.rb
Class HallsController < ApplicationController
def create
#hall = Hall.new hall_params
redirect_to users_path if #hall.save
end
private
def hall_params
params.require(:hall).(:hall, :attributes, :user_id)
end
end
This will allow you to show the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= form_for #hall, url: hall_path do |f| %>
<%= f.hidden_field :user_id, value: user.id %>
<%= f.text_field :x %>
<%= f.submit %>
<% end %>
<% end %>
--
Fix
ADD button or on the hall's name should add it to that user's assigned halls list
For this, I don't think you'd need a create action in the "traditional" sense - it will be more about adding new halls to a user's current halls. This is much different than creating a new hall itself:
#config/routes.rb
namespace :admin do
resources :users do
post "hall/:id", to: :add_all #=> domain.com/admin/users/:user_id/hall/:id
end
end
#app/controllers/admin/users_controller.rb
Class UsersController < ApplicationController
def add_hall
#user = User.find params[:user_id]
#hall = Hall.find params[:id]
#user.halls << #hall
end
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :user_halls
has_many :halls, through: :user_halls
end
#app/models/hall.rb
Class Hall < ActiveRecord::Base
has_many :user_halls
has_many :users, through: :user_halls
end
#app/models/user_hall.rb
Class UserHall < ActiveRecord::Base
belongs_to :user
belongs_to :hall
end
This uses the ActiveRecord collection methods to make this work, to which you'll be able to provide the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= button_to "Add Hall Test", user_add_hall_path(user, 1) %>
<% end %>

Resources