Probably heard it a hundred times already but thanks for all the screencasts. They really are excellent and find them to be a great resource!
I encountered a strange problem that I'm not really sure I understand. While experimenting with the ideas from your screencast I decided to add a respond_to block to a method being called through AJAX. After I did that whenever I made the AJAX call Rails complained that it couldn't find the partial that I was trying to render in my corresponding RJS file. I check my RJS and surely it was render :partial => 'form' and the partial was named _form.html.erb. The only way I could get it to work was if I removed the format.js statement from the respond_to block.
What if I want to record the country name and state name in the person model (rather than their id?) But I still want to record country_id in the state model. Can anyone point me in the right direction?
If I wanted to break the login controls out onto the home page (say with as the home controller in your example, rather than having the use click on a login link), how would I tell the home controller to get the User controller to make a new session?
Right now if I put the controls on the home page, the submit of course gets me an "uninitialized constant SessionsController" stack trace...
sorry about posting in spanish, but:
en este
<a href="http://qvitta.net/articles/2008/04/24/javascript-no-obstrusivo-o-cuando-ajax-es-un-problema">post comento un poco los problemas de ajax, este enfoque y demas</a> en espaƱol! si os lo quereis mirar.
In this episode you clearly showed how to handle checkboxes with HMABTM relations.
But you said in the comments that if you have many_to_many relations you have to create your own logic. Would you please expand on that in a future episode. I am an experienced software engineer up to now working in perl and php, but a newbee to Ruby and I greatly appreciate these podcasts. I have looked through the archive of episodes but don't see any response addressing my issue.
oops sorry (I've made a two round posting here :-> )
@martin:
las rutas por defecto de rails funcionan, fijate si no las has eliminado en alguna refactorizacion REST, sino hay otro screencast sobre custom_routes en REST
I have give you approach a try and it works pretty fine.
One thing though is that this way you end up with a two round call to rails to fulfill the request, one call to the people_controller and another to the javascripts_controller, with the repeating of bussiness processing if the thing get too complex.
If you want to try another approach this one looks fine:
http://railsenvy.com/2008/1/3/unobtrusive-javascript
@Lucas, you shouldn't need to specify "layout :application" as this will be the default. However, if you do need to specify another layout you can do this:
http://pastie.caboo.se/185520
I would consider this to be a bug in Rails, maybe I'll get around to submitting a ticket or patch.
@Aerpe, thanks for the suggestion. I'll add it to my list. :)
I think the main problem is that scriptaculous' Ajax.Autocompleter by default sets the target element's innerHTML to the response of the AJAX call.
Maybe an idea would be to override Ajax.Autocompleter's default onSuccess behavior so that it parses a, say, JSON string and turns it into a list before assigning it to the innerHTML of the target element. This way, it would be reasonable to have a mime type of text/javascript or even application/json because JSON is returned.
@Lucas - thanks for this 'semi' fix. I've been having problems with for the last day - no one else mentioned having problems so I thought my copy paste skills needed tuning...
I also have a layout :switch_layout (which is serves up a different layout depending on who's logged in) in my controller which is sending this Railscast off the tracks.
From what I've read of the above comments (Ryan, Joe and Geoffrey) - it would be cool to see a sudo mime type used and also a refactored auto_complete plugin...
I have no idea why everyone else gets this to work. I followed it step by step by I couldn't. Are you supposed to create three tables: Categories, Products, and Categories_products? And I tried something like product:references when generating a scaffold and it produces product_id instead of product_ids...the railscast should provide more information on this.
Thanks for the screencast, it's great and very useful.
When I was trying this, I found a problem with my controller: It wasn't generating anything in the "/controller.js" step. So I put the following code in the end of the method:
respond_to {|format| format.js}
When I tested it, I was given a 'Template is missing' screen, asking for an 'layouts/application.js.erb'.
So I realized my controller has a unremoved "layout 'application'" line in it.
After I removed it, it all worked.
I don't know what really happened, but it may generate some headache for people who'd really need the "layout 'application'" line.
I keep getting a major loop happening between the before_filter in my root controller...and the app controller authorize function, because if the filter chain is halted due to the admin not being logged in, the redirect is back to my root controller, which then calls the authorize function from the before filter...thus creating a loop...
so to remedy...this, from authorize
I redirect to the /sessions/new which is fine...but now every route /url is redirected there ... even though I have an :except in my before filter
This generates the formatted_catgories named route which should do what you want. I didn't need a respond_to block in my controller, but if you're not using Rails 2 you will need one.
Ryan, great railscast as always, but I have a question. When I enter "categories.js" into my browser to display the list of autocomplete items, I get "No route matches '/categories.js' with {:method=>:get}". I don't think I have my routes set up properly. Did you add any routes besides the default ones? I'm not well versed in routing yet, so I'm not sure what I need to add.
How would this be different if you wanted to map two virtual attributes to one real attribute? In this example, how would the code change if your database stored full_name and you wanted separate virtual attributes for first_name and last_name?
@James, the model will need access to the parent_id somehow, so I recommend creating a 2nd virtual attribute which sets an instance variable.
The problem is there's no way to guarantee the parent_id attribute will be set before the category_name. To solve this, you can use a callback to handle the category setting. Check out this code.
http://pastie.caboo.se/184873
You may want to email me if this doesn't solve your problem: ryan[at]railscasts[dot]com
When you create a setter method, they can only have one parameter (the value of the virtual attribute). How do you include other values from your controller in these setter methods?
def category_name=(name)
self.category = Category.find_or_create_by_name_and_parent_id(name, parent_id) unless name.blank?
end
Can't seem to figure out how to include additional variables (parent_id in this case) in these setters to keep them from being found if they 'belong_to' a different parent.
Same thing would occur if you were trying to tie a model object to "current_user".
@James, I'm not exactly sure if I understand your question. However, you could create a second virtual attribute for the bucket_id, save it to an instance variable, and use that in the category search.
@Geoffrey, I agree. I think they extracted this out into a plugin for a reason. I would love to see an updated version of it which flows better with modern techniques (such as REST).
@Bryan, it's a custom snippet I made. I use a lot of custom snippets and they are pretty easy to make. I recommend looking into it and developing your own library of snippets.
[Long time listener, first time caller...] Ryan, I swear I saw you type "field.{tab}" to trigger a snippet of a formated label+field. Is that a stock TextMate function or something you wrote for yourself?
Great screencast, but it really exposed how hackish the autocomplete plugin is.
Why isn't it a regular form builder like the other form fields? Customizing it shouldn't be so awkward (i.e. passing raw Javascript). Why can't the controller method use the virtual attribute on the model?
To be honest, using unobtrusive Javascript seems more straightforward than jumping through Ruby to accomplish this.
There is a real opportunity for someone to refactor this plugin and become a hero!
This may be beyond the scope of this tutorial, but how do you handle setting virtual attributes that are scoped to another model? Let's say you want to have several categories that belong to a bucket, and want to find_or_create_by_name_AND_BUCKET_ID. You can't pass the bucket info to the virtual attribute in the model. This question is much more about virtual attributes than auto-complete, but I've had it come up a lot in other situations as well. Thanks!
Oh, I forgot to mention that the config file doesn't get loaded until the first time you try to access a setting (lazy loading). If you don't fetch a config setting value with Configuration.xxx it adds zero overhead. Ruby gives you this for free.
@Joe, good points. The more I think about it, a custom mime type would probably be a preferred solution here over using js. As mentioned above, ideally I would like to return javascript here with RJS, but AFAIK the plugin/scriptaculous doesn't support this. Someone please correct me if I'm wrong.
I hadn't thought about the escaping issue. I'll change the code in the show notes. Thanks!
This is a pretty common app configuration pattern but for me it needs some rubification. I do pretty much the same thing but don't use a constant Hash, rather a class to manage the settings:
In lib/configuration.rb:
class Configuration
@@settings = YAML::load_file('config/myconfig.yml')[RAILS_ENV]
class MissingConfigOptionError < StandardError; end
def self.method_missing(key)
raise MissingConfigOptionError("#{key.to_s} is not in the config file") unless @@settings.include?(key.to_s)
@@settings[key.to_s]
end
end
That's it, there is nothing else to do. Create a config/myconfig.yml file with all the same stuff as in this railscast:
In config/myconfig.yml:
development:
foo: "bar"
production:
foo: "yummy"
To fetch and use the settings anywhere in your code, model, controller or view do:
x = Configuration.foo # => "bar"
Customize to suit. Maybe store the settings with mem_cache in a production environment and have a rake task to reload it or handle missing config options differently with perhaps a reasonable default (not a good idea IMHO) or add a test for the existence of a given key.
You could also make this a plugin without a problem by putting it in vendor/plugins/my_config_hotness/init.rb instead of lib/configuration.rb. Six and one half dozen.
Enjoy.
PS. This is not my idea. Someone a lot smarter than I put me on to it but went even further to allow for nested groups:
development:
foo: "bar"
bar:
foo: "yummy"
x = Configuration.bar.foo # => "yummy"
But I can't for the life of me find the page where I saw it done and I guess I failed to bookmark it :(
Retried this tutorial after setting up restful_auth as in episode 67.
I get the error when trying to execute a login using my openid:
Unknown action
No action responded to show
Something is getting confused with the routing within the controller and is trying to call the show action. Anyone run into this or find a work around?
I love RailsCasts but I have two comments around this particular episode:
1) I think that using "js" for the request really is wacky. The content type of the response is "text/javascript" indicating that the content really *is* javascript. Perhaps this is a place to register a new psuedo mime type?
2) You are probably open to escaping bugs when you are passing the search parameter through. What happens with your sample app if you type "?foo=bar" into your category box. You need to url encode the value.
Change element.value to encodeURIComponent(element.value).
The prototype way would be to replace the entire :with value with:
Object.toQueryString({search: element.value})
Wow this was a critical failure for me, but I can't see where I went wrong. I tried using the openid railscast on top of the restful_authentication using the stateful engine.
Everything seemed to work until I tried logging in. The engine went out to my openid provider and when control was passed back to my app it was looking for session/show.html.erb.
Just wanted to say thanks for this awesome screencast, and for all the others on your site. It's great to see an enthousiast like yourself sharing knowledge with the rest of the community.
You screencasts make my monday-mornings considerably more pleasant ;-).
@Mourad, great questions, I was wondering if someone would bring this up.
It would be really nice if the auto_complete plugin supported returning javascript (with rjs) instead of a simple HTML list, this way I could provide further instructions for modifying the functionality, however, AFAIK the plugin doesn't support this. Since the HTML list is so closely tied to javascript I find it acceptable to use the "js" extension.
As for your 2nd question, this is more difficult. You could pass a query parameter as Carlos mentioned. Alternatively you could create a new mime type, perhaps call it "autocomplete" and make an "index.autocomplete.erb" template. This should work, but I'm not sure if it's a best practice.
Hi,
Probably heard it a hundred times already but thanks for all the screencasts. They really are excellent and find them to be a great resource!
I encountered a strange problem that I'm not really sure I understand. While experimenting with the ideas from your screencast I decided to add a respond_to block to a method being called through AJAX. After I did that whenever I made the AJAX call Rails complained that it couldn't find the partial that I was trying to render in my corresponding RJS file. I check my RJS and surely it was render :partial => 'form' and the partial was named _form.html.erb. The only way I could get it to work was if I removed the format.js statement from the respond_to block.
Basically this works (controller):
def fill
@post = Post.new
@post.title = "Fill"
@post.body = "Fill"
respond_to do |format|
format.html { redirect_to(posts_url) }
format.xml { head :ok }
end
end
But this doesn't:
def fill
@post = Post.new
@post.title = "Fill"
@post.body = "Fill"
respond_to do |format|
format.html { redirect_to(posts_url) }
format.xml { head :ok }
format.js #This line causes an issue
end
end
What gives?
Rom
Great stuff Ryan!
I have a question though (for anyone).
What if I want to record the country name and state name in the person model (rather than their id?) But I still want to record country_id in the state model. Can anyone point me in the right direction?
If I wanted to break the login controls out onto the home page (say with as the home controller in your example, rather than having the use click on a login link), how would I tell the home controller to get the User controller to make a new session?
Right now if I put the controls on the home page, the submit of course gets me an "uninitialized constant SessionsController" stack trace...
@Stephen, thanks! I had no idea there was a :param_name option. That is much nicer. I'll update the code in the show notes.
This almost works, but it breaks support for tokens.
Instead of specifying :with, I use :param_name. This gives you the correct query string, it escapes the query automatically and tokens are supported.
<%= text_field_with_auto_complete :product, :category_name, { :size => 15 }, { :url => formatted_categories_path(:js), :method => :get, :param_name => 'search' } %>
brilliant as always...
sorry about posting in spanish, but:
en este
<a href="http://qvitta.net/articles/2008/04/24/javascript-no-obstrusivo-o-cuando-ajax-es-un-problema">post comento un poco los problemas de ajax, este enfoque y demas</a> en espaƱol! si os lo quereis mirar.
Thanks Ryan for yet another gem of a tutorial. keep up the great work.
Great screencast! I had this problem in one of the project I am working one. Thanks.
In this episode you clearly showed how to handle checkboxes with HMABTM relations.
But you said in the comments that if you have many_to_many relations you have to create your own logic. Would you please expand on that in a future episode. I am an experienced software engineer up to now working in perl and php, but a newbee to Ruby and I greatly appreciate these podcasts. I have looked through the archive of episodes but don't see any response addressing my issue.
Carl
oops sorry (I've made a two round posting here :-> )
@martin:
las rutas por defecto de rails funcionan, fijate si no las has eliminado en alguna refactorizacion REST, sino hay otro screencast sobre custom_routes en REST
that's it
hi,
great work.
I have give you approach a try and it works pretty fine.
One thing though is that this way you end up with a two round call to rails to fulfill the request, one call to the people_controller and another to the javascripts_controller, with the repeating of bussiness processing if the thing get too complex.
If you want to try another approach this one looks fine:
http://railsenvy.com/2008/1/3/unobtrusive-javascript
keep on the good work
@Lucas, you shouldn't need to specify "layout :application" as this will be the default. However, if you do need to specify another layout you can do this:
http://pastie.caboo.se/185520
I would consider this to be a bug in Rails, maybe I'll get around to submitting a ticket or patch.
@Aerpe, thanks for the suggestion. I'll add it to my list. :)
I think the main problem is that scriptaculous' Ajax.Autocompleter by default sets the target element's innerHTML to the response of the AJAX call.
Maybe an idea would be to override Ajax.Autocompleter's default onSuccess behavior so that it parses a, say, JSON string and turns it into a list before assigning it to the innerHTML of the target element. This way, it would be reasonable to have a mime type of text/javascript or even application/json because JSON is returned.
Important Safety Tip:
The custom route:
map.open_id_complete 'session', :controller => "session", :action => "create", :requirements => { :method => :get }
Should be placed BEFORE the main route:
map.resource :sessions
When I switched these, everything started working ok
Hey,
I can't find any contact details so I'll try my luck here.
There's hardly any resources or tutorials on developing the navigation for an application with rails.
Any subject you plan on taking on?
Kind regards
Aerpe
holy donkey, finally get it to work but with the additional following:
1. delete the id field in categories_products table
2. add this: @expense.tags = Tag.find(params[:tag_ids]) if params[:tag_ids] to update action
I don't know why I need step 2 to make it work...I thought the update_attribute will do, but it didn't. Can someone tell me why?
@Lucas - thanks for this 'semi' fix. I've been having problems with for the last day - no one else mentioned having problems so I thought my copy paste skills needed tuning...
I also have a layout :switch_layout (which is serves up a different layout depending on who's logged in) in my controller which is sending this Railscast off the tracks.
From what I've read of the above comments (Ryan, Joe and Geoffrey) - it would be cool to see a sudo mime type used and also a refactored auto_complete plugin...
Anyway, thanks for the screencast :D
I have no idea why everyone else gets this to work. I followed it step by step by I couldn't. Are you supposed to create three tables: Categories, Products, and Categories_products? And I tried something like product:references when generating a scaffold and it produces product_id instead of product_ids...the railscast should provide more information on this.
Thanks for the screencast, it's great and very useful.
When I was trying this, I found a problem with my controller: It wasn't generating anything in the "/controller.js" step. So I put the following code in the end of the method:
respond_to {|format| format.js}
When I tested it, I was given a 'Template is missing' screen, asking for an 'layouts/application.js.erb'.
So I realized my controller has a unremoved "layout 'application'" line in it.
After I removed it, it all worked.
I don't know what really happened, but it may generate some headache for people who'd really need the "layout 'application'" line.
fixed...sorry for the spam
before_filter :my_authenticate , :except => [:index, :show]
woop...
I keep getting a major loop happening between the before_filter in my root controller...and the app controller authorize function, because if the filter chain is halted due to the admin not being logged in, the redirect is back to my root controller, which then calls the authorize function from the before filter...thus creating a loop...
so to remedy...this, from authorize
I redirect to the /sessions/new which is fine...but now every route /url is redirected there ... even though I have an :except in my before filter
any ideas?
cheers
dion
@Dan, Here's what my routes file looks like:
map.resources :products, :categories
This generates the formatted_catgories named route which should do what you want. I didn't need a respond_to block in my controller, but if you're not using Rails 2 you will need one.
gracias. es posible ajustarlos para dos combobox mas?
saludos
por favor me dicen cuales serian las rutas routes.rb
me genera el siguiente error:
ActionController::RoutingError (No route matches "/javascripts/dynamic_states.js" with {:method=>:get}):
Ryan,
Thanks for the help, adding an additional virtual attribute and doing the find_or_create in a callback worked perfectly.
Thanks James; finally got it working! I had assumed I didn't need it since the file was index.js.erb instead of index.html.erb. Thanks for the help.
@Dan - You'll probably want to add "format.js" to your respond_to block in your categories controller.
Ryan, great railscast as always, but I have a question. When I enter "categories.js" into my browser to display the list of autocomplete items, I get "No route matches '/categories.js' with {:method=>:get}". I don't think I have my routes set up properly. Did you add any routes besides the default ones? I'm not well versed in routing yet, so I'm not sure what I need to add.
How would this be different if you wanted to map two virtual attributes to one real attribute? In this example, how would the code change if your database stored full_name and you wanted separate virtual attributes for first_name and last_name?
@James, the model will need access to the parent_id somehow, so I recommend creating a 2nd virtual attribute which sets an instance variable.
The problem is there's no way to guarantee the parent_id attribute will be set before the category_name. To solve this, you can use a callback to handle the category setting. Check out this code.
http://pastie.caboo.se/184873
You may want to email me if this doesn't solve your problem: ryan[at]railscasts[dot]com
great tips...
I added logged_in? to admin? to stop nil object errors...
def admin?
logged_in? && current_user.login == "admin"
end
Ryan,
Sorry I wasn't clear.
When you create a setter method, they can only have one parameter (the value of the virtual attribute). How do you include other values from your controller in these setter methods?
def category_name=(name)
self.category = Category.find_or_create_by_name_and_parent_id(name, parent_id) unless name.blank?
end
Can't seem to figure out how to include additional variables (parent_id in this case) in these setters to keep them from being found if they 'belong_to' a different parent.
Same thing would occur if you were trying to tie a model object to "current_user".
Thank you for the great webcasts, Ryan. FINALLY some tutorials that work!
@James, I'm not exactly sure if I understand your question. However, you could create a second virtual attribute for the bucket_id, save it to an instance variable, and use that in the category search.
@Geoffrey, I agree. I think they extracted this out into a plugin for a reason. I would love to see an updated version of it which flows better with modern techniques (such as REST).
@Bryan, it's a custom snippet I made. I use a lot of custom snippets and they are pretty easy to make. I recommend looking into it and developing your own library of snippets.
[Long time listener, first time caller...] Ryan, I swear I saw you type "field.{tab}" to trigger a snippet of a formated label+field. Is that a stock TextMate function or something you wrote for yourself?
Great screencast, but it really exposed how hackish the autocomplete plugin is.
Why isn't it a regular form builder like the other form fields? Customizing it shouldn't be so awkward (i.e. passing raw Javascript). Why can't the controller method use the virtual attribute on the model?
To be honest, using unobtrusive Javascript seems more straightforward than jumping through Ruby to accomplish this.
There is a real opportunity for someone to refactor this plugin and become a hero!
This is just what I was looking for - thank you, thank you.
Hey Ryan,
This may be beyond the scope of this tutorial, but how do you handle setting virtual attributes that are scoped to another model? Let's say you want to have several categories that belong to a bucket, and want to find_or_create_by_name_AND_BUCKET_ID. You can't pass the bucket info to the virtual attribute in the model. This question is much more about virtual attributes than auto-complete, but I've had it come up a lot in other situations as well. Thanks!
Oh, I forgot to mention that the config file doesn't get loaded until the first time you try to access a setting (lazy loading). If you don't fetch a config setting value with Configuration.xxx it adds zero overhead. Ruby gives you this for free.
@Joe, good points. The more I think about it, a custom mime type would probably be a preferred solution here over using js. As mentioned above, ideally I would like to return javascript here with RJS, but AFAIK the plugin/scriptaculous doesn't support this. Someone please correct me if I'm wrong.
I hadn't thought about the escaping issue. I'll change the code in the show notes. Thanks!
This is a pretty common app configuration pattern but for me it needs some rubification. I do pretty much the same thing but don't use a constant Hash, rather a class to manage the settings:
In lib/configuration.rb:
class Configuration
@@settings = YAML::load_file('config/myconfig.yml')[RAILS_ENV]
class MissingConfigOptionError < StandardError; end
def self.method_missing(key)
raise MissingConfigOptionError("#{key.to_s} is not in the config file") unless @@settings.include?(key.to_s)
@@settings[key.to_s]
end
end
That's it, there is nothing else to do. Create a config/myconfig.yml file with all the same stuff as in this railscast:
In config/myconfig.yml:
development:
foo: "bar"
production:
foo: "yummy"
To fetch and use the settings anywhere in your code, model, controller or view do:
x = Configuration.foo # => "bar"
Customize to suit. Maybe store the settings with mem_cache in a production environment and have a rake task to reload it or handle missing config options differently with perhaps a reasonable default (not a good idea IMHO) or add a test for the existence of a given key.
You could also make this a plugin without a problem by putting it in vendor/plugins/my_config_hotness/init.rb instead of lib/configuration.rb. Six and one half dozen.
Enjoy.
PS. This is not my idea. Someone a lot smarter than I put me on to it but went even further to allow for nested groups:
development:
foo: "bar"
bar:
foo: "yummy"
x = Configuration.bar.foo # => "yummy"
But I can't for the life of me find the page where I saw it done and I guess I failed to bookmark it :(
Retried this tutorial after setting up restful_auth as in episode 67.
I get the error when trying to execute a login using my openid:
Unknown action
No action responded to show
Something is getting confused with the routing within the controller and is trying to call the show action. Anyone run into this or find a work around?
Hey Ryan,
I love RailsCasts but I have two comments around this particular episode:
1) I think that using "js" for the request really is wacky. The content type of the response is "text/javascript" indicating that the content really *is* javascript. Perhaps this is a place to register a new psuedo mime type?
2) You are probably open to escaping bugs when you are passing the search parameter through. What happens with your sample app if you type "?foo=bar" into your category box. You need to url encode the value.
Change element.value to encodeURIComponent(element.value).
The prototype way would be to replace the entire :with value with:
Object.toQueryString({search: element.value})
Wow this was a critical failure for me, but I can't see where I went wrong. I tried using the openid railscast on top of the restful_authentication using the stateful engine.
Everything seemed to work until I tried logging in. The engine went out to my openid provider and when control was passed back to my app it was looking for session/show.html.erb.
It never seems to run open_id_authentication.
Hi Ryan,
Just wanted to say thanks for this awesome screencast, and for all the others on your site. It's great to see an enthousiast like yourself sharing knowledge with the rest of the community.
You screencasts make my monday-mornings considerably more pleasant ;-).
How would one adapt this for creating new "products" and not just adding because it does not seem to work.
Nice, Ryan.
This was a good one.
Your best railscast to date! Thanks for digging into the details of auto-completing virtual attributes, it is just what I needed!
@Mourad, great questions, I was wondering if someone would bring this up.
It would be really nice if the auto_complete plugin supported returning javascript (with rjs) instead of a simple HTML list, this way I could provide further instructions for modifying the functionality, however, AFAIK the plugin doesn't support this. Since the HTML list is so closely tied to javascript I find it acceptable to use the "js" extension.
As for your 2nd question, this is more difficult. You could pass a query parameter as Carlos mentioned. Alternatively you could create a new mime type, perhaps call it "autocomplete" and make an "index.autocomplete.erb" template. This should work, but I'm not sure if it's a best practice.