Suppose I have a Book model in Rails, and this model as a resources has predefined routes e.g. /books/:id/edit.
Now, in my app I wish to show a list of books and add an edit button where everyone can edit the book but only one specific field. I wish to achieve this by rendering a different view, using a different route and a different controller.
How should I define the route so that this works, i.e. the id of the Book instance is passed as needed?
Sounds like you can keep
rails g model book field_one field_two
rails g controller books
# routes.rb
resources :books
patch 'common_update/:id', to: 'books#common_update'
if you specifically want to hit another controller then you can just create a different one (rails g contoller_name) and replace books in the routes with contoller_name (note that the generator takes a plural!).
# books_controller.rb
def common_update
#book = Book.find(params[:id])
if #book.update(common_params)
# do something
else
# do something else
end
end
def common_params
params.require(:book).permit(:field_one) # but not field_two !
end
As for views, you can perhaps conditionally render the form for the owner (the user who created the book) and the form for everyone else. If you want a separate view/show you can also just add the route (get 'common_show', to: 'controller_name#common_show') plus a view file in the correct folder :+1:
Related
I am building a small app, that allows users to create lists and within these lists they can add gifts that they want. So far its very similar to a ToDo list app.
I have three models:
User - Can have many Lists
List - Can have many gifts and belongs to User
Gift - Belongs to List
In my List model as well as storing the name of the list, Im also creating a unique string of letters and numbers and storing it as shared_key in the record. The code looks like this:
def create_unique_url
begin
self.shared_key = SecureRandom.urlsafe_base64(10)
end while self.class.exists?(shared_key: shared_key)
end
and ideally I want the url to look something like this app.com/public/long_string_shared_key_goes here
My main Question is, how should do I go about setting up a route to access the record at this public address.
Should I create another controller called public and have a show method there? Or should I create a public action in my llist controller and somehow manually create a route to it?
Since it's just a matter of single action I'd not suggest to redefine the #to_param, since it might affect all of your existing functionality. Still a matter of taste, mostly
routes:
resources :lists, except: [ :show ]
get '/public/:shared_key' => 'lists#show'
controller:
def show
#list = List.find_by(shared_key: params[:shared_key])
end
view:
link_to list.name, list_path(shared_key: list.shared_key)
In you case i would just alter a little the showing of the list. First in the list model redefine the to_param method
def to_param
long_string_shared_key_goes #by default this was returning the id - example...to access show a list you had to navigate to /lists/:id
end
Now you need to change the routes.rb
match 'public/:long_string_shared_key_goes', to :'lists#show', via: [:get]
Now you just have to change some find methods (if you are accessing the lists with example list.find(params[:id] you now have to take in consideration that you dont have the id in the params, but you long_string_shared_key).
Hope this answered your question.
I have a controller, clients_controller, with corresponding index, show, edit, delete, new & form views. Is there a way to create a new view like clients/prospects.html.erb that acts the same way as clients/index.html.erb, except is routed at clients/prospects/?
I've tried this:
match '/clients/prospects' => 'clients#prospects'
And some other things in routes.rb, but of course get the error "Couldn't find Client with id=prospects".
The goal here is basically to have a prospects view and a clients view, and by simply switching the hidden field to a 1, it (in the user's mind) turns a prospect into a client (it's a CRM-like app).
There's a couple of things you need to do. First you need to put the your custom route before any generic route. Otherwise Rails assumes the word "prospects" is an id for the show action. Example:
get '/clients/prospects' => 'clients#prospects' # or match for older Rails versions
resources :clients
Also you need to copy / paste the index method in your ClientsController and name it prospects. Example:
class ClientsController < ApplicationController
def index
#clients = Client.where(prospect: false)
end
def prospects
#prospects = Client.where(prospect: true)
end
end
Lastly, you need to copy the index.html.erb view and name the copy prospects.html.erb. In the example above you would have to work with the #prospects instance variable.
Create a new action in clients controller named prospects. And then define a collection route in routes.rb for it as either resource full way. Or u directly use match as you were doing.
What you're doing is not wrong (although I'd change match to get, otherwise POST and DELETE requests to that url will also render your prospects view). Presumably you have
resources :clients
in your routes file? If so, what you have will probably work if you just move the line you quoted above the resources declaration -- the problem is that /clients/prospects matches the show route for the clients resource, so if it's defined first then that's the route that gets matched.
However, there's a more idiomatic way to define this route
resources :clients do
collection do
get :prospects
end
end
See Rails Routing documentation for more
Also see migu's answer for what else needs to be done once the url is being routed correctly (though there are other things you can do -- if you the two views are similar enough, you can reuse the view template, for example).
In my Rails site, I have a model named Person, which has an ActionController and migration (database). I've inserted few rows to this table using the console (and saved them there!).
In the PersonController I have a method "list" which I want to list all the people which are in the database :
def list
#persons = Person.all
end
However, in the list.html.erb file in the person's view, I can't access this arrey. Trying to write something like :
<% #persons.each do |r| %>
raises an error claims that #person is nil.
I think I'm doing something wrong here. In conclusion, how can I pass a database from the controller to the view, and how can I disply it?
Thanks
The method you are using to transfer data from the controller to the view is appropriate. My guess is that Person.all is not returning any results, meaning your database is not being populated via the console. Try adding your data in via seeds.rb in the db directory, and then run rake db:seed. That should do it for you. Also, generally lists of models are run through the 'index' route. I would recommend using that instead of a route 'list'.
You can also check this post to make sure you are using the console correctly:
how to add data to database from rails console
*****************EDIT*******************
To use index to list display a list of a model, open your routes.rb file and add the people resource to generate all routes for the person model
resources :people
That will give you index, show, edit, create, update, and destroy routes (run rake routes to see a list of all the routes). If you do not want all of those, you can use only: and except: with the resource command to limit the routes created. Example
resources :people, only: index
You then need to update your controller to match each route that you want to use. So instead of 'list' you can use index, show, edit, update, create, and destroy.
def index
#people = Person.all
end
is an example of the index action definition. Then you just need a view to match the action, like index.html.erb. If you have any people in the database, they should now be in the #people variable during the index action and you can use that to list out each person. If you want to create some people to try it out, you can use the console, the show action you created using the resources command in the routes.rb, or use the seed.rb file and run rake db:seed.
Your variable in controller is #persons and not #person. The error you are getting is #person is nil. If that is not the problem, then please remember that rails uses singular for the model name, which is Person and plural in other cases, people in naming the tables and the controllers.
If this doesn't help, please post your real filenames, and your first line of the controller along with other relevant code.
Good luck!
Check the database and make sure the data is there. In addition, make sure you use Person.create and not Person.new when you are creating a record via the console.
Person.new must be assigned to a variable and save must be called on that variable. Create doesn't require that extra step
u = User.new(:first => "Chris", :last => "Tilley")
u.save! //required with new
u = Person.create(:first => "Chris", :last => "Tilley") //doesn't require save
I am using ruby on rails to make a simple social networking site that includes different message boards for each committee of a student group. I want the url structure for each board to look like https://<base_url>/boards/<committee_name> and this will bring the user to the message board for that committee.
My routes.rb file looks like:
resources :committees, only: [:index]
match '/boards/:name', to: 'committees#index(name)'
My index function of committees_controller.rb file looks like:
def index(name)
#posts = Committee.where(name: name)
end
And then I'll use the #posts variable on the page to display all of the posts, but right now when I navigate to https://<base_url>/boards/<committee_name> I get an Unknown Action error, and it says The action 'index(name)' could not be found for CommitteesController.
Could someone guide me through what I have done wrong?
Once I get this working, how would I make a view that reflects this url structure?
Set up your routes like this:
resources :committees, only: [:index]
match '/boards/:name', to: 'committees#show'
and the controller like this:
def index
#committees = Committee.all
end
def show
#committee = Committee.find_by_name!(params[:name])
end
You can't really pass arguments to controller actions the way you were trying to with index(name). Instead, you use the params hash that Rails provides you. The :name part of the route declaration tells Rails to put whatever matches there into params[:name].
You also should be using separate actions for the listing of committees and displaying single committees. Going by Rails conventions, these should be the index and show actions, respectively.
When routing, you only specify the method name, not the arguments:
match '/boards/:name', to: 'committees#show'
Generally you will declare something with resources or match but not both. To stay REST-ful, this should be the show method. Index is a collection method, usually not taking any sort of record identifier.
Arguments always come in via the params structure:
def show
#posts = Committee.where(name: params[:name])
end
Controller methods that are exposed via routes do not take arguments. You may construct private methods that do take arguments for other purposes.
How do I Change the URL for the view in the controller.
In my controller I generate an ID which I want to display
in the URL of the browser when the view is rendered.
For example, when I enter / in my browser, it should redirect me
to /test/1. The ID is generated randomly by the controller.
So when I access / the 2nd time it could redirect me to /test/3.
I tried to do a match route in my routes.rb file.
But I couldn't find a solution.
routes.rb
get 'test/run'
root to: 'test#run'
match '/test/:id', to: 'test#run'
How about that:
class TestController
def show
...
end
def run
redirect_to Test.random
end
end
Of course, you have to write random scope for your Test model.
You may find helpful this question also - Random record in ActiveRecord
P.S. I'm not sure that the Test is a good name for your model. It could be already used by ruby or rails.