Undefined method for object defined in controller Rails - ruby-on-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

Related

Rails update attribute when dealing with through relationships

I’m trying to update an attribute on a show page, keeping it in the show page, that is connected through a different table. For example: manager logs into admin site and sees several resources, clicks on one and can see all users that have access to the resource (either approved or pending). They can currently delete a user’s access. What I’m trying to do is allow the manager to change the status of the request say from pending to approved.
So for my models I have the following:
class TrainingResource
has_many :user_training_resources, dependent: :destroy
end
class UserTrainingResource
belongs_to :user
belongs_to :training_resource
enum status: [:pending, :confirmed, :rejected]
end
class Users
has_many :user_training_resources, dependent: :destroy
has_many :training_resources, through: :user_training_resources
end
TrainingResourcesController
class Admin::TrainingResourcesController < Admin::ApplicationController
belongs_to_app :training_resources
add_breadcrumb 'Training Resources', :admin_training_resources_path
before_action :load_training_resource, only: [:show, :edit, :update, :destroy]
respond_to :html, :json
def index
#training_resources = TrainingResource.paginate(page: params[:page])
#training_resources = #training_resources.search(params[:search]) if params[:search]
respond_with(#training_resources)
end
def show
respond_with #training_resource
end
def new
#training_resource = TrainingResource.new
respond_with(#training_resource)
end
def create
#training_resource = TrainingResource.new(training_resource_params)
flash[:notice] = 'TrainingResource created successfully' if #training_resource.save
respond_with #training_resource, location: admin_training_resources_path
end
def edit
respond_with(#training_resource)
end
def update
flash[:notice] = 'TrainingResource updated successfully' if #training_resource.update(training_resource_params)
respond_with #training_resource, location: admin_training_resources_path
end
def destroy
flash[:notice] = 'TrainingResource deleted successfully' if #training_resource.destroy
respond_with #training_resource, location: admin_training_resources_path
end
private
def load_training_resource
#training_resource = TrainingResource.find_by!(id: params[:id])
end
def training_resource_params
params.require(:training_resource).permit(:name, :description, :total_subscriptions, :url)
end
end
UserTrainingResourcesController, which is pointing to the TrainingResourcesController
class Admin::UserTrainingResourcesController < Admin::ApplicationController
belongs_to_app :training_resources
add_breadcrumb 'Training Resources', :admin_training_resources_path
before_action :load_training_resource
respond_to :html, :json
def edit
respond_with #user_training_resource
end
def update
flash[:notice] = 'UserTrainingResource updated successfully' if #user_training_resource.update(user_training_resource_params)
respond_with #user_training_resource, location: admin_training_resources_path
end
def destroy
flash[:notice] = 'UserTrainingResource deleted successfully' if #user_training_resource.destroy
respond_with #user_training_resource, location: admin_training_resources_path
end
private
def load_training_resource
#user_training_resource = UserTrainingResource.find_by!(id: params[:id])
end
def user_training_resource_params
params.require(:user_training_resources).permit(
:training_resources_id, :status).merge(user_id: current_user_id)
end
end
Training Resource Show
<tbody>
<% #training_resource.users.each do |training| %>
<tr>
<td><%= training.full_name %></td>
<% utr = training.user_training_resources.where(training_resource: #training_resource).first %>
<td><%= utr.status.capitalize %>
<%= form_tag '/user_training_resource/edit', :method => :get do %>
<%= select_tag( :user_training_resources_id, options_for_select(['pending', 'confirmed', 'rejected']))%>
<%= submit_tag 'Edit Status', class: 'btn btn-default btn-sm' %>
<% end %>
<%= tb_form_for [:admin, #training_resource], :remote => true, :data => {:errors => :inline, :success => admin_user_training_resources_path} do |f| %>
<%= tb_form_errors(f.object, :base) %>
<%= f.tb_select :name, options_for_select(holder, :status) %>
<%= f.tb_save_buttons('User Training Resource', admin_user_training_resources_path) %>
<% end %>
</td>
<td class="table-actions">
<%= link_to 'Edit', edit_admin_user_training_resource_path(training), :class => 'btn btn-default btn-sm' %>
<%= link_to 'Delete', admin_user_training_resource_path(training), :method => :delete, :data => {:confirm => 'Are you sure you want to delete this?'}, :class => 'btn btn-danger btn-sm' %>
</td>
</tr>
<% end %>
</tbody>
User Training Resource Helper, Holder Method
def holder
TrainingResource.all.each(&:id)
end
The link to Edit I’ll end up taking out because it pushes the user to the UserTrainingResource edit page and I want to keep it on the current page. The top form with the select_tag isn’t actually reading in the current status and then the submit_tag is redirecting the page.
My latest attempt is the bottom form_for. This is pulling in the TrainingResource and not the UserTrainingResource data. When I change the tb_select to :status, options_for_select(holder, :status) I end up with undefined method `status’. It looks like it’s only aiming at TrainingResource. I’ve also tried:
Thought here that it would pull in the default status and then allow the options to change. Still have an issue with undefined method of ‘status’ and even then there’s the issues with save.
I’ve also tried:
In this case I end up with undefined method ‘map’. Which I've tried to pinpoint down with attempting pluralization on #training_resource and #user_training_resource but that turns up First argument in form cannot contain nil or be empty.
Edit:
Tried in the UserTrainingResourcesController:
def set_to_confirmed
#user_training_resource = UserTrainingResource.find(params[:user_training_resource])
end
Then in the show
<%= link_to 'Confirmed', {:controller => 'user_training_resources', :action => 'set_to_confirmed', :status => #training_resource.user_training_resource }, :class => 'btn btn-default btn-sm'%>
Although no errors on the page load when clicking on the link I get:
Requested URL: https://localhost:3000/admin/set_to_confirmed?status=%23%3CUserTrainingResource%3A%3AActiveRecord_Associations_CollectionProxy%3A0x00007f93ee8b0f90%3E
Latest attempt using the following:
<% #training_resource.spud_users.each do |training| %>
<tr>
<td><%= training.full_name %></td>
<% utr = training.user_training_resources.where(training_resource: #training_resource).first %>
<td>
<%= tb_form_for [:admin, #training_resource], :remote => true, :data => {:errors => :inline, :success => admin_training_resources_path} do |f| %>
<%= f.select(:user_training_resource_id, options_for_select(['pending', 'confirmed', 'rejected']), :selected => utr.status)%>
<%= f.tb_save_buttons('', admin_training_resources_path) %>
<% end %>
</td>
This will show only the options, which is good, but I need it to default to what's current in the database and the save isn't sticking.
If I get the point, you need to update the joining table UserTrainingResource changing the status column.
I probably would create three actions in Admin::UserTrainingResourcesController:
def set_to_pending
end
def set_to_confirmed
end
def set_to_rejected
end
Then I would add three link to each row in the table in TrainingResource#Show passing the necessary parameters to complete the action, like explained in this post: pass parameter by link_to ruby on rails

Rails: No route matches [POST] "/specials/1"

This is the error I'm getting:
No route matches [POST] "/specials/1"
I understand that it's not able to produce the post route, or it isn't available.
Here's my view/form code:
<%= form_for(:special, :url => {:action => 'update', :id => #special.id}) do |f| %>
<table class="table table-responsive table-striped table-condensed table-hover" summary="Special form fields">
<tr>
<th>Order</th>
<td><%= f.text_field :order, class: "form-control" %></td>
</tr>
<tr>
<th>Name</th>
<td><%= f.text_field :name, class: "form-control" %></td>
</tr>
<tr>
<th>Description</th>
<td><%= f.text_field :description, class: "form-control" %></td>
</tr>
<tr>
<th>Fine Print</th>
<td><%= f.text_field :fine_print, class: "form-control" %></td>
</tr>
<tr>
<th>Active</th>
<td><%= f.text_field :active, class: "form-control" %></td>
</tr>
</table>
<div class="form-buttons">
<%= submit_tag("Update Special") %>
</div>
<% end %>
Heres's my controller code:
class SpecialsController < ApplicationController
def index
#specials = Special.sorted
end
def show
#special = Special.find(params[:id])
end
def new
#special = Special.new
end
def create
#Instantiation of object using form parameters
#special = Special.new(special_params)
#Save the object
if #special.save
#If success, redirect to index action
redirect_to(:action => 'index')
else
# Redisplay the form so user can fix problems
render('new')
end
end
def edit
#special = Special.find(params[:id])
end
def update
#Find an existing object using form parameters
#special = Special.find(params[:id])
#Update the object
if #special.update_attributes(special_params)
#If succeeds, redirect to index action
redirect_to(:action => 'show', :id => #special.id)
else
# If update fails, redisplay the form so user can fix problems
render('edit')
end
end
def delete
end
private
def special_params
params.require(:special).permit(:name, :description, :fine_print, :active, :order)
end
end
I noticed that there is an update path:
PATCH /specials/:id(.:format) specials#update
I can't figure out why the post route isn't being applied. It's looking for the right #special instance, but it doesn't seem to have the route available. Any advice?
Usually when updating a record, we do a patch request to the route. Your form should look like this:
<%= form_for(#special) do |f| %>
Rails will determine the correct route is PATCH /specials/:id based on the fact that #special has been persisted to the database.
If you decide to use this same form as a partial in your new view, just make sure to add this to your controller:
def new
#special = Special.new
end
That way whether you are on the new route or the edit route, there will always be a #special object for form_for to infer whether to POST to /specials or PATCH /specials/:id

Trying to list info from multiple models in one view

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'

Why do I get this NoMethodError

I'm making a twitter-copy and right now I'm trying to show all the posts from the users an other user is following. I'm new at ruby and rails, so i might be doing this a really weird way..
These are the files I have:
session#home.html.erb
<h2 class='User_Header'> Home <h2>
<%= link_to "New Post", controller: "posts", action: "new" %>
<%= link_to "Log Out", :controller => "sessions", :action => "logout" %>
<%= show_tweets("following") %>
sessions_helper
module SessionsHelper
def show_tweets(opt)
if opt == "following"
#sub = Subscription.where("userID = ?", #current_user.id)
#post = Post.where("user_id = ?", #sub.followingID)
render partial: 'shared/follower_tweets'
end
end
def show_tweet(s)
#post = Post.where("user_id = ?", s.id)
render partial: 'shared/tweet'
end
def tweet_username(p)
#username = User.where("id = ?", p.user_id)
Rails.logger.debug #username.inspect
render partial: 'shared/user'
end
end
_follower_tweets.html.erb
<h2>Tweets</h2>
<table>
<tr>
<th>Username</th>
<th>Tweet</th>
</tr>
<% div_for(#post, class: 'post') do %>
<td><%= tweet_username(#post) %></td>
<td><%= #post.content %></td>
<% end %>
</table>
_user.html.erb
<%= #username %>
session.rb
class Session < ActiveRecord::Base
attr_accessible :content, :user_id, :followingID, :userID
end
Error
app/views/sessions/home.html.erb where line #9 raised:
undefined method `followingID' for #<ActiveRecord::Relation:0x007fd74b66f8a8>
What is happening is that you have followingID on your Session model instead of Subscription model. Should be something like the following:
class Subscription < ActiveRecord::Base
attr_accessible :followingID
end
However, the problem is bigger than that. You must read about Active Record Associations, then you would be able to do something like
#subs = #current_user.subscriptions
#posts = #current_user.posts
check if your model's association is correct. the messages indicates that there's an error about this.

Simple quantity function problems, shopping cart

I just picked up Agile Web Development with Rails 3rd Ed., and I'm going thru the Depot Application chapters, I'm attempting to create a simple Edit quantity function, and delete function. I've had luck with the delete function but no luck with the edit quantity function.
I'm going to provide a lot of information, so please don't feel overwhelmed. I've found this to be a challenging problem.
To add_to_cart.html.erb
<div class="cart-title">Your cart</div>
<table>
<% for item in #cart.items %>
<tr>
<td><% form_for #cart.items, :url => {:action => "cart_update", :id => "#{item.getinventoryid}"} do |f| %>
<%= f.text_field :quantity, :size => '3' %>
<%= f.hidden_field :id, :value => "#{item.getinventoryid}" %>
<%= f.submit 'cart_update' %>
<% end %></td>
<td><%=h item.quantity %> ×</td>
<td><%=h item.title %></li></td>
<td><%=h item.description %></td>
<td class="item-price"><%= number_to_currency(item.price) %></td>
<td><%= link_to 'remove', {:controller => 'inventories', :action => 'remove_cart_item', :id => "#{item.getinventoryid}"} %></td>
</tr>
<% end %>
<tr class="total-line">
<td colspan="4">Total</td>
<td class="total-cell"><%= number_to_currency(#cart.total_price) %></td>
</tr>
</table>
<%= button_to "Checkout", :action => 'checkout' %>
<%= button_to 'Empty cart', :action => 'empty_cart' %>
inventories_controller:
def cart_update
#inventory = Inventory.find(params[:id])
#cart = find_cart
#cart.increment_inventory_quantity(params[:inventory])
end
def remove_cart_item
inventory = Inventory.find(params[:id])
#cart = find_cart
#cart.remove_inventory(inventory)
redirect_to_index("The item was removed")
end
Cart.rb model
attr_accessor :items
def increment_inventory_quantity(id, quantity)
inventory_to_increment = #items.select{|item| item.inventory == inventory}
# We do this because select will return an array
unless product_to_increment.empty?
inventory_to_increment = inventory_to_increment.first
else
# error handling here
end
inventory_to_increment.quantity = quantity
end
def remove_inventory(inventory)
#items.delete_if {|item| item.inventory == inventory }
end
cart_item.rb model
attr_accessor :inventory, :quantity
def getinventoryid
#inventory.id
end
This produces strange results:
Notice the quantity 16 appears in both items from my loop (#Fail). When I submit the form a ArgumentError in InventoriesController#cart_update wrong number of arguments (1 for 2) error is returned. Parameters being passed:
{"commit"=>"cart_update",
"_method"=>"put",
"authenticity_token"=>"sH1tWXTJPltpSq5XaAkww7259IR5ZiflnqSFB2Zb0IY=",
"id"=>"50",
"cart_item"=>{"quantity"=>"16",
"id"=>"50"}}
You are getting the wrong number of arguments error because you are passing one argument to #cart.increment_inventory_quantity in the controller method. That method requires two arguments.
# In your controller:
def cart_update
#inventory = Inventory.find(params[:id])
#cart = find_cart
#cart.increment_inventory_quantity(params[:inventory]) # you are passing one thing
end
# Then in your model:
def increment_inventory_quantity(id, quantity) # the method wants two things
# ...
Possibly you intended to do something like this:
def cart_update
#inventory = Inventory.find(params[:cart_item][:id])
#cart = find_cart
#cart.increment_inventory_quantity(#inventory.id, params[:cart_item][:quantity])
end
Are you sure it's form_for( #cart.items ) and not form_for( item )?

Resources