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.
Related
I am completely new to Rails and I have a database that links to a certain page depending on the user's search but it will always give me the id.
For example if a user searches, I will get, "localhost:3000/fruit/1" instead of "localhost:3000/fruit/apple". Does anyone know how to switch the url from an id to name?
You need to define a 'to_param' method in the model you are generating a link for, e.g.
class Fruit < ActiveRecord:Base
def to_param
name
end
end
Then in your controller you need to change the find for the show action to find by the attribute you are using in your 'to_param' method, e.g.
#fruit = Fruit.find_by(name: params[:id])
See http://api.rubyonrails.org/classes/ActiveRecord/Integration.html#method-i-to_param for additional details
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 have a custom route setup that has a location_id in the url (see below)
resources :menu_items, :path => "/location_menu/:location_id"
So when I hit /location_menu/1 it will show me location_1's menu, /location_menu/2 will show location_2's menu, etc.
Each user is associated to multiple locations (has_many :locations)
I am trying to use cancan to restrict users from viewing certain menu_item URLS.
For example: User 1 is associated with location 1 and 2. So they can only view the page /location_menu/1 and /location_menu/2. But they would not be able to view /location_menu/3.
I created a custom method as a before_filter in my controller:
before_filter :location_check
...
def location_check
#location = Location.find(params[:location_id])
authorize! :see_location, #location
end
In my ability.rb
can :see_location, MenuItem do |location| location && user.location_ids.include?(location.id) end
For some reason, this does not work for me. What could I be doing wrong? If you guys could help me, I would really appreciate it!
Thanks.
Check once within ability.rb,
user.location_ids.each do |l|
can :view, MenuItem, location_id: l
end
First, I'm not seeing what benefit you get from defining a custom route rather than either a nested resource, but it looks likes its inflating your domain language and resulting in a situation where any particular resource could be referred to by several different names - this will probably become confusing enough that it's worth it to simplify now.
In your location_check method, you are authorizing see_location on a Location instance, but the portion of the Ability class you've shown us concerns a MenuItem class. Try defining your ability like this:
can :see_location, Location, id: user.location_ids
Edit
If you need a cancan action to authorize MenuItems directly, try this (assuming a belongs_to :location relationship on MenuItem):
can :view, MenuItem, location_id: user.location_ids
As for your routes, think even simpler. All you need is this:
resources :location_menus
...and then everything related to this goes in a LocationMenusController. Don't worry that there's no model by the same name - you still can still lookup your locations with Location.find(params[:id]). From what I understand, everything about the location menu pages hinges around the current user's access to a particular location, so you can treat LocationMenu as a kind of virtual resource wrapped around Location.
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.