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
Related
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
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 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)
I want to support:
POST images/1/comments/2/like
and
POST comments/2/like
They both point to same resources with same action. How can I do that in rails route file?
resources :images do
resources :comments do
member do
post 'like'
end
end
end
This will work for
POST images/1/comments/2/like
but how can I also for when I don't specify the images/1 part?
You can make it more beautiful actually. According to http://ruby-journal.com/how-to-dry-your-rails-routes/, this also works:
comments = Proc.new do
member do
post 'like'
end
end
resources :comments, &comments
resources :images do
comments.call
end
and in Rails 4 you could use concerns
concern :comments_concern do
member do
post 'like'
end
end
resources :comments, concerns: :comments_concern
resources :images, concerns: :comments_concern do
#do more stuff here
end
I didn't test this, but it might help. Have a look at the website mentioned. Good luck
-frbl
I would consider reworking which RESTful routes for Comment you're nesting, and which you're not. I'm assuming your models look something like this:
# app/models/image.rb
class Image < ActiveRecord::Base
has_many :comments
end
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :image
end
Because your Image and Comment models possess a one-to-many relationship, I can see why you'd think to nest the comments resource route within the images one. However, of all the CRUD actions in comments_controller.rb, only create actually requires that a parent image ID be explicitly passed in. From a RESTful perspective, only the new and create actions require that a image_id is passed to the action. The edit, update, delete, and like actions can all take place independently of the parent image.
Consider the alternative routing schematic instead:
# config/routes.rb
resources :images do
resources :comments, :only => [:index, :new, :create]
end
resources :comments, :only => [:show, :edit, :update, :destroy] do
member do
post 'like'
end
end
Now, only the comment actions that are explicitly depended on a parent ID are actually nested within the images routes. The remainder of the comment actions are directly routed to the comments controller without passing in the parent ID. Your routes are no longer duplicated, and each action will have exactly one route declared for it.
simply add resources :images it may work
Not sure there is a much more beautiful way than repeating this below:
resources :comments do
member do
post 'like'
end
end
Like so:
resources :images do
resources :comments do
member do
post 'like'
end
end
end
resources :comments do
member do
post 'like'
end
end
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)