There is the following routing:
resources :accounts, only: [:update] do
get 'search', on: :collection
resources :transactions, only: [:create]
end
Abilities:
can [:update, :search], Account
can [:create, :index], Transaction
Controller:
# Web API controller for actions on Transaction
class Api::V1::Web::TransactionsController < Api::V1::Web::ApplicationController
load_and_authorize_resource :account
load_and_authorize_resource :transaction, through: :account
def create
render json: params and return
end
end
When I try to create a new transaction I get an error:
CanCan::AccessDenied
in Api::V1::Web::TransactionsController#create
What am I doing wrong? How can I fix it? Thanks in advance.
Related
I have been trying to add the name of the BlogCategory that a BlogPost belongs to in a URL such as this:
sitename.com/blog/category-name/blog-post-title
At the very least, I want this to render for the show of the BlogPost but am okay with it being the url for every action such as new, edit, and destroy.
I'm using the friendly_id gem, if that makes a difference.
BlogCategory Model:
class BlogCategory < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
has_many :blog_posts
# This is a self referential relation. This is where records in a table may point to other records in the same table.
has_many :sub_categories, class_name: "BlogCategory", foreign_key: :parent_id
has_many :sub_category_blog_posts, through: :sub_categories, source: :blog_posts
belongs_to :parent, class_name: 'BlogCategory', foreign_key: :parent_id, optional: true
# This is a scope to load the top level categories and eager-load their posts, subcategories, and the subcategories' posts too.
scope :top_level, -> { where(parent_id: nil).includes :blog_posts, sub_categories: :blog_posts }
def should_generate_new_friendly_id?
slug.nil? || name_changed?
end
end
BlogCategory Controller:
class BlogCategoriesController < ApplicationController
before_action :admin_user, only: [:new, :create, :edit, :update, :destroy]
before_action :set_blog_link, only: [:show, :edit, :update, :destroy]
...
private
def cat_params
params.require(:blog_category).permit(:name, :parent_id, :sub_category, :summary)
end
def main_cat
#cat = BlogCategory.parent_id.nil?
end
def set_blog_link
#blog_link = BlogCategory.friendly.find(params[:id])
redirect_to action: action_name, id: #blog_link.friendly_id, status: 301 unless #blog_link.friendly_id == params[:id]
end
end
BlogPost Model:
class BlogPost < ApplicationRecord
extend FriendlyId
friendly_id :title, use: :history
belongs_to :blog_category
validates :title, presence: true, length: { minimum: 5 }
validates :summary, uniqueness: true
default_scope {order(created_at: :desc)}
def should_generate_new_friendly_id?
slug.nil? || title_changed?
end
end
BlogPost Controller:
class BlogPostsController < ApplicationController
before_action :admin_user, only: [:new, :create, :edit, :update, :destroy]
before_action :set_post_link, only: [:show, :edit, :update, :destroy]
before_action :find_post, only: :show
...
private
def post_params
params.require(:blog_post).permit(:title, :body, :summary, :thumbnail_link, :blog_category_id)
end
def find_post
#post = BlogPost.friendly.find(params[:id])
# If an old id or a numeric id was used to find the record, then
# the request path will not match the post_path, and we should do
# a 301 redirect that uses the current friendly id.
if request.path != blog_post_path(#post)
return redirect_to #post, :status => :moved_permanently
end
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
def set_post_link
#post_link = BlogPost.friendly.find(params[:id])
redirect_to action: action_name, id: #post_link.friendly_id, status: 301 unless #post_link.friendly_id == params[:id]
end
end
Here is relevant code from my routes.rb file:
Rails.application.routes.draw do
resources :blog_categories, path: 'blog'
resources :blog_posts
end
What I've tried
I've tried the following without success but have little understanding of what I'm doing:
get 'blog/:blog_category_name/:blog_post_title', to: 'blog_posts#show', as: 'blog_post'
and also tried
resources :blog_posts, path: 'blog/:blog_category_name/:blog_post_title', except: [:new, :create]
resources :blog_posts, only: [:new, :create]
With this in my BlogPost controller inside the show method/block:
#post_url = BlogPost.find_by(title: params[:blog_post_title], blog_category_id: params[:blog_category_name])
I even tried adding the params used in the routes to the permitted list under post_params.
I also tried making a new post to see if old posts weren't linking properly because of the url structure change.
The URL's I'm getting are not utilizing the parameters I'm passing to them.
What you're doing here is really just a nested resource but with a vanity route and and slugging which doesn't actually require such a heavy hand.
The typical controller for a nested resource would look like this:
class BlogPostsController < ApplicationController
before_action :set_blog_category
before_action :set_blog, only: [:show, :edit, :update, :delete]
# GET /blog/foo/bar - your custom vanity route
# the conventional route would be
# GET /blog_categories/foo/blog_posts/bar
def show
end
# GET /blog/foo/blogs_posts -> index
# GET /blog/foo/blogs_posts/new -> new
# POST /blog/foo/blogs_posts -> create
# ...
private
def set_blog_category
#blog_category = BlogCategory.friendly.find(params[:blog_category_id])
end
def set_blog
#blog = Blog.friendly.find(params[:id])
end
end
Besides the fact that you're using friendly.find you don't actually need to do anything to do the lookup via slugs instead of the id column. If you want to find the records only by their friendly id (and not allow numerical ids) use the find_by_friendly_id method instead.
Note that :id (or _id) in a parameter name is not equal to the id column - it's just a name for the unique indentifier segment in the URI pattern.
While you can configure the name of the param its actually kind of silly as in Rails things just work when you stick with the conventions.
You can just define the vanity route for this as:
resources :blog_categories, path: 'blog', only: [] do
# the typical routes nested under "blog_posts"
resources :blogs_posts, only: [:new, :create]
# your custom vanity route should be defined last to avoid conflicts
resources :blogs_posts, path: '/', only: :show
end
Generating the URL can be done either by calling the named blog_category_blog_path helper or by using the polymorphic route helpers:
blog_category_blog_path(#blog_category, #blog_post)
redirect_to [#blog_category, #blog_post]
form_with model: [#blog_category, #blog_post]
If you have legacy URLs using a different structure that you want to redirect I would consider using a separate controller or just doing the redirect in the routes to separate out the responsibilities from this controller.
You also should avoid duplicating the authorization/authentication logic across your controllers (your admin_user method). Thats how you get security holes.
I'm using ActiveResource to pull objects from an internal API
This API has the following(simplified):
class Project < ApplicationRecord
has_many :contributions
...
end
class Contribution < ApplicationRecord
belongs_to :project
belongs_to :user
...
end
class User < ApplicationRecord
has_many :contributions
...
end
and routes so contributions can only be created associated to a project
resources :projects, except: [:new, :edit] do
resources :contributions, except: [:new, :edit, :destroy]
end
resources :users, except: [:new, :edit] do
resources :contributions, only: [:index, :show]
end
resources :contributions, only: [:index, :show, :update]
Is it possible to submit a dynamic prefix so that I can hit these paths selectively? i.e. projects/:project_id/contributions on create, but /contributions on index (all).
EDIT:
My active resources all look like so:
class Contribution < ActiveResource::Base
self.site = "#{base_url}/api/v1/"
self.headers['Authorization'] = "Token token=\"#{TokenGenerator}\""
...
end
Not much customization there.
My biggest concern is the create post which I would like to always be nested inside a project.
At the moment I'm checking params in the /contributions route to see if there is any viable 'parent_id' in them, and figuring out if said parent exists.
I'm guessing the gem was not designed with the idea of a resource having multiple routes. I can always include:
class Project ActiveResource::Base
self.site = "#{base_url}/api/v1/"
self.headers['Authorization'] = "Token token=\"#{TokenGenerator}\""
...
def contributions
Contributions.all(params: {project_id: id})
end
end
inside Projects.rb and make sure the API controller knows how to handle parents if they exist, but only because I have access to the source of both the API and the consumer app.
worth asking too: Am I just over complicating this?
I have a weird error when I want to redirect users to the root_url when they try to access blogs/new url in my app.
My routes are
resources :blogs, only: [:index, :show] do
resources :comments, only: [:create]
end
namespace :admin do
resources :blogs
resources :users, only: [:index, :show]
resources :comments, only: [:create, :new, :destroy]
end
My non-admin blogs controller looks like this:
class BlogsController < ApplicationController
before_action :set_blog, only: [:show]
def show
unless #blog
redirect_to blogs_path
flash[:notice] = "You are not authorized to create a post."
end
end
def index
#blogs = Blog.all
end
private
def set_blog
#blog = Blog.find(params[:id])
end
end
I get the error Couldn't find Blog with 'id'=new.
In rails, the priority of routes goes from top to bottom. Meaning, when you try to hit /blogs/new, the route gets matched with the show action of blogs defined in the top of your routes.rb.
blogs/new gets matched with /blogs/:id which is mapped to blogs#show action.
In the set_blog method, params[:id] is new and since there is no record with the id of new, you're getting that weird error.
How to get around this? Change the priority of your routes.
Move the following block below the admin namespaced routes.
namespace :admin do
resources :blogs
resources :users, only: [:index, :show]
resources :comments, only: [:create, :new, :destroy]
end
resources :blogs, only: [:index, :show] do
resources :comments, only: [:create]
end
By the way, your question says that you want to avoid non-admin users to access blogs#new. If that's the case, you should try to hit /admin/blogs/new and not /blogs/new.
If you had done that, you wouldn't have gotten the error in the first place. But still, its good to know about the priority of routes in rails.
Hope this helps!
I'm trying to use the link_to helper function to create a new order for a particular product. Here is my:
product model
class Product < ActiveRecord::Base
has_many :orders
end
routes.rb
resources :products, :only => [:show, :new, :create, :index, :update, :destroy] do
resources :orders, :only => [:create]
end
view for product/show.html.erb
<%= link_to 'New Order', new_product_orders_path(#product) %>
controller for orders
class OrdersController < ApplicationController
def create
#order = Order.new
end
end
relevant rake routes:
product_orders POST /products/:product_id/orders(.:format) orders#create
But when I do that I get undefined method `new_product_orders_path'
Whats the correct way to do this in Rails 4?
In your routes add new action here
resources :orders, :only => [:create, :new]
Also your controller is missing new action, in your create action you need to save your record
class OrdersController < ApplicationController
before_filter :set_product
def new
#order = #product.orders.new
end
def create
#order = #product.orders.new(params[:order])
#order.save
end
private
def set_product
#product = Product.where("id =?", params[:product_id]).first
end
end
I think you need
resources :products, :only => [:show, :new, :create, :index, :update, :destroy] do
resources :orders, :only => [:create, :new]
end
You can also check your routes by typing '/rails/info/routes' at the end of your server path.
I'm refactoring my application to use 1 level deep nested resources everywhere, it's a JSON-only API. Here's a trimmed version of my routes.rb:
resources :measurements, only: [:index, :show] do
resource :tag_node, controller: :physical_nodes, only: [:show]
resource :anchor_node, controller: :physical_nodes, only: [:show]
resource :experiment, only: [:show]
end
resources :physical_nodes, only: [:index, :show] do
resources :tag_nodes, controller: :measurements, only: [:index]
resources :anchor_nodes, controller: :measurements, only: [:index]
end
resources :experiments, only: [:index, :show] do
resources :measurements, only: [:index]
end
And my trimmed down models:
class Measurement < ActiveRecord::Base
self.table_name = 'measurement'
self.primary_key = 'id'
belongs_to :physical_node, foreign_key: :tag_node_id
belongs_to :physical_node, foreign_key: :anchor_node_id
belongs_to :experiment, foreign_key: :experiment_id
end
class PhysicalNode < ActiveRecord::Base
self.table_name = 'physical_node'
self.primary_key = 'id'
has_many :measurements, foreign_key: :tag_node_id
has_many :measurements, foreign_key: :anchor_node_id
end
class Experiment < ActiveRecord::Base
self.table_name = 'experiment'
self.primary_key = 'id'
has_many :measurements, foreign_key: :experiment_id
end
1.:
What works:
GET /experiments/4/measurements.json works fine
What doesn't work: (everything else ;) )
GET /measurements/2/experiment.json
Error message:
Processing by ExperimentsController#show as HTML
Parameters: {"measurement_id"=>"2"}
ActiveRecord::RecordNotFound (Couldn't find Experiment without an ID)
This should be easy to fix. More important is:
2.:
GET "/measurements/2/tag_node"
Processing by PhysicalNodesController#show as HTML
Parameters: {"measurement_id"=>"2"}
How can I get rails to call it tag_node_id instead of measurement_id?
Solution:
After a long chat with 'dmoss18', it became clear that it makes no sense to put the tag_nodes and anchor_nodes as child elements of physical_nodes, as they only exist in the measurements table.
So now my routes.rb looks like this:
resources :measurements, only: [:index, :show, :create]
resources :physical_nodes, only: [:index, :show]
resources :tag_nodes, only: [] do
resources :measurements, only: [:index]
end
resources :anchor_nodes, only: [] do
resources :measurements, only: [:index]
end
resources :experiments, only: [:index, :show] do
resources :measurements, only: [:index]
end
I've also removed all those only childs, because this is not the way the database was designed.
1: Your ExperimentsController#show action is likely looking for params[:id] to find the experiment when rails is passing in the measurement id. You will need to do something like the following:
#experiment = Experiment.find(params[:measurement_id])
However, this will not work since your experiment table doesn't have a measurement_id column. I wouldn't suggest nesting experiments as a resource of measurements. That is not how your database is laid out. If you still want to nest it though, here is what you need to do:
#experiment = Measurement.find(measurement.experiment_id).experiment
Your "has_many" and "belongs_to" don't need the "foreign_key" attribute on them. Rails will take care of this itself.
2: Since your parent resource of this route is Measurement, rails will assume the id parameter is called :measurement_id. Update your associations like this:
class Measurement < ActiveRecord::Base
self.table_name = 'measurement'
self.primary_key = 'id'
belongs_to :tag_node, :class_name => 'PhysicalNode', :foreign_key => :tag_node_id
belongs_to :anchor_node, :class_name => 'PhysicalNode', :foreign_key => :anchor_node_id
belongs_to :experiment
end
class PhysicalNode < ActiveRecord::Base
self.table_name = 'physical_node'
self.primary_key = 'id'
has_many :tag_node, :class_name => 'Measurement', :foreign_key => :tag_node_id
has_many :anchor_node, :class_name => 'Measurement', :foreign_key => :anchor_node_id
end
class Experiment < ActiveRecord::Base
self.table_name = 'experiment'
self.primary_key = 'id'
has_many :measurements
end
I would not nest anything under the measurements resource since it is a child resource, and not a parent resource. Since your /measurements/1/[anythingelse] is a measurements route, rails assumes the id is called :measurement_id. You are nesting things under the measurement resource/object whose id is 1. In other words, you are saying that measurement x HAS tag_nodes and HAS anchor_nodes, which isn't really true.
If you still wanted to, you could create individual actions in your controller for each resource, like this:
resources :measurements, only: [:index, :show] do
resource :tag_node, controller: :physical_nodes, only: [:show_tag]
resource :anchor_node, controller: :physical_nodes, only: [:show_anchor]
resource :experiment, only: [:show]
end
Create a show_tag and show_anchor action in your physical_nodes controller. These actions would then look for params[:measurement_id)