I have a relationships model (for following and followers) and my project is also aimed at providing an api. Now there is a profile api that sends out the name, username, amount of followers and following. If I send a get request to my channel controller then I get the name, username and the followers and following as []. If someone follows the user I get a "stack level too deep" in console. I have no idea what the cause to this could be and how I could fix this? Any thoughts?
Also, here is some more info:
show.json.jbuilder (my json show page)
json.call(
#user,
:username,
:name,
:following,
:followers
)
profiles_controller.rb
class API::V1::ProfilesController < ApplicationController
skip_before_action :configure_permitted_parameters, only: [:show]
respond_to :json
def show
#user = User.friendly.find(params[:id])
end
end
routes.rb
Rails.application.routes.draw do
mount API::Base, at: "/"
resources :relationships
namespace :api do
namespace :v1 do
resource :sessions, only: [:create, :destroy]
resources :registrations
resources :profiles do
member do
get :following, :followers
end
end
end
end
end
The follower and following amount works on the html webpage and I can also follow other people through the html webpage. It seems as the issue is in the json?
You should declare the content for following/followers in jbuilder
json.call #user, :username, :name
json.following #user.following, :username, :name
json.followers #user.followers, :username, :name
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.
so I have a route that is a bit more complex and I have an issue with the order of two nested scopes. Their order seems to be reversed. I want the most inner scope to be the last segment of the URL before the action. But it is the first.
routes.rb
namespace :customer do
namespace :api do
resources :products, only: [], param: :uid do
scope module: 'products' do
scope :buyer do
post :set_to_waiting_list, to: 'buyers#set_to_waiting_list'
end
end
end
end
end
Controller:
module Customer
module Api
module Products
class BuyersController < Customer::ApiController
def set_to_waiting_list
# do stuff
end
end
end
end
end
This gives me this route when running rake routes:
customer_api_product_set_to_waiting_list POST /customer/api/buyer/products/:product_uid/set_to_waiting_list(.:format) customer/api/products/buyers#set_to_waiting_list
But the URL I'm actually looking for is:
POST /customer/api/products/:product_uid/buyer/set_to_waiting_list
Reason is that this modifies the buyer and not the product. Also the buyer is fetched via the product uid (plus logged in user), so this URL format makes much more sense.
I don't really understand
I'm still interested in the logic behind this. But for anyone looking for an solution, I just solved it with:
namespace :customer do
namespace :api do
resources :products, only: [], param: :uid do
scope module: 'products' do
post :set_to_waiting_list, path: 'buyer/set_to_waiting_list' to: 'buyers#set_to_waiting_list'
end
end
end
end
You need to add a nested block so that the scope knows where to place the path prefix:
namespace :customer do
namespace :api do
scope module: 'products' do
resources :products, only: [], param: :uid do
nested do
scope :buyer do
post :set_to_waiting_list, to: 'buyers#set_to_waiting_list'
end
end
end
end
end
end
nested is not documented, so I'm not sure if you should rely on this. As an alternative, you can add a resource :buyers, only: [] {} wrapper:
namespace :customer do
namespace :api do
scope module: 'products' do
resources :products, only: [], param: :uid do
resource :buyer, only: [] do
collection do
post :set_to_waiting_list
end
end
end
end
end
end
The route and controller are the same in both cases, but the URL helper is different:
customer_api_product_set_to_waiting_list
POST /customer/api/products/:product_uid/buyer/set_to_waiting_list(.:format)
customer/api/products/buyers#set_to_waiting_list
vs
set_to_waiting_list_customer_api_product_buyer
POST /customer/api/products/:product_uid/buyer/set_to_waiting_list(.:format)
customer/api/products/buyers#set_to_waiting_list
Source: https://github.com/rails/rails/issues/12626
I get posts by :title instead :id
routes.rb:
Rails.application.routes.draw do
#RSS
get 'feed' => 'posts#feed'
get 'archive' => 'posts#archive'
devise_for :users, controllers: { omniauth_callbacks: "callbacks" }
root to: "posts#index"
resources :posts, param: :title do
resources :comments, only: [:new, :create, :destroy]
resources :images do
resources :comments, only: [:new, :create]
end
resources :links do
resources :comments, only: [:new, :create]
end
resources :photos, only: [:new, :create,:destroy]
resources :songs, only: [:new, :create, :destroy]
end
post_controller:
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create, :show]
respond_to :html, :json, :rss, :atom
def index
if params[:search].blank?
#posts = Post.includes(:comments, :photos).all
else
#search = Post.search do
fulltext params[:search]
end
#posts = #search.results
end
respond_with(#posts)
end
def show
set_post
end
def new
#post = Post.new
end
def edit
#post = Post.find_by(title: params[:title])
end
def create
#post = Post.new(post_params)
#post.errors.add(:base, :invalid) unless #post.save
respond_with(#post)
end
def update
set_post # this #post dont get post becouse browser pass wrong title and rails dont find it
if #post.valid? # here error becouse #post hasnt find
#post.update(post_params)
else
#post.errors.add(:base, :invalid)
end
respond_with(#post)
end
def destroy
set_post
#post.destroy
respond_with(#post)
end
def feed
#posts = Post.all.reverse
respond_with(#posts)
end
def archive
#posts_by_year = Post.limit(300).all.order("created_at DESC").
group_by {|post| post.created_at.beginning_of_year}
end
private
def set_post
#fix N+1 queries and find by posts title
#post = Post.includes(:comments, :photos, :links).find_by(title: params[:title])
end
def post_params
params.require(:post).permit(:title, :content)
end
end
When I create new post, if I include in my post title a dot, I get from Rails error. Therefore I use validation format: method for this case.
post.rb:
class Post < ActiveRecord::Base
#overwrite the to_param for changing routes from posts/id to posts/title
def to_param
title
end
has_many :comments, dependent: :destroy, as: :commentable
has_many :images, dependent: :destroy
has_many :links, dependent: :destroy
has_many :photos, dependent: :destroy
has_many :songs, dependent: :destroy
validates :content, presence: true
validates :title, presence: true, length: { maximum: 100 }
validates :title, format: { without: /\./,
message: "must be without dot" }
searchable do
text :title, :content
end
end
After this, when I update the post,my validation format method works and I get my validation message 'must be without dot'. Well. I delete in my post title input field dots and submit form. Now browser send to server previous post title value with dot.
Started PATCH "/posts/my%20test%20post%20title%20with%20dot." for 127.0.0.1 at 2017-01-26 11:57:34 +0300
ActionController::RoutingError (No route matches [PATCH] "/posts/my%20test%20post%20title%20with%20dot."):
Therefore rails can't find post by title with dot an I get the error.
What can I solve this problem? Maybe :title instead :id in this case is bad idea?
I assume you're using form_for? Rails will set the title on the post object then run validation. When it fails it will display the edit view, form_for will then use the to_param method to set the url, which will use the posts updated title. When you try and update again it'll use the title from the url to try and find the post, but won't be able to because there isn't a post in the database with that title.
You should use something like friendly_id but if you really want to roll your own then a simple implementation would be to have a slug column which gets set based on the title after_validation but obviously you're going to have to make sure it's unique so personally I'd switch to friendly id or another gem that deals with slugs.
I need to define a method/action in my LessonsController that I can call from the lesson show action that marks the lesson as being completed by the current user. What does that controller method look like?
Here's the overview of my models:
User
has_many :completions
has_many :completed_steps, through: :completions, source: :lesson
Lesson
has_many :completions
has_many :completed_by, through: :completions, source: :user
Completions
belongs_to :user
belongs_to :lesson
My Completions Table looks like this:
t.integer "user_id"
t.integer "lesson_id"
t.boolean "completed_step"
t.string "completed_by"
I'm assuming in the LessonsController it looks like this
def complete_step
self.completions.create(completed_step: true, user_id: current_user.id)
end
Routes info:
Rails.application.routes.draw do
namespace :admin do
resources :users
resources :coupons
resources :lessons
resources :plans
resources :roles
resources :subscriptions
resources :completions
root to: "users#index"
end
devise_for :users, :controllers => { :registrations => "registrations"}
# Added by Koudoku.
mount Koudoku::Engine, at: 'koudoku'
scope module: 'koudoku' do
get 'pricing' => 'subscriptions#index', as: 'pricing'
end
resources :lessons do
member :complete
end
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
root 'pages#home'
get '/dashboard' => 'pages#dashboard', :as => 'dashboard'
mount StripeEvent::Engine, at: '/stripe-events' # prov
end
Here's my button link to make this functional.
<%= button_to "Mark this Lesson as Complete", complete_lesson_path(#lesson), method: :put, class: "btn btn-warning btn-lg" %>
Will this work or am I WAY off? Thanks!
Keep this is the LessonsController, but change it in either of the following ways:
def complete_step
current_user.completions.create(completed_step: true, lesson_id: #lesson.id)
end
# ~~ OR ~~
def complete_step
#lesson.completions.create(completed_step: true, user_id: current_user.id)
end
Both of these assume that you've already set #lesson in the controller, probably in a before_action :set_lesson.
EDIT:
If you need a route suggestion, then assuming you have resources :lessons in your routes file, you can either use an existing route (likely update) or add a member route like this:
resources :lessons do
get 'complete', on: :member
end
If you add a route, then you will need to add an action that looks like
def complete
complete_step
redirect #lesson
end
or similar, however you want to handle the response itself. You will also need to ensure that #lesson is set, so you should tweak your before_action :set_lesson, only: [:show, :update, ...] to also include :complete
Please try with below code.in your completion controller
def create
#lession = Lession.find(params[:lession_id])
#completion = current_user.completions.create(completed_step: true, lesson_id: #lesson.id)
redirected_to #completion
end
You can also just pass user: current_user to the completions.create method instead of passing in the current_user.id
Something like #lesson.completions.create(completed_step: true, user: current_user)
For my rails application the associations are as follows:
A user has many bookmarks and belongs to user.
A user has many friendships.
A user has many reminders.
A user has many comments.
A bookmark has many comments.
A comment belongs to a user and belongs to a bookmark.
A friendship belongs to a user.
A reminder belongs to a user
My routes.rb file:
Rails.application.routes.draw do
root 'welcome#index'
get 'home', :to => 'home#index'
get 'searchApis', :to => 'home#searchApis'
devise_for :users, :controllers => { registrations: 'registrations' }
resources :users, shallow: true do
resources :bookmarks, except: :new
resources :friendships, only: [:index, :show, :destroy]
resources :reminders
end
resources :bookmarks, shallow: true do
resources :comments
end
end
Am I writing these routes out correctly?
When I rake routes, I'm getting bookmarks#index twice so I am confused. The prefix for one of them is bookmark, and the other is bookmarks. Why is this happening?
From my understanding, the application does not need to see all of the bookmarks in an index because they are only visible to the user who made them. However, I want reminders to be visible to the user's friends.
I'm hoping to get suggestions to clean up my routes, if possible. I really doubt I am doing this correctly.
My interpretation of your spec:
#config/routes.rb
resources :users, only: [] do #-> show a user's collections (no edit)
resources :bookmarks, shallow: true, except: [:new, :edit, :update] #-> url.com/bookmarks/:id
resources :comments, :friendships, :reminders, shallow: true, only: [:index, :show] #-> url.com/comments/:id
end
resource :bookmarks, except: :index do #-> url.com/bookmarks/:id
resources :comments #-> url.com/bookmarks/:bookmark_id/comments/:id -- should be scoped around current_user
end
For the comments controller, do this:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def new
#bookmark = Bookmark.find params[:bookmark_id]
#comment = #bookmark.comments.new
end
def create
#bookmark = Bookmark.find params[:bookmark_id]
#comment = #bookmark.comments.new bookmark_params
#comment.user = current_user
#comment.save
end
end
Don't make a welcome or home controller, you don't need them.
You can put off-hand actions in your application controller:
#config/routes.rb
root 'application#index'
get 'home', to: 'application#home'
get 'search_apis', to: 'application#search_apis'
Of course this is somewhat of an antipattern (you'll end up bloating your ApplicationController), but if you only have obscure "one-off" actions in other controllers, you'll be best suited using the above.
Also, only use snake_case with lowercase for URL's - HTTP's spec determines all URLs should be handled as lowercase:
Converting the scheme and host to lower case. The scheme and host
components of the URL are case-insensitive. Most normalizers will
convert them to lowercase. Example: →
http://www.example.com/
Although that's only for the domain/host, it's applicable in the URL itself, too.
Shallow provides the :index, :new and :create only. So you're getting index twice. Once from within users and the other bookmarks - comments.
On re-reading your associations at the start of your post, and that comments belongs_to both users AND bookmarks, it's probably a good idea to create a Polymorphic relationship.
A rough guide would be for your models,
class Comment < ActiveRecord::Base
belongs_to :messages, polymorphic: true
end
class User < ActiveRecord::Base
has_many :comments, as: :messages
end
class Bookmark < ActiveRecord::Base
has_many :comments, as: :messages
Then either rails generate migration Comments if you haven't already, and have it look like the following:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :name
t.references :messages, polymorphic: true, index: true
t.timestamps null: false
end
end
end
otherwise run a migration to add columns to your Comment model. I.e rails g migration AddMessagesToComments messages:references
But be sure to open your new migration file named as above and add polymorphic: true before you rake db:migrate