why do i get NoMethodError? - ruby-on-rails

I want my trade page to show a list of every Item that a user has added to their cart, and I'm having trouble understanding why this implementation is giving me a NoMethodError
So in the #show action of my TradesController I have a trade_ids variable that contains an array of added items returned by $redis.smembers current_user_trade. I then use this to perform a lookup on the id of each item, and loop through the instance variable in my view.
My Trades Controller:
class TradesController < ApplicationController
def show
trade_ids = $redis.smembers current_user_trade
#trade_items = Item.find(trade_ids)
end
def add
$redis.sadd current_user_trade, params[:item_id]
render json: current_user.trade_count, status: 200
end
def remove
$redis.srem current_user_trade, params[:item_id]
render json: current_user.trade_count, status: 200
end
private
def current_user_trade
"trade#{current_user.id}"
end
end
Here's the method I'm using to add items to current_user_trade:
class Item < ActiveRecord::Base
extend FriendlyId
friendly_id :slug, use: [:slugged, :finders]
def slug
[artist.parameterize, title.parameterize].join("-")
end
def trade_action(current_user_id)
if $redis.sismember "trade#{current_user_id}", id
"Remove from"
else
"Add to"
end
end
end
My routes:
resource :trade, only: [:show] do
put 'add/:item_id', to: 'trades#add', as: :add_to
put 'remove/:item_id', to: 'trades#remove', as: :remove_from
end
And then in my trades view I've got a basic:
<% #trade_items.each do |item| %>
<p><%= item.title %></p>
etc
<% end %>
My initial thought is that this had something to do with the fact that I've been using FriendlyId for slug generation. But according to FriendlyId's doc, adding :finders to the slug declaration in my Item model should reenable lookups by id. But it's not.
I also tried refactoring this so that it used my Items :slug, but that too was to no avail.

Fixed it. Turns out it was cached. I switched from storing item_id to item_slug halfway through, and needed to run redis-cli flushall to get it to store and access the right data.

Related

How to get assocated model's attribute values in views ? Rails

I have three models...
models/resident.rb
class Resident < ActiveRecord::Base
belongs_to :hostel
has_many :leaves,dependent: :delete_all
has_one :user,dependent: :delete
end
models/user.rb
class User < ActiveRecord::Base
belongs_to :resident
end
models/leave.rb
class Leave < ActiveRecord::Base
belongs_to :resident
end
Now when I am trying to access the value of leave's attribute in views/leave/show.html.erb
I am getting this:
app/views/leaves/show.html.erb
<%= #leaves %>
out put In Browser :
#<Leave::ActiveRecord_Associations_CollectionProxy:0x007fde611850f0>
My leave controller looks like :
leaves_controller.rb
class LeavesController < ApplicationController
def new
if logged_in?
#leave=Leave.new
else
flash[:info]="Please login to mark a leave"
redirect_to root_path
end
end
def show
#leaves= current_user.resident.leaves
end
def create
#leave=current_user.resident.leaves.create(leave_params)
if #leave.save
flash[:info] = "Leave successfully marked"
redirect_to new_leave_path
else
flash[:danger] = "Something wrong Happened try again"
redirect_to root_path
end
end
private
def leave_params
params.require(:leave).permit(:start_date,:end_date,:destination)
end
end
Am I making correct leaves for resident and related user (create method)?
Is show method correct ?
and How to assess the user's leaves attribute in show.html.erb of leaves views.
A Resident has_many Leaves so current_resident.leaves returns an array of all the current_resident's leaves. You will need to loop through leaves to show individual attributes. Try
#leaves.first.attribute_name
in your view to get an idea of how the data is represented. To show all the leaves you'll need to use a loop in the view
#leaves.each do |leave|
leave.inspect
end
You are doing everything fine, and show method is fine, and the template shows exactly what is was told to show.
#leaves is a collection. You probably want to show it’s elements? This should lead to the proper solution:
<% #leaves.each do |l| %>
<%= l.inspect %>
<% end %>

How to link model to controller action

I have a Slider model in my project and it has a lot of polymorphic associations with other model like Product, Manufacturer, Article and etc.
So, when I use 'show' action with one of the models I also show related Slider. It's ok. But sometimes I need to show Slider with 'index' action.
What is the best way to link some of the sliders to actions, not to other models?
UPDATE
routes
resources :products, :articles, :industries, :manufacturers, only: [:index, :show]
Product controller
class ProductsController < ApplicationController
load_resource
# GET /products
# GET /products.json
def index
#catalog = Product.by_type_and_manufacturer
end
# GET /products/1
# GET /products/1.json
def show
#page_slider = #product.slider
end
end
So in 'show' action I just use product.slider to get related Slider instance. But I want to show another slider for all products by index action.
In that case, what you're trying to do is not possible. You cannot create a relation to a controller action. What you need to do is link the relation's controller action, rather than trying to create a relation to the controller action. A model can only be related to another model (you cannot has_many index, show, delete, etc...)- In other words, call up the data for the relation, and link to that relation's controller action in the view.
example:
#Models:
class Page < ActiveRecord::Base
has_many :sliders
end
class Slider < ActiveRecord::Base
belongs_to :page
end
#Controllers
class PagesController < ApplicationController
def index
#pages = Page.all # lists all pages
end
def show
#page = Page.find(params[:id]) # simplified, this will probably make use of strong params in your actual code
#sliders = #page.sliders # all sliders related to the page
end
# if you would like to show a page that just has all sliders for a specific page and not the page itself...
def show_page_sliders # you will have to create a route and view for this manually
#page = Page.find(params[:id]) # simplified, this will probably make use of strong params in your actual code
#sliders = #page.sliders # all sliders related to the page
# note that this controller action is identical to the show action, because the data we're going to need is the same- the difference comes in the view
end
end
class SlidersController < ApplicationController
def index
#sliders = Slider.all
end
def show
#slider = Slider.find(params[:id])
end
end
# Views
# page#index
<% #pages.each do |p| %>
...
page listing code goes here. if you want to list the sliders for each page on the index...
<% p.sliders.each do |s| %>
...
individual slider info goes here
...
<% end %>
...
<% end %>
# pages#show
<%= #page.name %>
<%= #page.content %> <!-- or whatever data you have for page -->
# since here we are showing a singular page, we can just use our #page instance variable to list out the sliders
<% #page.sliders do |s| %>
...
Slider listing code goes here
...
<% end %>
# pages#show_sliders
<!-- this is identical to the page#show view, minus the actual page info, and with the addition of a link back to the parent page -->
<%= link_to "Back to page", page(s.page_id) %>
<% #page.sliders do |s| %>
...
Slider listing code goes here
<!-- you can link to any path from the slider listing -->
<%= link_to "Show", slider(s.id) %>
<%= link_to "Edit", edit_slider_path(s.id) %>
<%= link_to "Delete", delete_slider_path(s.id) %>
...
<% end %>
#######################UPDATE#############################
# to define one slider per controller action
class PagesController < ApplicationController
def index
#pages = Page.all
# you need to add a "controller_action" column to your Slider model
#slider = Slider.find_where(controller_action: "pages#index")
end
def show
#page = Page.find(params[:id])
#slider = Slider.find_where(controller_action: "pages#show")
end
# etc ...

Rails: How to only allow User to apply to job only once?

I am creating a job board, and I don't want to allow the users the option to apply for the same job twice. How can I limit this?
app/views/jobs/job.html.erb
<% if applied_to_this_job? %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
app/helpers/jobs_helper.rb
def applied_to_this_job?
JobApplication.exists? user_id: current_user.id
end
Obviously this doesn't work because it checks if this user has applied to any job. How Can I check to see if the current user has applied to the job being viewed.
Also, how can I limit this at the controller level so that the user can't go to job_application/new and get to the form.
You would use a before_filter in the controller action.
class JobsController < ActionController::Base
before_filter :has_applied?, only: [new, create]
....
private
def has_applied?
if JobApplication.where(user_id: :current_user.id, job_id: params[:job_id]).any?
redirect_to :index, alert: "You have already applied"
end
end
end
This would allow the user to visit /jobs/new and post the application to /jobs/create unless they have applied. If they have applied, they will be redirected to the index in the sample code.
Also as another answer has noted, it would be wise to pass in the job id as well. Updated sample code above to reflect.
You need to check and see if the JobApplication object is for this #job try:
JobApplication.where( user_id: current_user.id, job_id: #job.id ).exists?
Although what you've accepted will work, I think it's somewhat of a surface-level fix.
You'll be much better using validators to determine if the user can actually create another job application. This will protect against any problems with the business logic in your "front-end" views
Here's how I'd handle it:
--
Uniq
#app/models/user.rb
class User < ActiveRecord::Base
has_one :job_application
end
#app/models/job_application.rb
class JobApplication < ActiveRecord::Base
belongs_to :user
validates :user_id, uniquness: true
end
You may also wish to give your database a uniq index for your user_id column:
> $ rails g migration AddUniqueIndex
#config/db/add_unique_index.rb
class AddUniqueIndex < ActiveRecord::Migration
def change
add_index :job_applications, [:job_id, :user_id], unique: true
end
end
This will give you a highly efficient DB-level uniqueness index - meaning that if you try and add any more applications than is permitted, it will either fail silently, or come back with an error.
Controller
The structure of the controller would allow you to be less stringent about the accessibility of the job_application functionality:
#app/views/jobs/job.html.erb
<% if current_user.has_applied?(params[:job_id]) %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
#app/models/user.rb
class User < ActiveRecord::Base
has_many :job_applications
def has_applied?(job_id)
job_applications.find job_id
end
end

Rails 4 Stack level too deep Error

I'm creating a marketplace app where sellers can list items to sell. I want to create a category dropdown so customers can select a category to shop.
In my listing model, I have a 'category' field. When a user selects a category, I want the view to filter listings from that category.
In my routes.rb:
get '/listings/c/:category' => 'listings#category', as: 'category'
To create the category menu - in my index.html.erb:
<%= Listing.uniq.pluck(:category).each do |category| %>
<%= link_to category, category_path(category: category) %>
<% end %>
In my listings controller:
def category
#category = category
#listings = Listing.not_expired.where(:category => #category)
end
category.html.erb:
<% #listings.each do |listing| %>
#some html
<% end %>
The homepage category menu shows up. The routes are created. But when I click on the category, the url such as listings/c/necklaces gives me the stack level too deep error.
FYI "Stack Level Too Deep" basically means you have an infinite loop in your code somewhere
--
From what I can see, the error will be here:
def category
#category = category
With this code, you're basically invoking the category method again, which in turn will invoke the category method etc, in a never-ending cycle. This will prevent your application from being able to run without reloading itself in infinite recursion.
You should change it to:
def category
#category = params[:category]
#listings = Listing.not_expired.where(:category => #category)
end
However, a much more refined way would be:
#app/models/category.rb
class Category < ActiveRecord::Base
has_many :listings do
def not_available
#your not available method here
end
end
end
#app/models/listing.rb
class Listing < ActiveRecord::Base
belongs_to :category
end
#app/controllers/your_controller.rb
def category
#category = Category.find params[:categpry]
#listings = #category.listings.not_available

ActiveRecord Rescue from View

I build a little e-commerce solution. At the end of an order, I want the cart to delete, so a user can't access the same cart after ordering. After I render the order_confirmation page, I have an after_action [:show] that deletes the cart. If refreshed, the order confirmation page won't work because the line items(items teh user bought) were destroyed with the cart.
This is fine, I already sent them an email and if they refresh the confirmation page I'd like an activerecord::recordnotfound rescue.
my view looks like this though
<% #order.items.each do |id| %>
<% #line_item = LineItem.find(id) %>
(line item data to show)
When refreshed, the line_items delete and I get an activerecord error.
Couldn't find LineItem with id=8
Since this is being called from the view, and isn't exactly conventional -- how can I rescue to redirect and say something like "Your Cart is Reset" or something along those lines? I tried putting it in the controller but it didn't get touched (or didn't trigger..)
after_action :remove_cart, only: [:show]
def index
#orders = Order.all
end
def show
#order = Order.find(params[:id])
#cart = current_cart
rescue ActiveRecord::RecordNotFound
logger.error "User Refresh Page"
redirect_to products_path
raise
end
If it's super advised not to have a loop like that in the view, should create an array in the controller and then loop through them on the view and create the rescue in the controller?
UPDATE:
remove_cart method is pretty blunt, looks like this.
def remove_cart
#cart = current_cart
#cart.destroy
end
Update
I've followed Damien's instructions but the after commit
gives me this
(6.2ms) COMMIT
undefined method `true=' for #<Cart:0x00000106b9fb98>
Redirected to http://localhost:3000/orders/8
Completed 302 Found in 1933ms (ActiveRecord: 19.4ms)
with order.rb
after_commit :archive_cart, on: :create
private
def archive_cart
cart.archive!
end
and cart.rb as
def archive!
update_attribute(active, false)
end
This is based on our running conversation, here:
https://chat.stackoverflow.com/rooms/55591/discussion-between-peege151-and-damien-roche
1. Update your Order model to include:
class Order
belongs_to :cart
has_many :line_items, through: :cart
end
Then, in your view:
<% #order.line_items.each do |line_item| %>
2. As meager noted, you should archive order details. Add a status to your cart, such as:
add_column :carts, :active, :boolean, default: true
Then, instead of #cart = current_cart in your controller, refer to the cart directly via #order.cart.
3. Move destroy/archive_cart logic into your models
Note: this is not normal behaviour -- Order would usually have a status, and the cart would be archived when that Order is confirmed, but asker is using a preliminary model (OrderPreview), where each new Order is pre-confirmed.
class Order
after_commit :archive_cart, on: :create
private
def archive_cart
cart.archive!
end
end
class Cart
def archive!
update_attribute(:active, false)
end
end

Resources