DRYing up routes and javascript responses - ruby-on-rails

I'm not sure if I'm missing a known design pattern, but I keep coming up against the following problem with RESTful routes Rails.
In my example, I have a users controller that can respond in javascript (:js) format. The default response populates a page element with a list of the paginated users:
# /app/controllers/users_controller.rb
class UsersController < ActionController
def index
#users = User.paginate(:all, :page => params[:page], :conditions => ['name ILIKE ?', params[:name])
respond_to do |format|
format.html
format.js
end
end
end
The corresponding RJS template would look like:
# /app/views/users/index.js.rjs
page.replace_html :users, :partial => 'users'
This works fine, allowing me to perform AJAX lookups on users. However, in another part of my site (say the user editing form) I would like to perform an AJAX lookup of users, but update a set of ''select'' options or perform an inline autocomplete, rather than update the #users page element, e.g.
# /app/views/users/edit.html.erb
<%= f.text_field :name %>
$('#user_name').autocomplete({url: '/users', data: 'name=value', ...})
My question is what would be the best DRY way to achieve this? I don't think I should need to create a new controller action to correspond to the different view, as this would involve repeating the finder code. The only solution I've come across so far is to build some javascript conditions into my RJS helper:
# /app/views/users/index.js.rjs
page << "if($('#users').length > 0)"
page.replace_html :users, :partial => 'users'
page << "else"
page.replace_html :user_options, :partial => 'user_options_for_select'
This feels very brittle, and unclean for Rails. Am I missing something in how I can respond with different views depending on the calling controller?
Appreciate any help!
Chris

In one of them you have a list of users and the other one a list of options.
So even though for now, your two pages are having the same feature, they're independent from each other and you might want to change things for only one of them in the future.
So I'd keep them distinct with two different javascript actions . It'll will allow you to much more easily make them evoluate on their different path.
Anyway as you can see, they're already quite different. You have two different partials and two different html tags id.
Trying to have the same code for them both here seems quite confusing to me.
So yes I'd create two actions, one for the users list and one for their options.

Related

Rails3 Admin UI frameworks

We will soon be rewriting a 5 year old rails app, with a very unsound code foundation, from scratch in a brand new Rails 3 app with all the new hotness. The current app has a substantial custom admin UI backend which depends on now admin frameworks at all. Just some base controller classes and some somewhat useful CSS conventions. But maintaining that UI is a lot of work, especially if we want it to look half way nice.
So I'm in the market for an Admin UI framework that will make the simple stuff trivial, but without getting the way of more complex customization in both form and function.
The top contender, ActiveAdmin, seems to be very popular, and after playing with it a bit I have some concerns. It seems to declare a whole unique DSL that exists in a single ruby file. This is kind of neat, but it's also a completely different than how most other Rails apps are architected. It abstracts away the view, the helper the controller, and gives you a pure ruby DSL. It seems to me that this would get in the way of doing tricky things, more advanced custom things in our admin views. DSL's are great, until you want to do something they don't explicitly support.
Example "Resource" from my experimentation, with no controller and no view.
ActiveAdmin.register Region do
menu parent: "Wines"
show title: :name
index do
column(:zone) { |o| link_to o.zone, admin_region_path(o) }
column(:name) { |o| link_to o.name, admin_region_path(o) }
default_actions
end
end
So, the questions:
Is not being based on the standard Rails MVC architecture in separate files, and the typical controller inheritance based implementation of an admin area, actually something I should be concerned about? Will it hamper extensibility in the long term?
Is the DSL in ActiveAdmin better and more flexible than I'm giving it credit for?
Should I be looking at some other framework that lends itself better to my goals of high customization?
Should I stop being lazy and roll my own?
Does the choice of Mongoid instead of MySQL as a database affect any of the above questions?
It is worth mentioning that in ActiveAdmin you can also create your own complex forms by using partials
form partial: 'my_form'
and expand controller functions with a controller block.
controller do
def edit
end
def index
end
end
+1 for active admin, I use it in many projects, including a cms I'm building. It indeed is more flexible than many people who are newer with it give it credit for, at the end of the day you can always do:
controller do
def custom_action
Puts "hi"
end
end
(Think that's the right syntax writing from phone so all this is off top of head)
Also, I swear by inherited resources, which active admin controllers extend, as they really force you (in a good way) into writing restful, reuse able code. Bottom line is I believe active admin is leaps and bounds ahead of the others I've tried (railsadmin and at least one other)
Update:
Sure, here is the inherited_resources documentation
https://github.com/josevalim/inherited_resources
And here is an example of modifying the controller directly, from my little CMS project.
ActiveAdmin.register Channel do
index do
column :title
column :slug
column "Fields" do |channel|
"#{link_to('View Fields', admin_channel_entry_fields_path(channel))} #{link_to 'Add Field', new_admin_channel_entry_field_path(channel)}".html_safe
end
column "Actions" do |channel|
"#{link_to('View Entries', admin_channel_entries_path(channel))} #{link_to 'Create Entry', new_admin_channel_entry_path(channel)}".html_safe
end
default_actions
end
form :html => { :enctype => "multipart/form-data" } do |f|
f.inputs "Channel" do
f.input :title
f.input :display_name
f.input :image
end
f.buttons
end
controller do
def begin_of_association_chain
current_site
end
def tags
query = params[:q]
if query[-1,1] == " "
query = query.gsub(" ", "")
ActsAsTaggableOn::Tag.find_or_create_by_name(query)
end
#tags = ActsAsTaggableOn::Tag.all
#tags = #tags.select { |v| v.name =~ /#{query}/i }
respond_to do |format|
format.json { render :json => #tags.collect{|t| {:id => t.name, :name => t.name }}}
end
end
end
end
Basically, I am using the inherited resources, begin_of_association_chain method (one of my favorite things about IR), to scope all the data within channels, or any of the admin resources that inherit from my channels resource, to the current site, without having a url like /admin/sites/1/channels -- Because I am already setting current_site based on the url the visitor enters with. -- Anyways, basically once you are inside:
controller do
puts self.inspect
end
Returns the actual controller itself, e.g. Admin::ChannelsController (which < InheritedResources::Base, maybe not directly but all the IH controller methods should be available at this point).

Displaying Posts by Tag - Ruby on Rails Blog

I have a Rails blog coming along quite nicely. However, I cannot seem to get my tagged articles to show up within their own page (for example, I'd like ONLY the articles tagged Arts & Entertainment to show up when that link is clicked).
I have a column in my scaffold model entitled tags. It takes a string. So
1) How do I go about accessing ONLY a specific tag? I tried something like the following:
def self.sports
find(:all, :tags => 'gainmuscle')
end
to no avail.
2) How do I get them to show up in the view?
Any help would be very much appreciated.
You can probably define a method inside your controller something similar to this :
def self.tagged_with
#articles = Article.tagged(params[:tag]).paginate(default_paginate_options)
end
Here tagged is a namedscope. you can neglect that but the action generates all the articles which are tagged with the tag selected as :param. Tag is an attribute of the model Article in this case.
1) here you could use the scope in the model:
class YourModel < ActiveRecord::Base
scope :by_tag, lambda{|tag| where(:tag => tag)}
...
end
And then in controller:
#collection = YourModel.by_tag("gainmuscle")
2) I would say the best way is to create a partial with html code for 1 single post, and then render it this way:
render :partial => 'post', :collection => #collection
(You can check partials usage here: http://apidock.com/rails/ActionView/Partials)
Just to mention another approach; there are quite a few gems to deal with this exact purpose.
This might be interesting, and personally I can vote for acts_as_taggable_on. This gives you quite a lot of options which might come in handy later on as well.

how to pass instance variable to another view in rails

Hey. I think I am in a mind trap here. I am using Rails 2. In the index view of my controller I set up something like
def index
#posts = Post.all
end
so that I can use #posts in my index, e.g. each-do. Id like to pass #posts to a custom made view, in where I can use the same variable again. This I want to do over a link from the index view. Something like that:
link_to "newpage", {:controller => 'posts', :action => 'newmethod', :param => #posts}
What I have created so far is a new method in my Post controller. A new view. And and a new route to that site. Any suggestions? thx for your time
You're going to have to collapse those values into something that will fit in a URL, then decode them later. For instance:
# Put this in your helper method module PostsHelper
def post_ids
#posts.collect(&:id).join(',')
end
Your adjusted link would be:
link_to "newpage", {:controller => 'posts', :action => 'newmethod', :param => post_ids }
When you fetch the next page you'll need to decode these by retrieving them again:
#posts = Posts.find(params[:param].split(/,/))
There's no way to pass an instance variable between requests because they are explicitly cleared out.
As a note, try and use the generated route methods instead of the hash-style declaration. You would probably have a route already listed in rake routes:
# Instead of { :controller => 'posts', :action => 'new', :param => post_ids }
new_post_path(:param => post_ids)
These generated methods are much more readable in practice and have the advantage of being configurable later if you want to re-interpret what they mean by adjusting your routing table.
Another note is that if the list of IDs gets very large, you may not be able to encode them into a URL as the limit is about 1500 bytes. You may instead have to serialize the conditions used to generate the list in the first place and then re-run those again later. So long as you're dealing with tens of items and not hundreds you should be okay, though.
In your controller
def newmethod
#posts = Post.all
end
You can't pass all your models in a link ! The #posts var in the index action disappears after the request
I know that store arbitrary data in session is not a best practice, but in some cases this approach is simple and easy.
In your controller:
def balabala
#...
session[:your_var] = "this is the var used in another view&action!"
# ...
end
In any other pages:
<%= session[:your_var] %>
That's it. ugly, not MVC at all. :) Only recommended for very rare cases. :)

Rails: Difference between List and Index

I appreciate this is an incredibly noob question, but having Googled multiple combinations of search terms I regretfully am still in the dark. These things can be difficult when one doesn't know and so obvious when one does.
I have semi-home page where incoming customers can choose to see a queried list or do a handful of other things. This isn't a home page but a sort of mini 'switchboard' within the site.
The seven standard RESTful Rails controller methods are (as I understand them):
List # shows a list of records generated with .find(:all)
Show # shows details on one record
New # initiates new record
Create # saves and renders/redirects
Edit # finds and opens existing record for edit
Update # updates attributes
Delete # deletes record
What to use when some users need to see a selected 'list' of records that isn't literally .find(:all)? How would this work given I still need a list function that gives me .find(:all) for other purposes?
I've heard of 'index' being used in Rails controllers, but I don't know the difference between index and list.
For best practice and best design, what controller methods would you use for a mini-switchboard (and other intermediate pages such as 'About Us')?
Any specific answers would be a bit more useful than links to http://guides.rubyonrails.org/action_controller_overview.html etc. :) Thanks very much.
First, I think it's important to note that the "standard methods" are neither standard nor methods in a sense. These are considered actions, and are only standard in that they are the conventions used with scaffolding. You can create any number of actions and group them logically with a controller.
If you open up [Project]/config/routes.rb and read through the comments, I think you'll understand a little better how controllers and actions map to a specific route. For instance, you can create a named route to the login controller's login action and call it authenticate by adding to the top of your routes.rb:
# ex: http://localhost/authenticate
map.authenticate 'authenticate', :controller => 'login', :action => 'login'
# map.[route] '[pattern]', :controller => '[controller]', :action => '[action]'
# ex: http://localhost/category/1
map.category 'category/:id', :controller => 'categories', :action => 'list'
# ex: http://localhost/product_group/electronics
map.search 'product_group/:search', :controller => 'products', :action => 'list'
To partially answer your question, you may want to consider adding a category model and associating all products to a category. then, you can add a named route to view items by category as in the code block above.
The major benefit to using named routes is that you can call them in your views as category_url or category_path. Most people don't want to do this and rely on the default route mappings (at the end of the routes.rb):
# ex: http://localhost/category/view/1
map.connect ':controller/:action/:id'
# ex: http://localhost/category/view/1.xml
# ex: http://localhost/category/view/1.json
map.connect ':controller/:action/:id.:format'
The key thing to mention here is that when a URI matches a route, the parameter that matches against a symbol (:id, or :search) is passed into the params hash. For instance, the search named route above would match a search term into params[:search], so if your products have a string column called 'type' that you plan to search against, your products controller might look like:
class Products < ApplicationController
def list
search_term = params[:search]
#products = Product.find(:all, :conditions => ["type = ?", search_term])
end
end
Then, the view [Project]/app/views/products/list.html.erb will have direct access to #products
If you'd really like an in-depth view into Ruby on Rails (one that is probably 10 times easier to follow than the guide in the link that you posted) you should check out
Agile Web Development with Rails: Second Edition, 2nd Edition

Rails Rendering Action in Different Controller

So I have a controller called Music with one action which is index. On the music/index.html page I list out a bunch of Songs (via a Song model) and at the bottom I have a for to create a new Song.
Song has validations which I have tested and work just fine. When the new Song saves in controller Songs action create I redirect_to => 'music_index_path' so the person can see the new song in the list. However when the Song does not save (does not pass validations) then I cannot use redirect_to since the form error_messages do not carry over. I need to use render but cannot say render :controller => 'music', :action => 'index.
My goal is to be able to show the error messages for the Song form ON the music/index.html page.
How should I go about this. I am open to other ideas (like changing up controllers) as well.
Thanks!
It sounds to me like Music should be a part of Song, or the other way around. You can always use routes to disguise it as one or the other to the user.
song/index sounds to me like it should display all songs, which is all music does anyway.
My first thought is that we need to rethink this process. Assuming you're using RESTful controllers, it's unclear to me why you would need a Music controller and a Song controller... how are those resources different? The next relevant question would be, why is it not sufficient to show errors via Song#create ? I mean, they couldn't get it right when it was just a form, is the distraction of additional content likely to help? :)
With that said, here is a possible solution. (Given that you didn't paste your code, I'm making a lot of assumptions here.)
<hack>
first, extract the form parts from songs/new to songs/_form, then from the music/index view, render :partial => songs/_form, and in the songs controller, render :action => '../music/index' (this is called a hackity-hack.) Because it's a hack, you will almost certainly need to go into music#index and add #song = Song.new
</hack>
If you start running on edge, the ability to pass a flash through a redirect was just added...but that doesn't really get you there.
The simplest solution though, is that you need to render index, but set up all of the variables that are needed for that page. If you factor that out into a separate module or method, you can call it from both the index action and the save failure.
def index
setup_for_index
end
def create
#song = Song.new(params[:song])
#song.save
#...
#on failure
setup_for_index
render :controller => "music", :action => "index"
end
def setup_for_index
#songs = Song.all
#etc
end
The other thing you could do is use form_remote_for and have the song form just update the div on failure. Then use an RJS template return type reload the whole song list on success.
While I want to reiterate what others have stated about your resource architecture deserving a second look, you can certainly render views for other resources using the :template option:
render template: 'music/index'
Why not a simple if request.post? conditional in the index action's view rather than redirecting to another page?
You can also try using flash[:song_save_error] to pass the error conditions back to your Music controller.
http://api.rubyonrails.org/classes/ActionController/Flash.html
You could try render :file => ...

Resources