How to make the normal way of Routes with hash keys - ruby-on-rails

Now I can see my received message by accessing to example.com/messages/46747
and My routes are set set like this
'messages/:id' => 'messages#show', :as => 'show_messages'
The link tag to access that page is set like
<%= link_to 'show message', show_messages_path %>
In this case, the IDs of the messages are shown in the URL.
I guess that IDs should be taken place by hash keys in most cases.
How can I achieve that?

Use ActiveRecord::Base to_param in order to change the url generated by the path:
class User < ActiveRecord::Base
def to_param # overridden
name
end
end
user = User.find_by_name('Phusion')
user_path(user) # => "/users/Phusion"
Just override the to_param method in Message class by the text you'd like to see in the url.

Related

How do I make my redirect include the name instead of an ID?

I'm using Rails 5.1. In my controller, I would like to redirect to my "show" method like so
redirect_to(#organization)
but I would like the URL to appear as
/organization/organization_name
instead of
/organization/primary_key_id
How do I set this up? I already have a field "name" in my Organization model.
Edit: As requested, this is the index method of my PagesController ...
class PagesController < ApplicationController
# Should be the home page
def index
worker_id = params[:worker_id]
worker = Worker.find_by_id(worker_id)
if worker && worker.organization
redirect_to(worker.organization)
else
render :file => "#{Rails.root}/public/404", layout: false, status: 404
end
end
end
Edit: My config/routes.rb file
resources :organizations, :only => [:show] do
post :update_work
get :get_work
get :mine
get :poll
post :submit
get :home
get :terms_of_use
end
Here's the app/model/stratum_worker.rb file
class StratumWorker < ApplicationRecord
has_one :organization_worker
has_one :organization, :through => :organization_worker
OK, if you are not interested to use any gem then you can without gem like
class Model < ApplicationRecord
def to_param # overridden
organization_name
end
end
in this case, you need to make sure the organization_name name is unique, for uniqueness the organization_name you can use validation like this
validates_uniqueness_of :organization_name
then the model will look like this
class Model < ApplicationRecord
validates_uniqueness_of :organization_name
def to_param # overridden
organization_name
end
end
and now to the controller using find_by_organization_name(params[:organization_name]) instead of find(params[:id]).
Second Option
You can not change anything to your controller if used like this in just model
class Model < ApplicationRecord
def to_param # overridden
organization_name
"#{id} #{organization_name}".parameterize
end
end
then the URL looks like this /10-microsoft.
See this to_param method. The complete reference of with gem or without gem Rails Friendly URLs
RailsCasts.com created an episode for Pretty URLs with FriendlyId, can you check it out for getting the idea.
From Comment
I don't think what's going on but sure something wrong with the relationship, can you check like this
redirect_to(worker.organizations.first)
#=> OR
redirect_to organization_path(worker.organizations.first.id)
Update
I think worker.organization are missing somehow, would you try like this?
if worker && worker.organizations.present?
redirect_to(worker.organizations.first)
....
the present method making sure worker.organizations not blank.
I don't know about the relationship, you can try like this and let me know what's happening if it's not working then I strongly recommend to post the models with relationship concept.
Update 2 after question update
At first, you don't need the through relationship because it uses too Many To Many relationships. Your relationship is One To One then your model will look like this
class StratumWorker < ApplicationRecord
has_one :organization_worker
....
has_one :organization, :through => :organization_worker
organization_worker.rb file like this
class OrganizationWorker < ApplicationRecord
belongs_to :stratum_worker
#=> Add code what you need like for URL which was the actual motive in this post
....
Then the action looks like this
def index
worker_id = params[:worker_id]
worker = StratumWorker.find_by_id(worker_id)
if worker && worker.organization_worker.present?
#redirect_to(worker.organization_worker)
redirect_to organization_path(worker.organization_worker)
else
render :file => "#{Rails.root}/public/404", layout: false, status: 404
end
end
and the show action
OrganizationWorker.find(params:id)
I think the problem will solve now. If still, you getting errors then please read the One To One relationship again & again until clearing the relationship concept.
Hope it will help.
I wrote a post here detailing exactly this a while ago. Most of my answer will be from there. The relevant Rails documentation for this is here.
Quick definitions:
Slug: part of the URL to identify the record, in your case organization_name
Primary key: a unique identifier for database records. This usually is and should be id.
Summary
If you type organization_path(#organization), it'll automatically use the id attribute in the URL. To adjust to using organization_name, you'll need to make 2 changes:
Override the route params in your routes.rb file.
Override the to_param method in the model
1. Override The Route Params
At the moment, if you run rails routes your routes look like so:
organizations GET /organizations(.:format) organizations#index
POST /organizations(.:format) organizations#create
new_organization GET /organizations/new(.:format) organizations#new
edit_organization GET /organizations/:id/edit(.:format) organizations#edit
organization GET /organizations/:id(.:format) organizations#show
PATCH /organizations/:id(.:format) organizations#update
PUT /organizations/:id(.:format) organizations#update
DELETE /organizations/:id(.:format) organizations#destroy
The edit_organization and organization paths use id as a parameter to lookup your organization.
Use this to override the route params
Rails.application.routes.draw do
resources :organizations, param: :organization_name
end
Now rails routes will show that your routes look like so:
organizations GET /organizations(.:format) organizations#index
POST /organizations(.:format) organizations#create
new_organization GET /organizations/new(.:format) organizations#new
edit_organization GET /organizations/:organization_name/edit(.:format) organizations#edit
organization GET /organizations/:organization_name(.:format) organizations#show
PATCH /organizations/:organization_name(.:format) organizations#update
PUT /organizations/:organization_name(.:format) organizations#update
DELETE /organizations/:organization_name(.:format) organizations#destroy
2. Override The Model Params
By default organization.to_param will return the id of the organization. This needs to be overridden, do this by modifying your Model:
class Organization < ApplicationRecord
def to_param
organization_name
end
end
Conclusion & Warning
You can now continue using your redirects and forms as usual, but instead of the route using the id, it'll now use the organization name.
Also, good luck with your mining pool! Lemme know which coin you're mining and I might join!
Also, I didn't cover this because it isn't a part of your original question, but, you should ensure that the organization_name is unique! Not only should you add a uniqueness constraint validates :organization_name, uniqueness: true in the mode, you should also enforce it at the database level in your migration.
Addendum 1: Customizing for routs
When your routes are defined as so:
resources :organizations, :only => [:show] do
post 'update_work'
get 'get_work'
get 'mine'
get 'poll'
post 'submit'
get 'home'
get 'terms_of_use'
end
Your routes will be as so:
organization_update_work POST /organizations/:organization_id/update_work(.:format) organizations#update_work
organization_get_work GET /organizations/:organization_id/get_work(.:format) organizations#get_work
organization_mine GET /organizations/:organization_id/mine(.:format) organizations#mine
organization_poll GET /organizations/:organization_id/poll(.:format) organizations#poll
organization_submit POST /organizations/:organization_id/submit(.:format) organizations#submit
organization_home GET /organizations/:organization_id/home(.:format) organizations#home
organization_terms_of_use GET /organizations/:organization_id/terms_of_use(.:format) organizations#terms_of_use
organization GET /organizations/:id(.:format) organizations#show
Changing the param like so:
resources :organizations, :only => [:show], param: :organization_name do
post 'update_work'
get 'get_work'
get 'mine'
get 'poll'
post 'submit'
get 'home'
get 'terms_of_use'
end
Will change your routes to
organization_update_work POST /organizations/:organization_organization_name/update_work(.:format) organizations#update_work
organization_get_work GET /organizations/:organization_organization_name/get_work(.:format) organizations#get_work
organization_mine GET /organizations/:organization_organization_name/mine(.:format) organizations#mine
organization_poll GET /organizations/:organization_organization_name/poll(.:format) organizations#poll
organization_submit POST /organizations/:organization_organization_name/submit(.:format) organizations#submit
organization_home GET /organizations/:organization_organization_name/home(.:format) organizations#home
organization_terms_of_use GET /organizations/:organization_organization_name/terms_of_use(.:format) organizations#terms_of_use
organization GET /organizations/:organization_name(.:format) organizations#show
Which should work totally fine with your redirect.
Method that is called under the hood for id generation is to_param
so in your case to get your desired result you should add this to your Organization class:
class Organization < ApplicationRecord
...
def to_param
name
end
...
end
!!!WARNING!!! - since Rails is also using the parameter on the other side (e.g. in show method Organization.find(params[:id]) uses the URL id), now it will be params[:id] == "some_organization_name" so change your instance lookups accordingly - in show action for example use Organization.find_by!(name: params[:id]) and so on
As for your routing error - make sure that worker.organization is not nil.
There is a gem friendly_id that does exactly what you are asking for: https://github.com/norman/friendly_id
You add,
gem 'friendly_id'
Then bundle install and run rails generate friendly_id and rails db:migrate
to your Gemfile and,
class Organization < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
end
to your model then,
class OrganizationController < ApplicationController
def show
#user = Organization.friendly.find(params[:id])
end
end
to your controller.
This prevents the issues you can run into in Kkulikovskis answer where you have to make sure that you are looking things up correctly.

Rails use object property in path

I'm trying to get a path helper to work with a object's name property
so in routes
get 'reset/:name', to: 'users#reset_name', as: 'reset_name'
in controller reset_name def
... #user
in view (html.erb)
... <%= link_to reset_name_path(#user) %>
for some reason the link is still coming out like /reset/name/1, which uses the object's id and not name. What am I doing wrong?
You can override the to_param method to make Rails use the name instead of the id when generating routes. This will apply to all routes that operate on the User model.
class User < ActiveRecord::Base
def to_param
name
end
end
Alternatively, if you only want to do this for some routes, you can just change your link_to:
<%= link_to reset_name_path(:name => #user.name) %>

How to call a non action method by clicking a button in Rails

Forgive me if this is a very newby question.
how can i call a method (sharing an object on facebook) when user clicked on the share button in the view.
I can do the share/facebook parts, i just don't know how to call a method from the model when the user clicks on a button
my_controller.rb
def do_something
...
end
routes.rb
get "/something" => "my_controller#do_something", :as => :do_something
#you can also use post, put, delete or match instead of get
view
<%= link_to "call do something", do_something_path %>
for post etc...
<%= link_to "call do something", do_something_path, method: :post %>
I see two potential answers depending on your specific needs.
If you want to add a method to your model outside of the columns you've created for your object you can do so in the model.rb file:
model.rb
def name_twice
"#{self.name}#{self.name}"
end
You can then take an instance of a model to call this: "#model.name_twice".
If you want to add another routed method in the controller, you can define it in your models_controller file:
models_controller.rb
def approve
#model = Model.find_by_id(params[:model_id])
model.toggle!(:approved)
redirect_to #model
end
In order for the new controller function to work, you must add it in the routes file:
routes.rb
resources :models do
get 'approve', :on => :member
end
Hope this might be a little helpful. These examples should give you an idea of how to add other methods/actions to a model/controller.

How to set up routes to show additional information in the URL using namespaces?

I am running Ruby on Rails 3 and I would like to set up my routes to show additional information in the URL using namespaces.
In the routes.rb file I have:
namespace "users" do
resources :account
end
So, the URL to show an account page is:
http://<site_name>/users/accounts/1
I would like to rewrite/redirect that URL as/to
http://<site_name>/user/1/Test_Username
where "Test_username" is the username of the user. Also, I would like to redirect all URLs like
# "Not_real_Test_username" is a bad entered username of the user.
http://<site_name>/users/accounts/1/Not_real_Test_username
to the above.
At this time I solved part of my issuelike this:
scope :module => "users" do
match 'user/:id' => "accounts#show"
end
My apologies for not answering your question (#zetetic has done that well enough), but the best practice here is to stay within the RESTful-style Rails URL scheme except for rare exceptions. The way most people make pretty URLs in this way is to use a hyphen, e.g.:
/accounts/1-username
This does not require any routing changes. Simply implement:
class Account < ActiveRecord::Base
def to_param
"#{self.id}-#{self.username}"
end
end
And handle the extra string data in your finds by calling to_i.
class AccountController < ApplicationController
def show
#account = Account.find(params[:id].to_i)
end
end
When you do link_to 'Your Account', account_path(#account), Rails will automatically produce the pretty URL.
It's probably best to do this in the controller, since you need to retrieve the account to get the username:
#account = Account.find(params[:id])
if #account && #account.username
redirect_to("/user/#{#account.id}/#{#account.username}")
return
end
As to the second issue, you can capture the remaining parameter by defining it in the route:
get "/users/accounts/:id(/:other)" => "users/accounts#show"
This maps like so:
/users/accounts/1/something # => {:id => "1", :other => "something"}
/users/accounts/1 # => {:id => "1"}
And you can simply ignore the :other key in the controller.

How to add action 'current_user' to a restful 'user'?

I have a model 'User', it's a restful resource, and has the default methods like 'index, show, new, create' and others.
Now, I want to define a new action 'current_user', to show the information of current logged-in user, which is different from 'show'.
When I use:
link_to current_user.name, :controller=>'users', :action=>'current_user'
The generated url is http://localhost:3000/users/current_user, and error message is:
Couldn't find User with ID=current_user
Shall I have to modify the routes.rb? What should I do?
I have searched for some articles, and still have no idea.
Add
map.resources :users, :collection => {:current => :get}
Then, I use:
link_to 'current', current_users_path()
The generated url is:
http://localhost:3000/users/current
Now, everything is OK. Is this the best solution?
See my comment on the other answer for an explanation
map.current_user "users/current", :controller => :users, :action => :current
View:
link_to 'current', current_user_path
I would not add a new action for this. I would check the id passed to the show method.
class UsersController
def show
return show_current_user if params[:id] == "current"
# regular show code
end
private
def show_current_user
end
end
In the view use :current as the user id while generating path.
user_path(:current)

Resources