Resources inside scope have wrong update path - ruby-on-rails

I have the following routes:
namespace :admin do
scope 'users/:user_id' do
resources :orders
end
end
In my controller I have the following:
before_action :find_user
before_action :find_order, only: [:show, :edit, :update, :destroy]
...
def new
#order = #user.orders.new
end
def edit
end
def create
#order = #user.orders.new(order_params)
...
end
def update
if #order.update(order_params)
...
end
...
def find_user
#user = User.find(params[:user_id])
end
def find_order
#order = Order.find(params[:id])
end
My form_with is looking like this:
= form_with model: [:admin, #order], local: true do |f|
etc
The new and create paths are working correctly, and generating the good path: /admin/user/(user_id)/orders
However, the update_path isn't working correctly, and generates the following path:
/admin/user/(order_id)/orders/(order_id).
How can I fix this?

Try to add your #user to a model parameter:
= form_with model: [:admin, #user, #order], local: true do |f|
Too instead of scope you can use nested resources:
namespace :admin do
resources :users do
resources :orders
end
end

Related

How should I update a resource that is fully depend on other resource

Let's say I have those two models:
class Post < ApplicationRecord
belongs_to :site
end
class Site < ApplicationRecord
has_many :posts
end
In order to create a post, I need to know the site id. Right now I have a route that points to PostsController#create:
post 'posts', to: 'posts#update'
Should I expect the user to send the site_id in the body of the request?
# config/routes.rb
resources :sites do
resources :posts
end
This creates nested routes. Run $ rails routes to see the routes created.
Should I expect the user to send the site_id in the body of the request?
No. A nested route describes the relationship between the two resources. Its very obvious by looking at the path that POST /sites/1/posts will create a post belonging to a site.
It would be ok to pass a site id in the params if you are using shallow nesting and the user can change which site a post belongs to when updating.
# app/controllers/posts_controller.rb
class PostsController
before_action :set_site
before_action :set_post, only: [:show, :edit, :update]
# GET /sites/1/posts/1
def show
end
# GET /sites/1/posts
def index
#posts = #site.posts
end
# GET /sites/1/posts/new
def new
#post = #site.posts.new
end
# POST /sites/1/posts
def create
#post = #site.posts.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
end
# PATCH|PUT /sites/1/posts
def update
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
# GET /sites/1/posts/edit
def edit
end
private
def set_site
#site = Site.includes(:posts).find(params[:site_id])
end
def set_post
#post = #site.posts.find(params[:id])
end
def post_params
params.require(:post).permit(:title) # ...
end
end
# app/views/posts/_form.html.erb
<%= form_for [#site, #post] do |f| %>
# ...
<% end %>
# app/views/posts/new.html.erb
<%= render partial: 'form' %>
# app/views/posts/edit.html.erb
<%= render partial: 'form' %>

ruby - run http://localhost:3000/admin/users but redirect_to http://localhost:3000/admin/login

I am new in Ruby on Rails.
I want to run http://localhost:3000/admin/users to see users index page.
But when I run this link, it guide me to http://localhost:3000/admin/login.
Is there something wrongs with my route setting?
Rails.application.routes.draw do
get 'users/new'
get 'users/show'
if Rails.env.development?
mount LetterOpenerWeb::Engine, at: '/letter_opener'
end
root to: 'helps#top'
# admin login
get 'admin/login', to: 'admin/login#index', as: 'admin/login'
get 'admin/logout', to: 'admin/login#logout'
post 'admin/login/login'
get 'admin', to: 'admin/projects#index', as: 'admin_top'
namespace :admin do
resources :users, only: %i(index new create)
resources :projects do
resources :project_users
resources :project_comments
end
resources :images
resources :categories
resources :campanies
end
end
users_controller.rb
class Admin::UsersController < AdminController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
#users = User.all
end
def show
end
def new
#user = User.new
end
def edit
end
#Post /admin/projects
def create
#user = User.new(user_params)
if #user.save
flash[:notice] = 'User saved successfully'
redirect_to :back
else
flash[:alert] = #user.errors
binding.pry
render :new
end
end
def update
end
def destroy
end
private
def set_user
#user = User.find(params [:id])
end
def user_params
params.require(:user).permit(:campany_id, :name, :email, :password_digest, :profile, :prefecture_id, :address)
end
end
Thank you!
Your UsersControllers is under the admin namespace, that's to say you must be logged in order to access to this.
If you want to have access without validating the user is currently logged in, then you'll have to remove the constraint or verification to the controller or to make a new controller and index method which point to /admin/users but this time without the user verification.
That's:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
...
def index
#users = User.all
end
...
end
# config/routes.rb
get '/users', to: 'users#index'
'/users' or '/admin/users' as you want to do it, but if you use the last one then any person must infer that's a restricted resource .

undefined local variable or method `product' for #<#<Class:0x007fe77c4f3c68>:0x007fe77c69cb78>

I´m having a problem with an app that I´m building.
in products/show.html.erb I have this code to add product to cart.
<%= button_to product_items_path(product_id: product) do %>
<i class="fa fa-shopping-cart"></i>Add to Cart
<% end %>
And it always gives me this error undefined local variable or method 'product' for #<#<Class:0x007fe77c4f3c68>:0x007fe77c69cb78>
This error is happening in the first line According to Better Error gem
I'am using ActiveAdmin but I'm pretty sure that the error is not appearing because of that.
I'm not sure why this is happening, to me the code seems good but I must be overseeing something.
It would be great if someone could take look and maybe see what I´m not seeing.
This is the `ProductItemsController.rb``
class ProductItemsController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:create]
before_action :set_product_item, only: [:show, :destroy]
def create
product = Product.find(params[:product_id])
#product_item = #cart.add_product(product.id)
if #product_item.save
redirect_to root_url, notice:'Product added to Cart'
else
render :new
end
end
private
def set_product_items
#product_item = ProductItem.find(params[:id])
end
def product_item_params
params.require(:product_item).permit(:product_id)
end
end
And here is the ProductsController.rb
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
def show
end
private
def set_product
#product = Product.find(params[:id])
end
def product_params
params.require(:product).permit(:name, :description, :price_usd, :price_isl, :image, :category_id)
end
end
this is the routes.rbfile
Rails.application.routes.draw do
resources :categories
resources :labels
resources :products
resources :carts
resources :product_items
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
root 'pages#index'
Only instance variables are available to the view.
def create
#product = Product.find(params[:product_id]) # Prefix variable name with #
#product_item = #cart.add_product(product.id)
if #product_item.save
redirect_to root_url, notice:'Product added to Cart'
else
render :new
end
end
And your view:
<%= button_to product_items_path(#product) do %>
<i class="fa fa-shopping-cart"></i>Add to Cart
<% end %>
You should be able to just pass in the object to the _path helper.

Can't save form_for one controller in view partial, rails 4

This has been asked on SO a lot before, but I can't find anything that quite applies. What I'm trying to do is render an edit form for SettingsController in the edit view of UsersController. I'm super new to RoR, so I'm not even sure what I'm doing wrong.
This questions seems closest, but when I initialize #setting = Setting.new in the Users controller, I get a Settings form without the defaults set for new users in the migration. But if I initialize #setting = Setting.edit or Setting.update, I get an undefined method or wrong number of arguments error.
When the Setting.new form is saved, it throws this error:
undefined method for find_by_id in the SettingsController: app/controllers/settings_controller.rb:43:in `correct_user'.
When I check the database, the settings records are being correctly created when a user is created, but the settings record is not updated when the form is saved.
setting.rb:
class Setting < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
user.rb:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
has_one :setting, dependent: :destroy
after_create :create_setting
end
UsersController:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update, :index, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
#setting = Setting.update
end
def update
#user = User.find(params[:id])
#setting = Setting.update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated!"
redirect_to #user
else
render 'edit'
end
end
def settings
#title = "Settings"
#setting = Setting.find_by_user_id(params[:user_id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
the SettingsController:
class SettingsController < ApplicationController
before_action :logged_in_user, only: [:create, :edit, :update, :show, :index]
before_action :correct_user, only: [:create, :edit, :update, :show, :index]
def index
#settings = Setting
end
def show
#setting = User.find(params[:id]).setting
end
def new
#setting = Setting.new
end
def edit
#setting = Setting.find(params[:id])
end
def create
#setting = current_user.settings.build(setting_params)
#setting.save
end
def update
#setting = Setting.find(params[:id])
if #setting.update_attributes(post_params)
flash[:success] = "Settings updated!"
redirect_to request.referrer
else
render 'edit'
end
end
private
def setting_params
params.require(:setting).permit(:reading_theme)
end
def correct_user
#setting = current_user.setting.find_by_id(params[:id]) ##the line which throws an error when the form is saved
redirect_to root_url if #setting.nil?
end
end
The form partial:
<%= form_for(#setting) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= radio_button_tag(:reading_theme, "flatly") %>
<%= label_tag(:reading_theme_flatly, "Light (Default)") %>
</div>
<div class="field">
<%= radio_button_tag(:reading_theme, "darkly") %>
<%= label_tag(:reading_theme_darkly, "Dark") %>
</div>
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
routes.rb:
resources :users do
member do
get :following, :followers
end
end
resources :settings, only: [:new, :create, :edit, :update]
...
ETA: the settings migration:
class CreateSettings < ActiveRecord::Migration
def change
create_table :settings do |t|
t.string :reading_theme, default: => "flatly"
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
How do I get the proper defaults so that my form can be saved correctly?
Any defaults that you include for fields in the migration will be "unknown" to the model class (Setting) in Ruby. Ruby (or rather Rails ActiveRecord) does not read the default values from the table definition when creating a model object. This can lead to a dual personality problem like you're seeing here.
What you have to do is to add the relevant defaults into the Ruby code, where appropriate. For example, in your Settings controller, you can make these changes:
def new
#setting = Setting.new
# Set any defaults that will be visible to the user on the form
#setting.reading_theme = "flatly"
# The form will allow the user to choose their own values, based on the defaults
end
def create
#setting = current_user.settings.build(setting_params)
# Set any defaults that will NOT be visible to the user
#setting.save
end
This gives you the ability to distinguish between default values that are visible to the user and defaults that are not.
Note that you also have the option of establishing defaults when you create the model object, but this may be more complicated in some situations, and seems to be far less common in practical use. There's an SO answer for that in How to initialize an ActiveRecord with values in Rails?, in case this better suits your needs.
Not can use find_by_id in has_one relationship
#setting = current_user.setting.find_by_id(params[:id])
Just #setting = current_user.setting

Adding acts_as_votable voting to posts and comments, routing issue?

I am trying to use acts_as_votable voting system on both posts and comments in my rails app. I am currently generating some obviously improper routes for my comments#upvote and comments#downvote, Here they are below:
upvote_post PUT /posts/:id/upvote(.:format) comments#upvote
downvote_post PUT /posts/:id/downvote(.:format) comments#downvote
But they routes need to be something like /posts/comment/:id/downvote. Here is how I am currently doing my routes
resources :posts do
member do
put "like", to: "posts#upvote"
put "dislike", to: "posts#downvote"
end
resources :comments
member do
put "upvote", to: "comments#upvote"
put "downvote", to: "comments#downvote"
end
end
Also, will I need two votes tables since I want both comments and posts to be votable?
Here is my my comments controller if needed:
class CommentsController < ApplicationController
before_filter :authenticate_user!, :except => [:index, :show]
def index
#post = Post.find(params[:post_id])
#user = User.find(params[:user_id])
#comments = #post.comments.order('created_at desc')
end
def new
#post = Post.find(params[:post_id])
#comment = #post.comments.new(params[:id])
end
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(comment_params)
#comment.post_id = #post.id
#comment.user_id = current_user.id
if #comment.save
redirect_to post_comments_path(#post)
else
redirect_to new_post_comment_path(post)
end
end
def destroy
end
def upvote
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.upvote_by current_user
redirect_to post_comments_path(#post)
end
def downvote
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.downvote_by current_user
redirect_to post_comments_path(#post)
end
private
def comment_params
params.require(:comment).permit(:body, :post_id, :user_id)
end
end
Thanks for the help
As a rule you probably don't want to nest your routes so deep. And the comment vote doesn't need to know the id of the post, just the comment. You also might find that rather than have a separate method for up and down voting in the controller having the votes go to the VotesController and just handle it there
So, I would do something like this:
resources :posts do
resources :comments, only: [:new, :create, :destroy]
resources :votes, only: [:create, :update, :destroy]
end
resources :comment do
resources :votes, only: [:create, :update, :destroy]
end
That's if you want to have votes be a polymorphic relation on both in which case in your ApplicationsController you'd want a method like:
def find_model
params.each do |name, value|
if name =~ /(.+)_id\z/
return $1.classify.constantize.find(value)
end
end
nil
end
# VotesController
def create
model = find_model
if model
# do stuff
end
end
If you have separate votes tables you can skip this last part.
Had a similar problem fixed it by changing
put "like", to: "posts#upvote"
put "dislike", to: "posts#downvote
to
get "like", to: "posts#upvote"
get "dislike", to: "posts#downvote"
posts_controller
def upvote
#post.upvote_by current_user
redirect_to :back
end
def downvote
#post.downvote_by current_user
redirect_to :back
end
post show page
<%= link_to "like", like_post_path(#post), method: :get %>
<%= link_to "dislike", dislike_post_path(#post), method: :get %>
hope this helps

Resources