I have resources :users and custom_id inside users table.
I want to use link_to "user", user method choosing custom_id to provide link with this field as param.
My show action inside users_controller.rb:
...
#user = User.find(custom_id: params[:id])
...
In Your form You could use:
<%= link_to #user.name, #user %>
because You already wrote in UsersController:
...
#user = User.find(custom_id: params[:id])
...
A better way to manage this is via the to_param method which you can define inside a model. When you pass a model instance into a path helper, it calls this method on it, which by default returns the id. You could override it to use custom_id instead.
#in User class
def to_param
self.custom_id
end
Now you can say
link_to "user", user_path(#user)
and it will generate html like
user
and so #user.custom_id will come through in params[:id].
You will still need to make sure that you load the user via the custom id field, rather than the id, in the controller. A common pattern is to put it into a protected method in the controller:
#in UsersController
protected
def load_user
#user = User.find(custom_id: params[:id])
end
now you can say load_user in all the actions which load #user, or put it into a before filter for the relevant actions, which is even DRYer.
Related
I have never found a good solution for this problem. I have the following routes structure:
resources :contents
namespace :admin do
resources :contents
end
When I call content_path(content) I want the id to be the slug of the content, while when I call admin_content_path(content) I want the id to be the id of the content. I just want the id not to be related to the model (actually the id is the returning value of the to_param method of the model), but to the route.
I would like to avoid defining helper methods for every route, it's a weak solution in my opinion.
I know I can write admin_content_path(id: content.id) or content_path(id: content.slug), but this is just an hack actually. Also, this is especially annoying in form_for, since I can't write
form_for #content
but I'm forced to use
form_for #content, url: #content.new_record? ? admin_contents_path : admin_contents_path(id: #content.id)
Usually, you would change the route to:
resources :contents, param: :slug
and then you override to_param method to become:
class Content < ApplicationRecord
def to_param
slug
end
end
And finally in your controller, you replace Content.find(params[:id] with Content.find_by(slug: params[:slug]).
That will give you URLs like /contents/foo-bar when you call content_path(content).
In your case, you can additionally create a subclass that overrides the to_param method:
module Admin
class Content < ::Content
def to_param
id && id.to_s # This is the default for ActiveRecord
end
end
end
Since your admin/contents_controller.rb is namespaced under Admin (e.g Admin::ContentsController), it will by default use the Admin::Content class instead of the normal Content class, and thus the object itself and all routes should be as you like them to be, including forms.
I would say that's two different problems : URL generation for your resources on the user front-end side (using slugs) and URL generation for your admin forms.
Obviously in your admin, you will never be able to just write form_for #resource because your admin is namespaced, so the minimum would at least be form_for [:admin, #resource].
Let's say you have to_param on some of your models to return a slug, you may create your own customised helpers on your admin back-office to always return a path namespaced to /admin/ and using the id of the record.
One generic way to do that is adding this kind of code in your Admin root controller.
class Admin::AdminController < ApplicationController
helper_method :admin_resource_path, :edit_admin_resource_path
def admin_resource_path(resource)
if resource.new_record?
polymorphic_path([:admin, ActiveModel::Naming.route_key(resource)])
else
polymorphic_path([:admin, ActiveModel::Naming.singular_route_key(resource)], id: resource.id)
end
end
def edit_admin_resource_path(resource)
polymorphic_path([:edit, :admin, ActiveModel::Naming.singular_route_key(resource)], id: resource.id)
end
end
Then in your form you can use form_for(#user, url: admin_resource_path(#user). It will work on both user creation and user edition.
You will be able to use those helpers also in your controllers to redirect...
Well, I found a nice solution, but only on Rails >= 5.1 (which is in rc1 at the moment), using the brand new direct method:
namespace :admin do
resources :contents
end
# Maps admin content paths in order to use model.id instead of model.to_param
{ admin_content: :show, edit_admin_content: :edit }.each do |direct_name, action|
direct direct_name do |model, options|
options.merge(controller: 'admin/contents', action: action, id: model.id)
end
end
Trying to figure our how to set up associations in form.
I have 3 models:
class Request < ActiveRecord::Base
has many :answers
has many :users, through: :answers
end
class Answer < ActiveRecord::Base
belongs to :user
belongs to :request
end
class User < ActiveRecord::Base
has many :answers
has many :requests, through: :answers
end
I am trying to figure out: how to have a User link to Answer#new from Request#Show, and then create an Answer record passing in the Request#Show request_id from the previous page - creating an association between the User's Answer and the Request he was viewing.
My method of doing this now is: I flash the request_id value on Request#Show, and then when a User links to Answer#new, it passes the flashed value into a hidden form tag on Answer#new. This does not seem like the best way to do this.
Any thoughts?
Kudos for the creative approach using flash, however your right there is an easy way. You can pass parameters much between controllers just like passing parameters between methods using the route names.
I didn't quite follow what it was you were trying to achieve in this case but it looks like this blog entry here should get you started..
https://agilewarrior.wordpress.com/2013/08/31/how-to-pass-parameters-as-part-of-the-url-in-rails/
Good luck!
User link to Answer#new from Request#Show
This can be achieved with either sessions or nested resources (or both!). Let me explain:
I would definitely add a nested resource to your requests routes:
#config/routes.rb
resources :requests do
resources :answers, only: [:new, :create] #-> url.com/requests/:request_id/answers [POST]
end
This gives you the ability to call a "nested" route (IE one which sends data to a child controller, and requires "parent" data to be appended to the request).
In your case, you want to create an answer for a request. The most efficient way is to use a routing structure as above; this will allow you to use the following controller method:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
#request = Request.find params[:request_id]
#answer = #request.answers.new
end
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.save
end
private
def answer_params
params.require(:answer).permit(:title, :body)
end
end
The above gives you the ability to create an answer by passing the request_id to the nested route. You must note the corresponding route will require a POST method in order to work.
You don't need the new method. If you wanted it, it can easily be handled with the above structure.
Passing the user is a little more tricky.
You can either use the routes, or set a session.
I would personally set a session (it's cleaner):
#app/controllers/requests_controller.rb
class RequestsController < ApplicationController
def show
session[:user_id] = #user.id #-> I don't know how you populate #user
end
end
This will give you the ability to access this session here:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
user = User.find session[:user_id]
end
end
#app/views/requests/show.html.erb
<%= link_to "New Answer", request_new_answer_path(request) %>
--
If you're using Devise, the user object should be available in the current_user object (which means you don't have to set session[:user_id]):
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
## current_user available here if using devise
end
end
To assign a #user to the new answer record, just do this in answers#create:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
...
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.user = current_user
#answer.save
end
end
Something like this worked for me:
I have two models (Formula and FormulaMaterial)
Formula has_many FormulaMaterials, which belongs to Formula
My Formula controller sets #formula like so:
#formula = Formula.find(params[:id])
I list my Formula Materials in my Formula show.html.erb by declaring it in my Formula controller like so:
#formula_materials = FormulaMaterial.where(:formula_id => #formula)
When I want to add a new FormulaMaterial to my Formula, the "New Formula Material" button in my show.html.erb file looks like this:
<%= link_to 'Add Material To Formula', new_formula_material_path(:formula_id => #formula), class: "btn btn-success" %>
In the "new_..._path" I set the associated id to the #formula variable. When it passes through to the new.html.erb for my FormulaMaterial, my URL looks like so:
http://localhost:3000/formula_materials/new?formula_id=2
In my FormulaMaterial new.html.erb file, I created a hidden_field that sets the value of the association by using "params" to access the formula_id in the URL like so:
params[:formula_id] %>
I am not sure if this is the best way to do this, but this way has allowed me to pass through the view id from the previous page as a hidden, associated and set field in the form every time.
Hope this helps!
I would like to get user profiles accessible from the URL: root/user/(username)
As of now I have it working with root/user/(id) but i want it to be more user friendly and shareable with the username in the URL.
This is how I currently have it set up
#user_controller.rb
class UserController < ApplicationController
def profile
#user = User.find(params[:id])
end
end
#routes.rb
match 'user/:id' => 'user#profile'
#profile.html.erb
<h1><%= #user.firstname %>'s Profile</h1>
Basically what I'm trying to do is to change out :id for :username. I've created the usernames in the user models from devise so I know that is working. But right now when I try to get usernames in the URL I get Couldn't find User with id=username.
Change your controller
class UserController < ApplicationController
def profile
#user = User.find_by_username(params[:username])
end
end
Then the route
match 'user/:username' => 'user#profile'
Try friendly_id. No need for any hacks in controller or model level.
I have a like model, recording which user liked which record. I used polymorphic association so a user can like many models.
Currently I use nested-resources to handle likes.
POST /items/:item_id/likes
DELETE /items/:item_id/likes/:id
Now for some reasons I want to get rid of the use of like_id by designing a better route. This is because it will be easier to cache a fragment view.
Note that item model is only one of a few models which are likable, and I want to avoid code duplication if possible.
What's a good way to design routes and controllers that will not use like_id but also allows better code reuse in controller?
Possible implementation
I was thinking of routes like this:
POST /items/:item_id/like
DELETE /items/:item_id/like
I won't use nested like resource. Instead I place a like action in items controller. It will determine if the request is a POST or a DELETE and act accordingly. This however doesn't feel DRY.
I don't know about Rails necessarily, but in Zend Framework I would create a front controller plugin to route all requests with methods 'LIKE' and 'UNLIKE' to a particular controller which then deduces which route was requested, and subsequently which resource was requested, and then performs the necessary actions to 'like' or 'unlike' that resource in the name of the requesting user.
Why? Because the user is 'like'-ing or 'unlike'-ing the resource in question, not 'creating a like' or 'deleting a like'. Sure, in the backend, the 'like' is a record in a cache or database that gets created or deleted -- but the semantics of a resource are not necessarily equivalent that of whichever method is used to persist that resource.
What you need is Singular Resources.
routes.rb
resources :items do
resource :like, only: [:create, :destroy]
end
likes_controller.rb
class LikesController < ApplicationController
before_action :load_likeable
def create
#like = Like.where(likeable: #likeable, user: current_user).first_or_create
redirect_back(fallback_location: #likeable)
end
def destroy
#like = Like.find_by(likeable: #likeable, user: current_user).destroy
redirect_back(fallback_location: #likeable)
end
private
def load_likeable
klass = [Recording].detect { |c| params["#{c.name.underscore}_id"] }
#likeable = klass.find(params["#{klass.name.underscore}_id"])
end
end
likes_helper.rb
module LikesHelper
def like_button_for(item)
if item.liked
form_tag recording_like_path(item), method: :delete do
button_tag "UnLike"
end
else
form_tag recording_like_path(item), method: :post do
button_tag "Like"
end
end
end
end
item.liked is method from Item model
In my Ruby on Rails app, I've got:
class AdminController < ApplicationController
def create
if request.post? and params[:role_data]
parse_role_data(params[:role_data])
end
end
end
and also
module AdminHelper
def parse_role_data(roledata)
...
end
end
Yet I get an error saying parse_role_data is not defined. What am I doing wrong?
Helpers are mostly used for complex output-related tasks, like making a HTML table for calendar out of a list of dates. Anything related to the business rules like parsing a file should go in the associated model, a possible example below:
class Admin < ActiveRecord::Base
def self.parse_role_data(roledata)
...
end
end
#Call in your controller like this
Admin.parse_role_data(roledata)
Also look into using (RESTful routes or the :conditions option)[http://api.rubyonrails.org/classes/ActionController/Routing.html] when making routes, instead of checking for request.post? in your controller.
Shouldn't you be accessing the parse_role_data through the AdminHelper?
Update 1: check this
http://www.johnyerhot.com/2008/01/10/rails-using-helpers-in-you-controller/
From the looks of if you're trying to create a UI for adding roles to users. I'm going to assume you have a UsersController already, so I would suggest adding a Role model and a RolesController. In your routes.rb you'd do something like:
map.resources :users do |u|
u.resources :roles
end
This will allow you to have a route like:
/users/3/roles
In your RolesController you'd do something like:
def create
#user = User.find_by_username(params[:user_id])
#role = #user.roles.build(params[:role])
if #role.valid?
#role.save!
redirect_to #user
else
render :action => 'new'
end
end
This will take the role params data from the form displayed in the new action and create a new role model for this user. Hopefully this is a good starting point for you.