Trying to list info from multiple models in one view - ruby-on-rails

So I have this database where a user can borrow a book. We have books, users and people who borrow.
The code can be found here: https://gist.github.com/Veske/8490542
I am wondering, how can I make it so that I display all the books in borrows view, and then also be able to select thoes books for borrowing for my self or another user?
If I try to make a book instance variable in borrow controller for the view, hell gets loose. So I really have no idea now..
Edit: the #book in the view is not needed anymore as it did not work, i had a action in controller for it before.
This is my controller:
class BurrowsController < ApplicationController
before_action :signed_in_user, only: [:index,:edit,:update, :destroy]
before_action :admin_user, only: :destroy
def index
#burrow = current_user.burrows.build
#burrows = Burrow.all
end
def show
#burrow = Burrow.find(params[:id])
end
def new
#burrow = current_user.burrows.build
end
def create
#burrow = current_user.burrows.build(burrow_params)
if #burrow.save
flash[:success] = "Burrowing a book was successful!"
redirect_to #burrow
else
render current_user
end
end
# Private section, makes the page unable to be seen for non logged in users
private
def burrow_params
params.require(:burrow).permit(:user_id, :book_id)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
# Redirecting not logged in user etc.
def signed_in_user
unless signed_in?
store_location
redirect_to '/sessions/new', notice: "Please sign in!"
end
end
end
And this is my view for creating a new borrow entry:
<% provide(:title, "Burrow a book") %>
<b align="center">Choose the name of a book you want to burrow and enter 'Submit!'</b>
<%= form_for(#burrow) do |f| %>
<div class="forms">
<%= f.text_field :book_id, placeholder: "Type in the name of the book...", autofocus: true %>
<%= f.submit 'Submit!' %>
</div>
<% end %>
The view is bad currently, i am experimenting with absolutly everything all the time right now and I just don't understand what needs to be done.
Borrows index:
<% provide(:title, 'All burrowers') %>
<h2 align="center">All borrowers</h2><
<table align="center">
<tr>
<td align="left"><b>Who borrowed</b></td>
<td align="left"><b>Borrowed what</b></td>
<% if current_user.admin? && !current_user?(#user) %>
<td align="left"><b>Admin functions</b></td>
<% end %>
</tr>
<% #burrows.each do |burrow| %>
<tr>
<td align="left"><%= link_to burrow.user.name, burrow.user %></td>
<td align="left"><%= link_to burrow.book.name, burrow.book %></td>
<% if current_user.admin? && !current_user?(#user) %>
<td>
<%= link_to "Delete this user", burrow, method: :delete, data: { confirm: "You sure?" } %>
</td>
<% end %>
</tr>
<% end %>
</table>

One possible solution for this would be to use a select_tag list like this:
<%= form_for(#burrow) do |f| %>
<div class="forms">
<%= f.select("book_id", Book.all.collect {|b| [ b.name, b.id ] }, { include_blank: true }) %>
<%= f.submit 'Submit!' %>
</div>
<% end %>
Is that what you were looking for?
BTW - I think you mean 'borrow' rather than 'burrow'

Related

Delete/Create in-place (without redirect)

I have a rails question. I'm building a site where posts have likes, both posts and likes are their own model. A user can only like a post once, and once they like it the like button becomes an "unlike" button, that deletes the like.
I'm trying to create an experience in which the user can like, or unlike a post - and will not be redirected, but the like will update. With my limited rails knowledge, this isn't an easy task. Can anyone point me in the right direction?
Here is my /likes/_likes.html.erb template partial with the like/unlike button:
<% liked = #post.likes.find { |like| like.user_id == current_user.id} %>
<div class="likes">
<% if liked %>
<%= button_to 'Unlike', post_like_path(#post, liked), method: :delete %>
<% else %>
<%= button_to 'Like', post_likes_path(#post), method: :post %>
<% end %>
<%= #post.likes.count %><%= (#post.likes.count) == 1 ? 'Like' : 'Likes'%>
</div>
Here is my Like controller:
class LikesController < ApplicationController
before_action :find_post
before_action :find_like, only: [:destroy]
def create
if (!already_liked?)
#post.likes.create(user_id: current_user.id)
end
end
def destroy
if (already_liked?)
#like.destroy
end
end
private
def already_liked?
Like.where(user_id: current_user.id, post_id:
params[:post_id]).exists?
end
def find_post
#post = Post.find(params[:post_id])
end
def find_like
#like = #post.likes.find(params[:id])
end
end
Here is one of the views in which the _likes partial shows up (although the issue persists everywhere it appears):
<div class="post-display">
<% if #post.title %>
<h1><%= #post.title %></h1>
<% end %>
<% if #post.user %>
Post by <%= #post.user.email %>
<% end %>
<% if #post.price %>
<p>$<%= sprintf "%.2f", #post.price %></p>
<% end %>
<% if #post.description %>
<p><%= #post.description %></p>
<% end %>
<% if #post.image.present? %>
<%= image_tag #post.image.variant(:small) %>
<% end %>
<%= render 'likes/likes' %>
</div>
<% if current_user == #post.user %>
<%= link_to "Edit", edit_post_path(#post) %>
<%= button_to "Delete", #post, method: :delete %>
<% end %>
<% if #post.comments.count > 0 %>
<div class="post-comments">
<h2 class="post-comments-headline">Comments</h2>
<%= render #post.comments %>
</div>
<% end %>
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
If you don't have an answer to my question, but have an idea on how to improve my code - let me know either way! I'm trying to learn here...
Thank you,
Jill
Since you're using rails 7, rendering turbo_stream in response to "like" and "unlike" buttons will update the page without refreshing.
# config/routes.rb
resources :posts do
# NOTE: i've used singular `resource`, since there is no need to have `id`
# for the like.
resource :like, only: [:destroy, :create]
end
https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resource
# app/models/post.rb
class Post < ApplicationRecord
has_many :likes
def liked_by? user
likes.where(user: user).exists?
end
end
# app/models/like.rb
class Like < ApplicationRecord
belongs_to :post
belongs_to :user
# NOTE: avoid double likes.
validates_uniqueness_of :user, scope: :post, message: "already liked this post"
# TODO: create a unique index migration, to really make sure no double likes.
# `add_index :likes, [:post_id, :user_id], unique: true`
end
I've simplified LikesController a bit. No need for before_action filters:
# app/controllers/likes_controller.rb
class LikesController < ApplicationController
# POST /posts/:post_id/like
def create
# NOTE: uniqueness validation in `Like` model will prevent creating dup likes.
post.likes.create(user: current_user)
# you can access `like` error if you want to show it:
# like = post.likes.create(user: current_user)
# like.errors.full_messages
# NOTE: that's it, now we render `likes` partial inside a `turbo_stream`
render turbo_stream: turbo_stream.replace(
helpers.dom_id(post, :like), # this is the target div `id` that will be replaced
partial: "posts/likes", # with `likes` partial.
locals: { post: post }
)
end
# DELETE /posts/:post_id/like
def destroy
# NOTE: this will work regardless if there are any likes or not.
post.likes.where(user: current_user).destroy_all
# NOTE: alternatively, we can render the same `turbo_stream` as above
# in a template `likes/likes.turbo_stream.erb`:
render :likes
end
private
def post
#post ||= Post.find params[:post_id]
end
end
<!-- app/views/posts/_likes.html.erb -->
<!-- `dom_id` helps us generate a uniq id: "like_post_1" -->
<div id="<%= dom_id(post, :like) %>">
<!-- yes, there is a rails helper for this -->
<%= pluralize post.likes.count, "like" %>
<% if post.liked_by? current_user %>
<%= button_to "Unlike", post_like_path(post), method: :delete %>
<% else %>
<%= button_to "Like", post_like_path(post) %>
<% end %>
</div>
This turbo_stream is the same as in create action:
<!-- app/views/likes/likes.turbo_stream.erb -->
<%= turbo_stream.replace dom_id(#post, :like) do %>
<%= render "posts/likes", post: #post %>
<% end %>
https://turbo.hotwired.dev/handbook/streams
Try this
views file where likes partial render
<div id='post_likes'>
<%= render 'likes/likes' %>
</div>
/likes/_likes.html.erb
<div class="likes">
<% if liked %>
<%= button_to 'Unlike', post_like_path(#post, liked), method: :delete, remote: true %>
<% else %>
<%= button_to 'Like', post_likes_path(#post), method: :post, remote: true %>
<% end %>
<%= #post.likes.count %><%= pluralize(#post.likes.count, 'Like') %>
</div>
views/likes/create.js.erb
$('#post_likes').html('<%= render 'likes/likes' %>');
views/likes/destroy.js.erb
$('#post_likes').html('<%= render 'likes/likes' %>');

Undefined method for object defined in controller Rails

I have a view with a form and a table that displays some data from the database. Whenever I try to access the object from my controller in my view I get undefined method domain for "https://www.lookagain.co.uk/":String. But if do <%#savedHTML = ScrapedPage.all%> everything works fine. I know the I should not do that in the view as it defeats to purpose of MVC but I don't seem to fin a fix.
View:
<%= stylesheet_link_tag "masterstyles.css" %>
<% #url = 'default' %>
<%= form_for #url, :url => {:controller => "page_scraper", :action => "scrape"} do |f| %>
<%= f.text_field (:url) %>
<%= f.submit "Scrape" %>
<% end %>
<%#domain ='default'%>
<%#date ='default'%>
<%= form_for #domain, :url => {:controller => "page_scraper", :action => "compare"} do |f| %>
<%=select_tag 'domain', options_for_select(#savedHTML.collect{ |u| [u.domain, u.domain] })%>
<%=select_tag 'date', options_for_select(#savedHTML.collect{ |u| [u.created_at, u.created_at] })%>
<%= f.submit "compare" %>
<% end %>
<div class="subjects index">
<h2>FGH Page Scraper</h2>
<table class="listing" summary="Links list">
<tr class="header">
<th>ID</th>
<th>link</th>
<th>Created at</th>
<th>Updated at</th>
</tr>
<% #savedHTML.each do |page| %>
<tr>
<td><%= page.id %></td>
<td><%= page.domain %></td>
<td class="center"><%= page.created_at %></td>
<td class="center"><%= page.updated_at %></td>
<td class="actions">
<%= link_to("Delete", {:controller => 'page_scraper', :action => 'delete', :id => page.id}, :class => 'action delete') %>
</td>
</tr>
<% end %>
</table>
</div>
Controller:
class PageScraperController < ApplicationController
require 'nokogiri'
require 'open-uri'
require 'diffy'
require 'htmlentities'
def scrape
#url = watched_link_params[:url].to_s
puts "LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOG#{#url}"
#page = Nokogiri::HTML(open(#url))
coder = HTMLEntities.new
#encodedHTML = coder.encode(#page)
create
end
def index
#savedHTML = ScrapedPage.distinct.pluck(:domain)
end
def show
#savedHTML = ScrapedPage.distinct.pluck(:domain)
end
def new
#savedHTML = ScrapedPage.new
end
def create
#savedHTML = ScrapedPage.create(domain: #url, html: #encodedHTML, css: '', javascript: '')
if #savedHTML.save
puts "ADDED TO THE DATABASE"
redirect_to(root_path)
else
puts "FAILED TO ADD TO THE DATABASE"
end
end
def edit
end
def upadate
end
def delete
#savedHTML = ScrapedPage.find(params[:id])
end
def destroy
#savedHTML = ScrapedPage.find(params[:id])
#savedHTML.destroy
redirect_to(root_path)
end
def compare
#domain = params[:domain].to_s
puts #domain
redirect_to(root_path)
#timestamp
end
def watched_link_params
params.require(:default).permit(:url)
end
def compare_params
params.require(:domain).permit(:domain)
end
end
The problem is that in your controller you are saving only string-values to #savedHTML variable (pluck will give you only an array of attributes from given objects). Therefore you cant ask "some_string".domain because String class doesn't have a domain method.
If you have a domain method on ScrapedPage object then in your controller action (index or show - whatever you are dealing with) you should replace
#savedHTML = ScrapedPage.distinct.pluck(:domain)
with
#savedHTML = ScrapedPage.select(:domain).distinct
The latter will give you unique ScrapedPage objects based on domain value. Look here for further info and examples.
NB! also a tip for refactoring:
Use strong parameters under private section. Also, if you have the same query in your controller twice in different actions then it is better to make it in before_action like this:
class PageScraperController < ApplicationController
before_action :set_saved_html, only: %i[index show]
def index
end
def show
end
private
def watched_link_params
params.require(:default).permit(:url)
end
def compare_params
params.require(:domain).permit(:domain)
end
def set_saved_html
#savedHTML = ScrapedPage.select(:domain).distinct
end
end

Rails 4 : Table Boolean Column Update Using "link_to "with a specific value "TRUE" always

In my customer controller the update method code is like bellow:
def update
#customer= Customer.find(params[:id])
if #customer.update_attributes(customer_params)
redirect_to customers_path
else
render 'edit'
end
end
In my view in customers index page I am planning to add a "link_to" link, if it is clicked, then that particular customers field "doc_delete" should be updated with value "TRUE".
<td><%= link_to "[Update", *************what is here ?******** method: :put %></td>
You can pass hidden params through button_to:
<%= button_to "Update", user, method: :put, params: { doc_delete: true } %>
This will create a micro-form, much like what Marwen alluded to. Whilst quite inefficient, it will be the best way to send data to your update action.
--
Another, more efficient, way would be to define a custom route/action:
#config/routes.rb
resources :customers do
patch :doc_delete, on: :member #-> url.com/users/:id/doc_delete
end
#app/controllers/customers_controller.rb
class CustomersController < ApplicationController
def doc_delete
#customer = Customer.find params[:id]
redirect_to customers_path if #customer.update doc_delete: true
end
end
#app/views/customers/index.html.erb
<% #customers.each do |customer| %>
<%= link_to "Update", customer_doc_delete_path(customer) %>
<% end %>
You will need a form to do that for you
<% unless customer.doc_delete? %>
<%= form_for customer do |f| %>
<%= f.hidden_field_tag :doc_delete, true %>
<%= f.submit %>
<% end %>
<% end %>
Where to insert this form?
Well if you are rendering you costumers using:
<%=render #costumers %>
then you will add the form in the /customers/_customer.html.erb
If you are looping them manually:
<% #customers.each do |customer| %>
<%=customer.full_name %>
## Here you can add the form
<% end %>
An another way, you can use Ajax.
#app/views/customers/index.html.erb
<% #customers.each do |customer| %>
<% if !customer.doc_delete == true %>
<%= link_to "Update", customer_doc_delete_path(customer), remote: true %>
<% else %>
<%= Updated %>
<% end %>
<% end %>
#config/routes.rb
resources :customers do
patch :doc_delete, on: :member #-> url.com/customers/:id/doc_delete
end
#app/controllers/customers_controller.rb
class CustomersController < ApplicationController
def doc_delete
#customer = Customer.find params[:id]
if #customer.update doc_delete: true
respond_to do | format |
format.js {render :nothing => true}
end
end
end
end
In my index.html
<td>
<%= hidden_field_tag 'delete_present', :value => "present" %>
<%=link_to "[update]", customer_path(customer, :doc_delete => true), :method => :put, :confirm => "Are you sure?" %>
</td>
In my customer controller
def update
if params[:doc_delete].present?
#customer= Customer.find(params[:id])
#customer.doc_delete=true
#customer.save
redirect_to customers_path
else
#customer= Customer.find(params[:id])
if #customer.update_attributes(customer_params)
redirect_to customers_path
else
render 'edit'
end
end
end

Iterate through an array to create rails objects

I am having trouble finding any information on how to iterate through an array and create objects.
My form creates a selectable list of users that when checked, pass the user_ids as an array object.
invitations\new.html.rb
<%= bootstrap_form_for Invitation.new do |f| %>
<br>
<ul>
<%= f.hidden_field :attended_event_id, :value => #event_selected.id %>
<li>
<%= check_box_tag 'attendee_id[]', user.id %>
<%= h user.name %>
</li>
<% end %>
</ul>
<br>
<%= f.submit "Invite Selected Users" %>
<% end %>
I would like to then create new Invitations objects by combining the attended_event_id with all of the objects in the attendee_id array.
After a bit of trouble I got the basics of my controller working but only by passing in the user_id as a text entry. Below is my Invitations controller. Not really sure where to start on this one as I haven't been able to find a good example.
invitations_controller.rb
def create
#invitation = Invitation.new(invite_params)
if #invitation.save!
flash.now[:success] = "Invited!"
redirect_to root_path
else
flash.now[:error] = "Failure!"
redirect_to root_path
end
end
private
def invite_params
params.require(:invitation).permit(:attended_event_id, :attendee_id)
end
end
Do you mean something like this?
<%= bootstrap_form_for Invitation.new do |f| %>
<br>
<ul>
<%= f.hidden_field :attended_event_id, :value => #event_selected.id %>
<% users.each do |user| %>
<li>
<%= check_box_tag 'invitation[attendee_id][]', user.id %>
<%= h user.name %>
</li>
<% end %>
</ul>
<br>
<%= f.submit "Invite Selected Users" %>
<% end %>
def create
#invitations = invite_params[:attendee_id].map do |attendee_id|
Invitation.new(
attended_event_id: invite_params[:attended_event_id],
attendee_id: attendee_id
)
end
if #invitations.any?(&:invalid?)
flash.now[:error] = "Failure!"
redirect_to root_path
else
#invitations.each(&:save!)
flash.now[:success] = "Invited!"
redirect_to root_path
end
end
private
def invite_params
params.require(:invitation).permit(:attended_event_id, {:attendee_id => []})
end
There is a good basic example on RailsGuides
http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object
Do you want to achieve something like this:
def create
params[:attendee_id].each do |user_id|
Invitation.create(:attended_event_id => params[:attended_event_id], :attendee_id => user_id)
end
.
.
.
end

Rails 3: Displaying conversation list and selected conversation on the same page (using Mailboxer)

Framework: Rails 3/ Jruby with Mailboxer gem.
I want to create a Facebook style inbox page that allows a user to scroll through their Inbox, Sent Items and Trash, whilst keeping the selected conversation displayed on the right hand side of the page (like Facebook's implementation of the desktop inbox)
The action of clicking the conversation title should render that entire conversation to the right side of the page, avoiding the need of dedicating an entire page to one conversation within the web browser. This is so (in a later version) I can implement an AJAX call that will only refresh the conversation part of the page, whilst allowing the user to keep an eye on their conversation list.
My problem is, I'm completely stumped as to how this would be implemented, without the routing error No route matches [GET] "/conversations/20/show_conversation" that I'm currently getting. I'm fairly new to Ruby on Rails, so the whole routing side of things is a bit confusing.
My question how do I display all my conversations, as well as the transcript of one selected conversation (at any given time) on the same page. Preferably, I would like to avoid the use of Javascript/ jQuery and stick to the Ruby on Rails implementation, if possible.
Here's a screenshot of my "messages" page, where "Conversation.." (on the right) should display the transcript of the conversation I had with the target user.
My controller code for the current page:
class ConversationsController < ApplicationController
before_filter :authenticate_user!
before_filter :get_mailbox
before_filter :get_conversation, except: [:index]
before_filter :get_box, only: [:index]
before_filter :get_conversation, except: [:index, :empty_trash]
def index
#conversations = #mailbox.inbox.paginate(page: params[:page], per_page: 10)
#inbox = #mailbox.inbox.paginate(page: params[:page], per_page: 10)
#trash = #mailbox.trash.paginate(page: params[:page], per_page: 10)
#sent = #mailbox.sentbox.paginate(page: params[:page], per_page: 10)
end
def show_conversation
#conversation
redirect_to conversations_path
end
[...]
private
def get_mailbox
#mailbox ||= current_user.mailbox
end
def get_conversation
#conversation ||= #mailbox.conversations.find(params[:id])
end
def get_box
if params[:box].blank? or !["inbox","sent","trash"].include?(params[:box])
params[:box] = 'inbox'
end
#box = params[:box]
end
end
My corresponding views: index.html.erb
<% page_header "Your Conversations" %>
<p><%= link_to 'Start conversation', new_message_path, class: 'btn btn-lg btn-primary' %>
<%= link_to 'Empty trash', empty_trash_conversations_path, class: 'btn btn-danger',
method: :delete, data: {confirm: 'Are you sure?'} %></p>
<!-- tab things, they're awesome -->
<div class="left_col">
<div class="col-sm-3">
<ul class="nav nav-pills">
<%= mailbox_section 'inbox', #box %>
<%= mailbox_section 'sent', #box %>
<%= mailbox_section 'trash', #box %>
</ul>
</div>
<!-- this working part isn't in the tutorial -->
<% if #box == 'trash' %>
<%= render partial: 'conversations/conversation', collection: #trash %>
<% elsif #box == 'inbox' %>
<%= render partial: 'conversations/conversation', collection: #inbox %>
<% elsif #box == 'sent' %>
<%= render partial: 'conversations/conversation', collection: #sent %>
<% end %>
<%= will_paginate %>
</div>
<div class="right_col">
<p><small>Conversation...</small></p>
<%= #conversation %> <!-- should I have a partial or something? -->
</div>
_conversation.html.erb partial where the link to show_conversation is
<%= link_to conversation.subject, show_conversation_conversation_path(conversation) %>
<div class="btn-group-vertical pull-right">
<% if conversation.is_trashed?(current_user) %>
<%= link_to 'Restore', restore_conversation_path(conversation),
class: 'btn btn-xs btn-info', method: :post %>
<% else %>
<%= link_to 'Move to trash', conversation_path(conversation),
class: 'btn btn-xs btn-danger', method: :delete,
data: {confirm: 'Are you sure?'} %>
<% if conversation.is_unread?(current_user) %>
<%= link_to 'Mark as read', mark_as_read_conversation_path(conversation),
class: 'btn btn-xs btn-info', method: :post %>
<% end %>
<% end %>
</div>
<p><%= render 'conversations/participants', conversation: conversation %></p>
<p><%= conversation.last_message.body %>
<small>(<span class="text-muted">
<%= conversation.last_message.created_at.strftime("%-d %B %Y, %H:%M:%S") %>
</span>)</small></p>
And finally, my routes.rb
resources :conversations, only: [:index, :show, :destroy] do
member do
post :reply, :restore, :mark_as_read, :show_conversation
end
collection do
delete :empty_trash
end
end
resources :messages, only: [:new, :create]
root :to => 'conversations#index'
I do have a working conversation partial that builds the conversation on a separate page. It works fine, but I haven't included it because I want to move away from having a separate page to view the conversation. Any help on this would be greatly appreciated!
Thanks,
Remove show_conversation references in controller, views and routes – you just need index action for your purpose.
Then in your controller find selected conversation:
def index
#conversations = #mailbox.inbox.paginate(page: params[:page], per_page: 10)
if params[:selected_conversation_id]
#selected_conversation = #mailbox.inbox.find(params[:selected_conversation_id])
end
#inbox = #mailbox.inbox.paginate(page: params[:page], per_page: 10)
#trash = #mailbox.trash.paginate(page: params[:page], per_page: 10)
#sent = #mailbox.sentbox.paginate(page: params[:page], per_page: 10)
end
Then in your index.html.erb render selected conversation if it is present (you should create that partial first of course):
<%= render partial: selected_conversation, object: #selected_conversation %>
In your _selected_conversation.html.erb you should use seleted_conversation variable (you should add rendering of all messages, below is simplified example for last message only):
<%= selected_conversation.last_message.body %>
Whenever you are making an URL to all conversations + selected conversation page, you just provide additional parameter to URL helper:
conversations_url(selected_conversation_id: #conversation.id)

Resources