@MissingHandle it's a delicate balance, but I find myself doing things like this:
Given the following users:
| first name | Chucky |
| last name | Myers |
When I am viewing the user index
Then I should see "Chucky Myers" in the "Users" section
Where 'in the "Users" section' is a way of scoping to a portion of the page (you can see our scoping steps in this gist: https://gist.github.com/894947)... This helps us only couple to the things that we need to see on the page in order to make effective assertions.
Again, it's a delicate balance that's more art (peppered with science) than anything.
@moabite, @tomrossi - I've stopped testing things via assigns entirely and check for the specific things I expect to see in the UI via integration tests (by interrogating request.body using a higher level encapsulation like RSpec/Capybara or Cucumber). For the most part I only use controller tests for unit testing 3rd party API integration or things I can't test via Selenium (like testing around Flash), and don't do any functional testing of my controllers.
To make sure I understand, what you are suggesting is that in the functional test to only check the result in general terms, e.g. 'assert_response :success' or 'assert_redirected_to some_path' etc depending on the expected result, and in an integration test look through the response.body for detailed content?
I just tried out the pattern (instead of the gem) to get a feel for it, as you showed in the first half. And I used the load_method option on filter_access_to. It cleaned the controller up beautifully!
Also - if you want your controller specs to pass, I think you need to make those private methods before_filters on your controllers. But, not really understanding action caching, would the before filters negate the benefit of it?
@rbates thanks for the coverage! For more background on what motivated me to start using this pattern and subsequently encapsulate it as a gem, check out this post: http://blog.voxdolo.me/a-diatribe-on-maintaining-state.html
@moabite, we tend to use integration tests for that sort of thing at Hashrocket. Asserting against the content of an instance variable in your tests means you're coupling with a specific part of an implementation. Interrogating the UI for the presence of an expected element (likely the name of a user or users in this case), allows you to only assert things you care about.
@John , I had the same problem as you did, NoMethodError (undefined method `find' for Symbol:Class):
I figured out the problem, eventually.
You need to change the authentication from find_or_create_by_xx to just find_by_xx , as you aren't creating it until the if statement. It's fairly easy to miss this in the railscast, it happens very quickly.
I've tried a similar thing (without decent_exposure) and I find it a bit confusing sometimes. For example, when you have an edit action where you can edit any article, and a show action where only certain articles can be accessed.
Then you end up calling things like "public_article" in the view (similar to Ryan's example in the cast). That means there's some coupling between the view and the responsibility to decide whether an article can be accessed in a certain action or not.
So far I'm not used to it and prefer the approach where the view receives an article and doesn't care about where it came from, but I'll give it another try sometime.
I definitely like the approach.
Because "@" should be used for addressing people ;).
@miguelsan,@tio: Agree with you, one should not even have to call expose for RESTful resources. I would rather prefer to have to tell the controller when *not* to expose or when the model name and controller name do not match. We could have nested resources exposed automatically too, by inspecting the routes.
@Ryan: I am confused about the default behavior of the expose(:comment) in the nested resource example. I understood, that you left it as the default because there was no id param. Would that not have worked too? I mean by delegating to the scope of :comments, which was article.comments, would that not have yielded article.comments.find(param[:id]) ?
Cheers
Robert
After reading the somewhat cool review of The Rails 3 Way at RubyInside, where the author complains that a "curious technique called Decent Exposure is pushed as a way to never use instance variables in your views", I knew I had to try it.
And right on cue you do a great episode on DecentExposure. Works great and although I didn't A/B it against standard Rails controller methods, it seems quite snappy. Thanks for your superb work.
@Nate, I am guessing any other speed/memory differences would be minimal. After the methods are defined (happens when class is loaded) it should be quite fast. But I haven't done any benchmarks.
@MissingHandle, I don't know about decl_auth, but I hope to get support for this into CanCan 2.0. You can keep track of the issue here.
I got inmediately excited and was loving every second of this video. I can recognise that DecentExposure is a pattern in itself, and a wonderful one, it should make it into Rails core!
I've been exercising a similar practice manually for a while now and I can confirm it really makes controllers more concise and readable in 99% of the cases. Good to know it has been wrapped up as a Gem now.
Really helpful.
I highly recommend some kind of a request system for future episodes.
One feature I couldn't figure out is replacing the ugly javascript alert-box on the delete-action with something like modalbox or similar. Google wasn't much of a help here.
Anyway keep more episodes coming.
On a sidenote, why don't you use flattr?
Awesome job Ryan, as per usual. You've been my #1 rails instructor since day one.
I think I found a minor issue. I don't think that your override of create actually works, because 'create' in the Devise RegistrationController calls 'sign_in_and_redirect', and presumably breaks out of the method. So the omniauth session cookie never gets cleared. This wouldn't really matter, except that I need to kick off a couple of background jobs at this moment (between sign up and confirmation). So I'd love to know how to go about overriding the RegistrationController.
Thanks again!
I only get error messages. 1 for 0 argument error when I try to pass anything on page. per method doesn't work claiming something about can't assign per to fixnum 1.
When you have time, could you include Ben Greene's helper (or some similar snippet) in your code post? It would save Rails 3 users a lot of time hunting down why their code displays as "Shoppery - - Mozilla Firefox" in the title
Couple of questions - firstly, I'd like to reiterate gnagno's question of somehow doing a find_or_create_by kind of thing, so that you can add new items at the same time.
Second question - how do you make this work for multiple fields on the same form - e.g. Books might have many authors, categories, publishers (bad examples - but you get the point).
Also - for tidying up the json output, isn't it as easy as adding:
@authors = Author.where("name like ?", "%#{params[:q]}%").select(['name', 'id'])
In the past I used Cucumber with Capybara and Selenium to test my javascript. It was just like the setup in this episode, but then with Cucumber instead of Rspec. Selenium also started Firefox automatically to test the javascript. But I was wondering how I could test this on a server instead of my own machine. I mean most servers don't have a GUI and don't have Firefox installed.
Anyone know if I can test javascript also on a server using Selenium and Firefox (or another browser)?
Ryan I found this CanCan through your great Railscasts. I really enjoy them! Thanks!
Do you know if anyone has used CanCan with a 3 Model setup, like Users, Roles and Resources? User has many :roles and many :resources through :roles. Resource has many :roles and has many :users through :roles. Roles has BT to user and resource.
Seems like another layer to me.
Insights/thoughts would be so appreciated. Thanks Again!
I am also interested in the use reset_session - Should it be used just after login to change the session id. (The rails security guide mentions it as a way to prevent Session Fixation). Is there any issues\problem with using it?
Excellent screencast Ryan. I love the beautiful interfaces of tokeninput and the clean implementation you have shown. Thank you for sharing this.. gem :)
@MissingHandle it's a delicate balance, but I find myself doing things like this:
Given the following users:
| first name | Chucky |
| last name | Myers |
When I am viewing the user index
Then I should see "Chucky Myers" in the "Users" section
Where 'in the "Users" section' is a way of scoping to a portion of the page (you can see our scoping steps in this gist: https://gist.github.com/894947)... This helps us only couple to the things that we need to see on the page in order to make effective assertions.
Again, it's a delicate balance that's more art (peppered with science) than anything.
@stephen, how do you keep those integration tests from becoming brittle?
@moabite, @tomrossi - I've stopped testing things via assigns entirely and check for the specific things I expect to see in the UI via integration tests (by interrogating request.body using a higher level encapsulation like RSpec/Capybara or Cucumber). For the most part I only use controller tests for unit testing 3rd party API integration or things I can't test via Selenium (like testing around Flash), and don't do any functional testing of my controllers.
I'm wondering like @moabite how you adapt your functional tests. I typically test the state of controller instance variables using the assigns method.
Making controllers downright anorexic.
Awesome episode as always, was wondering how you will redirect back to your rails3 app after you have request processing inside the sinatra app.
Thanks!
@Stephen Caudill, Thank you for the response.
To make sure I understand, what you are suggesting is that in the functional test to only check the result in general terms, e.g. 'assert_response :success' or 'assert_redirected_to some_path' etc depending on the expected result, and in an integration test look through the response.body for detailed content?
For declarative_authorization:
I just tried out the pattern (instead of the gem) to get a feel for it, as you showed in the first half. And I used the load_method option on filter_access_to. It cleaned the controller up beautifully!
Also - if you want your controller specs to pass, I think you need to make those private methods before_filters on your controllers. But, not really understanding action caching, would the before filters negate the benefit of it?
@rbates thanks for the coverage! For more background on what motivated me to start using this pattern and subsequently encapsulate it as a gem, check out this post: http://blog.voxdolo.me/a-diatribe-on-maintaining-state.html
@moabite, we tend to use integration tests for that sort of thing at Hashrocket. Asserting against the content of an instance variable in your tests means you're coupling with a specific part of an implementation. Interrogating the UI for the presence of an expected element (likely the name of a user or users in this case), allows you to only assert things you care about.
To answer my own question, here's one possibility:
assert @controller.users.size > 0
Not sure if I like it. Actually, pretty sure I don't.
The first regression test I ran after changing an existing controller over to using this failed on:
assert_not_nil assigns(:users)
Since instance variables aren't being set, clearly assigns() will always return nil.
examining 'users.size' in the test returns 0.
Visually examining the response.body, I can see that it is correct. Any ideas for a replacement assert?
@John , I had the same problem as you did, NoMethodError (undefined method `find' for Symbol:Class):
I figured out the problem, eventually.
You need to change the authentication from find_or_create_by_xx to just find_by_xx , as you aren't creating it until the if statement. It's fairly easy to miss this in the railscast, it happens very quickly.
I've tried a similar thing (without decent_exposure) and I find it a bit confusing sometimes. For example, when you have an edit action where you can edit any article, and a show action where only certain articles can be accessed.
Then you end up calling things like "public_article" in the view (similar to Ryan's example in the cast). That means there's some coupling between the view and the responsibility to decide whether an article can be accessed in a certain action or not.
So far I'm not used to it and prefer the approach where the view receives an article and doesn't care about where it came from, but I'll give it another try sometime.
I definitely like the approach.
Because "@" should be used for addressing people ;).
@miguelsan,@tio: Agree with you, one should not even have to call expose for RESTful resources. I would rather prefer to have to tell the controller when *not* to expose or when the model name and controller name do not match. We could have nested resources exposed automatically too, by inspecting the routes.
@Ryan: I am confused about the default behavior of the expose(:comment) in the nested resource example. I understood, that you left it as the default because there was no id param. Would that not have worked too? I mean by delegating to the scope of :comments, which was article.comments, would that not have yielded article.comments.find(param[:id]) ?
Cheers
Robert
After reading the somewhat cool review of The Rails 3 Way at RubyInside, where the author complains that a "curious technique called Decent Exposure is pushed as a way to never use instance variables in your views", I knew I had to try it.
And right on cue you do a great episode on DecentExposure. Works great and although I didn't A/B it against standard Rails controller methods, it seems quite snappy. Thanks for your superb work.
it works great but with a simple and small search what if we have an Array as returned on our search method
@Nate, I am guessing any other speed/memory differences would be minimal. After the methods are defined (happens when class is loaded) it should be quite fast. But I haven't done any benchmarks.
@MissingHandle, I don't know about decl_auth, but I hope to get support for this into CanCan 2.0. You can keep track of the issue here.
https://github.com/ryanb/cancan/issues/#issue/317
Hilarious cite: " ... and that works, because it does ...".
Thanks for this great screencast!
I wonder how seamlessly you could get this to work with declarative_authorization...
and +1 to @Nate Bird's question.
I'm with @miguelsan and @Tilo. My spontaneous thought too was that this should be in Rails core.
Much cleaner + leaner than sharing instance variables!
This should be Rails default behavior!
Awesome video! I've been watching Railscasts for a long time now and while everything you do is great, this vid is double great.
Keep up the great work.
I got inmediately excited and was loving every second of this video. I can recognise that DecentExposure is a pattern in itself, and a wonderful one, it should make it into Rails core!
Are there any speed or memory benefits to using methods rather than instance variables other than the more robust action caching?
It definitely does clean things up quite a bit. It kind of makes me think of the actions as belonging to a ghost town though with nothing in them.
Soon you won't have to write any code and rails will do it for you lol.
I've been exercising a similar practice manually for a while now and I can confirm it really makes controllers more concise and readable in 99% of the cases. Good to know it has been wrapped up as a Gem now.
What happens to the current Authorship records when author_ids is reassigned? Are they destroy? Or are they just nullified?
If you haven't got an attribute called "name" add this to your to your model.
def as_json(options={})
{ :id => self.id, :name => self.full_name }
end
where full_name is the attribute you want to search.
and then in the controller add
format.json { render :json => @authors }
Really helpful.
I highly recommend some kind of a request system for future episodes.
One feature I couldn't figure out is replacing the ugly javascript alert-box on the delete-action with something like modalbox or similar. Google wasn't much of a help here.
Anyway keep more episodes coming.
On a sidenote, why don't you use flattr?
Hey guys! I'm the author of jquery-tokeninput.
Just wanted to let you know about a few fixes I've implemented in the latest version:
See more in the changelog here:
https://github.com/loopj/jquery-tokeninput/blob/master/ChangeLog.md
Thanks for using the plugin!
For what it's worth, I solved my problem by adding an after_create method to my user model.
@Ryan
Can you let me know to implement this in a Non-RESTful environment using Has Many Through?
Thanks,
Shashank
Awesome job Ryan, as per usual. You've been my #1 rails instructor since day one.
I think I found a minor issue. I don't think that your override of create actually works, because 'create' in the Devise RegistrationController calls 'sign_in_and_redirect', and presumably breaks out of the method. So the omniauth session cookie never gets cleared. This wouldn't really matter, except that I need to kick off a couple of background jobs at this moment (between sign up and confirmation). So I'd love to know how to go about overriding the RegistrationController.
Thanks again!
Ahh, the same happens if I want to swap the dates of two nested Frobs.
Anyone got this working on Mongoid?
I only get error messages. 1 for 0 argument error when I try to pass anything on page. per method doesn't work claiming something about can't assign per to fixnum 1.
Thanks for all the Railscasts, Ryan!
When you have time, could you include Ben Greene's helper (or some similar snippet) in your code post? It would save Rails 3 users a lot of time hunting down why their code displays as "Shoppery - - Mozilla Firefox" in the title
Hey Ryan, love your work.
Couple of questions - firstly, I'd like to reiterate gnagno's question of somehow doing a find_or_create_by kind of thing, so that you can add new items at the same time.
Second question - how do you make this work for multiple fields on the same form - e.g. Books might have many authors, categories, publishers (bad examples - but you get the point).
Also - for tidying up the json output, isn't it as easy as adding:
@authors = Author.where("name like ?", "%#{params[:q]}%").select(['name', 'id'])
at the end?
Thanks.
Oops. I guess it's because you're getting a literal string from the params and not an array. Carry on! Nothing to see here!
Can't you use the association methods author_ids and author_ids= instead of making a custom author_tokens accessor?
In the past I used Cucumber with Capybara and Selenium to test my javascript. It was just like the setup in this episode, but then with Cucumber instead of Rspec. Selenium also started Firefox automatically to test the javascript. But I was wondering how I could test this on a server instead of my own machine. I mean most servers don't have a GUI and don't have Firefox installed.
Anyone know if I can test javascript also on a server using Selenium and Firefox (or another browser)?
Ryan I found this CanCan through your great Railscasts. I really enjoy them! Thanks!
Do you know if anyone has used CanCan with a 3 Model setup, like Users, Roles and Resources? User has many :roles and many :resources through :roles. Resource has many :roles and has many :users through :roles. Roles has BT to user and resource.
Seems like another layer to me.
Insights/thoughts would be so appreciated. Thanks Again!
Great episode!
I am also interested in the use reset_session - Should it be used just after login to change the session id. (The rails security guide mentions it as a way to prevent Session Fixation). Is there any issues\problem with using it?
Does the example still show how one would do this with Rails3?
Strange that you need to add the Crossdomain:false portion as that's the default for the script. Cool stuff either way Ryan.
Unfortunately, you can't change the order of the chosen items, so it wouldn't actually be useful for an authors list in many cases.
I don't want to spam but this is a great related web site that lets you create new rails templates.
http://railswizard.org/
this plugin should definitely be added to the wizard
Great episode!
Although, I too would love too see "Remember me" functionality.
Also, any reason you wouldn't use 'reset_session' instead of 'session[:user_id] = nil'?
Excellent screencast Ryan. I love the beautiful interfaces of tokeninput and the clean implementation you have shown. Thank you for sharing this.. gem :)
I got it!
I didn't have name column in model.