Dividing a Rails edit view for the purposes of UI - ruby-on-rails

So I have a 'Project' model which respective db table has many attributes.
Ideally, when I hit projects/1/edit, I'd like the user to:
See and be able to edit only a portion of the project attributes
See a navigation menu that allows her to navigate to another view that allows to see and edit the attributes left.
All this instead of showing all the attributes in one 'edit' view.
I'm leaning to a JS solution that hides and shows specific attributes when a menu is clicked. However, I'd ideally want that the urls reflect the section the user is located for example:
Instead of a url like projects/1/edit I'd like something like:
projects/1/edit/name_of_section_1 and projects/1/edit/name_of_section_2
In any case I wonder what would be the best practice when it comes to divide views like this.
UPDATE: Only a few precisions/revisions from Dan's solution below for future reference
I've used his second approach successfully revising his proposed code as follows:
Config/routes.rb
revised the controller name for both routes to 'projects'
resources :projects do
member do
get "settings/:panel" => "projects#edit", as: :settings
patch "settings/:panel/update" => "projects#update", as: :update_settings
end
end
Controllers/projects_controller.rb
There was some mistake in the proposed code for the edit action so I slightly changed it to make it work
def edit
panel = ['general', 'invitations', 'welcome_message', 'danger'].detect{|p| p == params[:panel] }
render template: "projects/#{panel}.html.erb" if panel
end

There's a couple of ways to do this and there all equally valid.
If you need all the fields filled in to create the record you'll either need to have tabbed views with javascript (bootstrap has a nice plugin for this)
To do this with custom routes you could have:
resources :projects do
member do
get "settings"
patch "update_settings"
get "settings2"
patch "update_settings2"
end
end
which would give you URLs like /projects/1/settings and projects/1/update_settings
If you have lots of panels you could do this more efficiently with something like this:
resources :projects do
member do
get "settings/:panel" => "settings#edit" as: :settings
patch "settings/:panel/update" => "settings#update" as: :update_settings
end
end
to allow for routes like /projects/1/settings/page1 and /projects/1/settings/page2
in your controller you could then have:
def edit
if panel = ['page1', 'page2'].detect{|p| p == params[:panel] }
render template: panel
end
end
this checks ifs its a valid panel you've made and then renders that specific template you've created in app/view/projects/page1.html.erb

Related

Rails 4: Simple search form with wrong redirect

I have some trouble with simple search form. When I pass information in the form on page:
/search
and press submit button it collects data and searches but instead of reloading current page it's passing params to index:
/claims?utf8=✓&claim_id=49&phone_number=%2B34(44)444444
But I want to show the result of search on current page (without any AJAX).
If I manually pass those params and change link it's working greate!
/search?utf8=✓&claim_id=49&phone_number=%2B34(44)444444
My routes.rb
Rails.application.routes.draw do
resources :administrators
resources :claims
root :to => "sessions#landing"
...
get 'search' => 'claims#search'
post 'claims/new' => 'claims#new'
post 'claims' => 'claims#create'
end
My search function in controller:
def search
#claim = Claim.search(params[:claim_id], params[:phone_number])
end
How can I modify my routes to do that?
So, couple of things are happening here. You need to to do 2 things
Make sure your controller action can handle multiple types of requests
Make sure you have 2 routes, 1 for posting (since you aren't using ajax) and 1 for getting
controller action
if request.post?o
#claim = Claim.search(params[:claim_id], params[:phone_number])
else
Claim.all (or whatever you want to display when the page is fresh before a search has been done)
end
And your routes are a little messy so lets fix that when we add the post.
resources :claims do
collection do
get 'search'
post 'search'
end
end

Best practice for adding a non standard view and controller action?

My situation I have a "Parent" model and controller. I want to know the best practice for adding independent pages such as a dashboard for users. My thought is that I can create a view dashboard.html.erb and inside the parent controller create a method of:
Parent controller
def dashboard
end
Routes.rb
get 'parents/dashboard'
I've done this once and it worked fine, but is was for a 'child' model.
When I run this same situation in the parent model I get the error
ActiveRecord::RecordNotFound in ParentsController#show
Couldn't find Parent with 'id'=dashboard
1.) All I've done is add a view, added the dashboard model to the controller, and placed get 'parents/dashboard' into the routes.rb and it tries to reference the show method??? Why?
2.) And is this the wrong way to add pages/actions to a rails application?
Do this:
#config/routes.rb
resources :parents do
get :dashboard, on: :collection #-> url.com/parents/dashboard
end
And is this the wrong way to add pages/actions to a rails application?
It's not "wrong", it's just ineffective, as demonstrated by your problem.
The problem you have is you've included your custom route below the resources :parents route. Because resources creates a /:id url which captures any requests sent to parents/:id, your "dashboard" request is being sent to the show action of the parents controller:
There are two remedies to your issue:
Put get 'parents/dashboard' above the resources :parents directive
Include an additional route to resources :parents (above)
You must remember that Rails matches your request with a route. That means the first route to match your request is processed.
So if you have...
#config/routes.rb
resources :parents
get "parents/dashboard"
... Rails will assume the dashboard is the :id in url.com/parents/:id, thus sending the request to show.
Apart from the very top code (the recommended answer), you could have the following:
#config/routes.rb
get "parents/dashboard", to: :dashboard
resources :parents
If you want to add an additional route with an :id then the syntax is different.
get 'parent_dashboard/:id', to: 'parents#dashboard'
Notice that the person string after get. This is used as the address of the website, when the user hits this, it would go to localhost:3000/parent_dashboard/1 if it is the first dashboard. You can exclude :id if you'd like. Of course, this would be different from the use case.
The second part of the route syntax is the :to, this method tells your app which controller and method to look at.
Hope this helps!
If you want to add a new view to the parents folder. Just do this:
parents_controller.rb
def dashboard
#parent = Parent.find(params[:id])
end
routes.rb
get '/parents/:id/dashboard', to: 'parents#dashboard', as: :parents_dashboards
resources :parents
Then in your parents/dashboard.html.erb view you can do everything that you can do in the parents/show view.
The link to your dashboard view would be parents_dashboards_path and you might have to use parents_dashboards_path(#parent) or parents_dashboards_path(parent) in certain circumstances.
This is an example of a custom path that works without using nested resources to accomplish access to the parent's dashboard.
I am using this approach in a project so I would like to hear any critique or comments on this approach. PEACE I'M OUTTA HERE!

Including attributes in custom Rails routes

I hope the title is not to misleading, as I don't know a better title for the problem I'm working on:
I have a doctor which belongs to location and specialty. I'd like to route to show action of the doc controller like this:
/dentist/berlin/7
I defined my routes like this:
get ':specialty/:location/:id', to: 'docs#show'
And in my views create the following url to link to the show action of the doc controller:
<%= link_to doc.name, "#{doc.specialty.name}/#{doc.location.name}/#{doc.id}" %>
Is this a good solution to the problem? If not, is there a cleaner way to construct urls like this possibly using resources? What the heck is the name for a this problem?
Thank your very much for your help in advance.
For references, you should have a look at this page (especially the end of section 2.6)
If it is only for a single route, it's okay as you did. But then if you want to have more than one route (like /dentist/berlin/7, /dentist/berlin/7/make_appointment, etc.) you might want to structure a bit more your routes so as to take advantage of rails resources.
For example, instead of
get ':specialty/:location/:id', to: 'doctors#show'
get ':specialty/:location/:id/appointment', to: 'doctors#new_appointment'
post ':specialty/:location/:id/appointment', to: 'doctors#post_appointment'
You could have something like this (the code is almost equivalent, see explanation below)
resources :doctors, path: '/:specialty/:location', only: [:show] do
member do
get 'new_appointment'
post 'create_appointment'
end
end
Explanation
resources will generate the RESTful routes (index, show, edit, new, create, destroy) for the specified controller (doctors_controller I assume)
The 'only' means you don't want to add all the RESTful routes, just the ones specified
Then you want to add member actions, ie. actions that can be executed on a particular item of the collection. You can chose different syntaxes
resources :doctors do
member do
# Everything here will have the prefix /:id so the action applies to a particular item
end
end
# OR
resources :doctors do
get 'new_appointement', on: :member
end
By default, the controller action is the same as the path name you give, but you can also override it
member do
get 'appointment', action: 'new_appointment'
post 'appointment', action: 'post_appointment'
end
Rails has some wonderful helpers when it comes to routing !
The correct approach is to give your route a name, like this:
get ':specialty/:location/:id', to: 'docs#show', as: 'docs_show'
Then you can use it like this:
<%= link_to doc.name, docs_show_path(doc.specialty.name, doc.location.name, doc.id) %>
Note 1:
Rails appends _path at the end of the route names you define.
Note 2:
You can see all the available named routes by executing rake routes.

Associating two objects without nested resource

Right now I have this:
resources :sources do
resources :pages
end
And when I want to create a page for a particular resource, I use the following route helper:
new_source_page_path(source_id)
So in the controller I know for which source I need to create the page for.
My routes are getting really messy with deep levels of nesting, and I have decided to avoid doing the nesting. However my doubt is, if I have this:
resources :sources
resources :pages
In the new action of my PagesController, how do I pass or tell that this new object needs to be associated with that particular Source?
You can pass the source_id as a GET param. Create your page#new links like this:
<%= link_to "new page", "/pages/new?source_id=#{source_id}" %>
Then in the page#new action assign that source to the page when you create the new page:
#page = Page.new
#page.source = Source.find params[:source_id]

Dynamic routes on runtime in Rails

I'm developing a site using refinery. Now for one specific page that is created in the back-end of refinery, i want to use my own controller and views. All the User can do with this page is to set the menu-position, title, meta-info etc. The URL for this page has to look the same as all the other pages.
So for example, the menu structure looks like:
menux
menu1
menu2
specific page
menux
And the URL for "specific page" looks like "locale/menu1/menu2/specific page"
The site is available in multiple languages, so i have to create these routes for all languages.
Currently i'm creating the routes like this:
specific_page_id = 1
Refinery::I18n.frontend_locales.each do |lang|
slugs = []
page = Refinery::Page.find_by_path_or_id(nil, specific_page_id)
# get slug for page in current language
slugs << page.translations.select { |p| p.locale == lang }.first.slug
# get all slugs from parrent pages
while !page.parent_id.blank?
page = Refinery::Page.find_by_path_or_id(nil, page.parent_id)
slugs << page.translations.select { |p| p.locale == lang }.first.slug
end
match "/:locale/#{slugs.reverse.join("/")}" => "controller#action", :via => :get, :constraints => { :locale => /#{lang}/ }
end
With this, i'm getting a route to the specified page in every language like described above.
But the problem is, when the user changes the name of the page or the position in the menu, the routes have to be generated again, which isn't done too often.
Now my question is, how can i do this more dynamically on run-time? I've read a bit about constraints but i don't know if this is what i need.
Thanks for your help!
I needed to figure out building routes off a database model myself in a Rails 4 application (which is called "ComingSoon" in the examples below. I wanted pages that could be edited on the back-end and given a user-friendly name, which is stored in the Page#name field. So "About Us" titled page typically becomes "about_us" name, which leads to "http://localhost:3000/about_us" The following is the technique I came up with:
Create a new model in app/models/dynamic_router.rb
class DynamicRouter
def self.load
ComingSoon::Application.routes.draw do
Page.all.each do |pg|
get "/#{pg.name}", :to => "pages#show", defaults: { id: pg.id }, as: "pages_#{pg.name}"
end
end
end
def self.reload
ComingSoon::Application.routes_reloader.reload!
end
end
The key above is that I pass the page's id as one of the parameters, so look up is still on the Page#id field, which is, IMHO, a lot better than using the friendly plugin or lookups on slugerized values.
Add the following line to your config/routes.rb
ComingSoon::Application.routes.draw do
# ...
DynamicRouter.load
end
Finally, when the Page is updated, we need to reload the routes, so add an after_safe callback on the Page model:
class Page < ActiveRecord::Base
after_save :reload_routes
def reload_routes
DynamicRouter.reload
end
end
I plan to refine this further to only reload routes if the name attribute is changed and perhaps simply edit the existing route rather than reloading everything if performance proves to be an issue (which at the moment, its not).

Resources