I am using the Rails 1.2.3, and follow the instruction that clouder said. But it looks like I still miss the dynamic_states.js, even I have correct output in http://localhost:3000/javascripts/dynamic_states. Could someone give any suggestion? Thanks!
It is amazing to me how cool the rails community is. I have wondered over and over how Rails can have so many useful features... and your comment proved why: because upon seeing a problem, instead of accepting it, we try to solve it.
Sweet.
Please do submit a ticket or update to rails too see a feature like that be added to future releases.
@Ryan: Thanks for introducing another feature in such a concise manner.
@Rymaï, thanks, I added the link to the show notes.
@Tom, I'm a little surprised partial updates are on by default in new 2.1 projects as well. I could see it causing some tricky bugs.
Your idea of being able to change this setting on a case by case basis makes sense. I look at it as more of a performance improvement which can be applied if you have a model with BLOBs or index columns. I recommend making a post on the rails core mailing list about this if there isn't one already.
I am a little dubious as to the wisdom of having partial updates as a default, however. Sure, when a record contains BLOBs or many large text fields it may make sense. But in most cases I'll bet the benefit is negligible.
@Rymai's link already points out the new risk of data consistency issues (it's worth reading!).
It seems to me that model-level syntax to turn partial updates off or on conditionally would be smarter. For example:
Anyway, it's worth thinking about the consistency issue. The case may be unlikely, but it introduces a data-level issue in the name of dubious claims of performance increase.
This was a really great Railscast highlighting probably my favorite new feature of Rails. Those SQL queries are great! Can't wait to take advantage of this...
That said--is the after_update callback absolutely necessary?
It looks to me like it works if you call task.save from within Project.existing_task_attributes= (right after the line "task.attributes = attributes").
@Arya: when you set Time.zone, this zone is stored in the Thread.current hash, so there's no possibility that the zone set for one request will leak into another.
@David: regarding TimeZone#to_s, I'm actually inclined to change this back to showing GMT -- I've started a thread about this on the Rails Core list.
@Mathijs, currently one named scope can't use another named scope, but I think many people are wanting this feature. There's already a couple tickets along this line:
A solution I use at the moment is this, but I'm not fully satisfied with it yet, so any comments are very welcome...
class Product << ActiveRecord::Base
belongs_to :category
named_scope :in_free_for_all, lambda {{ :conditions => {:category_id => Category.free_for_all.map(&:id)} }}
end
Which will do 2 queries, which is default for :include in rails 2.1 anyway I believe. I could use :select => 'id' to speed up the query a bit since I only need id's, but in the end, it would be nicest if rails could somehow write a subquery for it. Along the lines of:
I've been using the has_finder gem on which this is based for some time now, and it's really useful. One thing I still struggle with has to do with using named_scopes between models in associations.
In your example you are using 'visible' which is just a simple attribute. What if category had more advanced attributes, like hidden, private, public, read_only and stuff like that. Say Category already has some named_scopes, for example 'free_for_all' which means a category is public but not read-only.
Now it would make sense (maybe not in this example but in general) to be able to use these named_scopes in the Product model, not on instances only, but in named_scopes themselves as well. So Product.in_free_for_all can just use the 'free_for_all' scope in Category. Otherwise you would have to define this 'logic' twice, which isn't very DRY. I hope my explanation is clear enough :)
Of course I know that I could also use Category.free_for_all.map(&:products), but then all products get fetched right away which means sorting them (and other nice stuff finders have) will have to be done in ruby instead of in the DB.
I have been trying to do some of the same things using with_scope and some of them were not working correctly (I have an idea why now after watching your screencast) and now it will be so much easier to do what I was trying to do. Thanks for telling me about this new feature!
I have been using edge rails as of late just for this amazing functionality. In addition to the new level of dynamism this feature adds to Rails, it also adds such simplicity.
My favorite has to be simply the Model.all, Model.first, and Model.last shortcuts... cleans up my controllers for sure!
Man, with each new update of Rails, so many new possibilities for refactoring!
In the before filter, you change the value of Time.zone, does it automatically get changed back at the end of the request? So if a logged in user with their time zone set to a Russian TZ logs in and then an non-logged in user goes to the site immediately after that, do they see the previous user's time zone?
@Adam, I doubt you can use this with acts_as_ferret, but other plugins which end up calling "find" should work. For example, will_paginate just adds a custom paginate class method. You can append this to the search. (Untested).
Product.recent.paginate(:page => 1)
@Foo, this definitely lowers the need to create custom "self.find..." methods. However I would hesitate getting too fancy here. If you have a complicated search which needs to dynamically build up the conditions based on the passed parameters, then I wouldn't use named_scope for this.
@Mauricio, to echo Clemens, I doubt performance will be much of an issue. There's probably a slight overhead on the ruby side to get the scoping stuff figured out, but beyond that it seems efficient.
I have run into this error frequently. Two things you can do to fix it:
1. If it is an option for you, run 'sudo gem cleanup' to clean out old versions of rails gems.
2. Look for code in your app that extends ActiveRecord. The "Oracle find_rows" patch and the Oracle "prefetch_primary_key" patch are two common culprits. Check environment.rb for anything that messes with AR.
Is your project open-source or sharable? If so, send me a pointer to your repository and I'll take a look.
Foo:
I would guess that yes, pretty much all finds will be replaced by named scopes in near future. Think of your own experience and some of Ryan's previous screencasts: You shouldn't really have finds anyway as soon as you're using find conditions. Like, say, you'd have find_all_activated instead of find(:all, :conditions => "activated_at IS NOT NULL")
Mauricio:
As far as I can see in the source, there's not too much going on metaprogramming-wise. So I guess it's safe to say that scoping doesn't cause performance issues. And as Marcel points out, named scopes produce really clean and efficient SQL - so no problem there either. Plus, scopes have existed before in Rails (with_scope - see here: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001416).
Ryan:
Best episode so far, in my opinion. And not only because it was so long! ;-)
Wow, this looks really cool. I especially like the efficiency of the generated SQL query when you concatenated the named_scopes. Really amazing and definately a great feature for bigger, data-filled applications.
I am having problems with this one, where my .js.rjs template isn't being used, and instead there's an error saying that Rails apparently expects a .js.erb template file instead.
anyone know if there is a plugin for git that i can use for eclipse? i've gotten used to right-click -> commit and team -> revert etc. and doing those things on the command line feels like a step backwards...
I'm not sure what the original reason was for this, but it's made my life a lot easier when dealing with big datasets. For example I have an app with a cache of geographic information with 100K records which is a pain to constantly restore. While I'm still in development I frequently make changes to earlier migrations and just re-run them on my local, leaving the big dataset intact, without cluttering up the app with pre-production migrations.
I used to do this by going to the console and instantiating the class directly, but that was a pain.
@QuBit thanks for bringing this up! I was originally planning on mentioning it in this episode, but I couldn't think of a good scenario on when I'd want to do this.
If I need to remove an older migration, I much prefer creating a new migration which does this, that way it applies on deployment and for any other developers who may have the code.
Does anyone know the reasoning behind the db:migrate:up/down additions?
Great webcast, as usual. I following along successfully until the sequence that moves the fields_for statement to the partial and creates the link_to_function statement in the view. Now I an RJS error. Under IE6 I get an unspecified object error; under Firefox I get "Reference error: Insertion is not defined". In the interpolated javascript statement, all the html brackets are translated to escape sequences, e.g. <a href="#" onclick="try {
new Insertion.Bottom("tasks", "\n \u003Cp\u003ETask: \u003Cinput id=\"project_task_attributes__name\" name=\"project[task_attributes][][name]\" size=\"30\" type=\"text\" /\u003E \u003C/p\u003E\n\n");
} ...
I'm using Rails 2.0.2 and Ruby 1.8.6. Can someone suggest where I may have lost my way or how I can go about tracking down the problem, please?
Rails has extended the Time class so you can do to_s(:db). See also http://api.rubyonrails.com/classes/ActiveSupport/CoreExtensions/Time/Conversions.html
Instead of using :db which also includes seconds (which seems a bit too much) you could also try :long. Or make up your own string like to_s("%Y-%m-%d %H:%M")
For those who has troubled using has_many :through needs custom setter methods or have problem with creating new records..
i find the solution to make tag_ids= method to work with new records..
http://pastie.org/201438
So erm... $ script/generate migration update_users of course.
So erm..... how do you actually create a timestamped migrations file? I always did File > New and gave it a name myself.
Thanks for the rapid-fire onslaught of episodes lately. The recent Rails 2.1 episodes have been great. Keep on chuggin.
I am using the Rails 1.2.3, and follow the instruction that clouder said. But it looks like I still miss the dynamic_states.js, even I have correct output in http://localhost:3000/javascripts/dynamic_states. Could someone give any suggestion? Thanks!
@Pascal (me):
Forget it... I was running my project on Rails 2.0.2. I have to migrate my projet to 2.1 doh.
Hi, I have this error on server startup:
/Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:1532:in `method_missing': undefined method `partial_updates=' for ActiveRecord::Base:Class (NoMethodError)
I'm using Rails 2.0.2.
Any ideal?
Thx
@Tom:
It is amazing to me how cool the rails community is. I have wondered over and over how Rails can have so many useful features... and your comment proved why: because upon seeing a problem, instead of accepting it, we try to solve it.
Sweet.
Please do submit a ticket or update to rails too see a feature like that be added to future releases.
@Ryan: Thanks for introducing another feature in such a concise manner.
@Rymaï, thanks, I added the link to the show notes.
@Tom, I'm a little surprised partial updates are on by default in new 2.1 projects as well. I could see it causing some tricky bugs.
Your idea of being able to change this setting on a case by case basis makes sense. I look at it as more of a performance improvement which can be applied if you have a model with BLOBs or index columns. I recommend making a post on the rails core mailing list about this if there isn't one already.
These new features rock, as does your RailsCast.
I am a little dubious as to the wisdom of having partial updates as a default, however. Sure, when a record contains BLOBs or many large text fields it may make sense. But in most cases I'll bet the benefit is negligible.
@Rymai's link already points out the new risk of data consistency issues (it's worth reading!).
It seems to me that model-level syntax to turn partial updates off or on conditionally would be smarter. For example:
partial_update :unless => { :termination_date.changed? || :eligibility.changed? }
or
full_update :if => {
:termination_date.changed? || :eligibility.changed? }
Anyway, it's worth thinking about the consistency issue. The case may be unlikely, but it introduces a data-level issue in the name of dubious claims of performance increase.
Thank u so much for useful casts :)
Another useful webcast, as usual.
Thanks Ryan.
Wow! Truly awesome new feature!
Now I just need to get Hostgator to update to 2.1....
All installed great, no probs there - but it doesn't log 404 errors. Do you need to hook something in so the exception logger knows about them?
This was a really great Railscast highlighting probably my favorite new feature of Rails. Those SQL queries are great! Can't wait to take advantage of this...
Thanks!
Genius code--thanks so much!
That said--is the after_update callback absolutely necessary?
It looks to me like it works if you call task.save from within Project.existing_task_attributes= (right after the line "task.attributes = attributes").
Is that right?
@Ryan: excellent screencast!
@Arya: when you set Time.zone, this zone is stored in the Thread.current hash, so there's no possibility that the zone set for one request will leak into another.
@David: regarding TimeZone#to_s, I'm actually inclined to change this back to showing GMT -- I've started a thread about this on the Rails Core list.
Thanks for a great post! My attention tends to drift after about 7 minutes, so i'll have to come back and watch again a couple times :)
@Mathijs, currently one named scope can't use another named scope, but I think many people are wanting this feature. There's already a couple tickets along this line:
http://rails.lighthouseapp.com/projects/8994/tickets/57
http://rails.lighthouseapp.com/projects/8994/tickets/223
However, neither of these mention using an association's scope. You may want to add a comment to those tickets about that.
A solution I use at the moment is this, but I'm not fully satisfied with it yet, so any comments are very welcome...
class Product << ActiveRecord::Base
belongs_to :category
named_scope :in_free_for_all, lambda {{ :conditions => {:category_id => Category.free_for_all.map(&:id)} }}
end
Which will do 2 queries, which is default for :include in rails 2.1 anyway I believe. I could use :select => 'id' to speed up the query a bit since I only need id's, but in the end, it would be nicest if rails could somehow write a subquery for it. Along the lines of:
(example, doesn't work)
named_scope :in_free_for_all, :conditions => {:category => Category.free_for_all}
should translate to:
Select * from products where category_id in (select id from categories where public = 't' and read_only = 'f')
Any chance of getting behavior like this in the near future?
I've been using the has_finder gem on which this is based for some time now, and it's really useful. One thing I still struggle with has to do with using named_scopes between models in associations.
In your example you are using 'visible' which is just a simple attribute. What if category had more advanced attributes, like hidden, private, public, read_only and stuff like that. Say Category already has some named_scopes, for example 'free_for_all' which means a category is public but not read-only.
Now it would make sense (maybe not in this example but in general) to be able to use these named_scopes in the Product model, not on instances only, but in named_scopes themselves as well. So Product.in_free_for_all can just use the 'free_for_all' scope in Category. Otherwise you would have to define this 'logic' twice, which isn't very DRY. I hope my explanation is clear enough :)
Of course I know that I could also use Category.free_for_all.map(&:products), but then all products get fetched right away which means sorting them (and other nice stuff finders have) will have to be done in ruby instead of in the DB.
Any 'nice' solutions for this?
Indeed a very cool screencast.
Thanks again for this great episode!
In the revised example I found that I had to use the index option on my fields when I was specifying a to_param method for my equivalent Tasks model
I have been trying to do some of the same things using with_scope and some of them were not working correctly (I have an idea why now after watching your screencast) and now it will be so much easier to do what I was trying to do. Thanks for telling me about this new feature!
Thanks for this episode Ryan, I used your approach soon after watching this to make myself a JQuery UI Tabs Helper :)
http://www.codeofficer.com/blog/entry/ruby_on_rails_jquery_ui_and_tabsrenderer/
looking forward to the next episode!
@Arya, good question. I believe Time.zone does reset when the request ends. I have read this elsewhere. However I honestly have not fully tested it.
I have been using edge rails as of late just for this amazing functionality. In addition to the new level of dynamism this feature adds to Rails, it also adds such simplicity.
My favorite has to be simply the Model.all, Model.first, and Model.last shortcuts... cleans up my controllers for sure!
Man, with each new update of Rails, so many new possibilities for refactoring!
I've been using has_finder for a while now, and it's been a real pleasure to work with. I'm really glad it's made it into rails.
@Ryan:
in Ruby 1.9 you can use -> operator for lambda and put default values, like this:
named_scope :recent, ->(time = 2.weeks.ago) { {:conditions => ["released_at > ?", time]} }
In the before filter, you change the value of Time.zone, does it automatically get changed back at the end of the request? So if a logged in user with their time zone set to a Russian TZ logs in and then an non-logged in user goes to the site immediately after that, do they see the previous user's time zone?
@Adam, I doubt you can use this with acts_as_ferret, but other plugins which end up calling "find" should work. For example, will_paginate just adds a custom paginate class method. You can append this to the search. (Untested).
Product.recent.paginate(:page => 1)
@Foo, this definitely lowers the need to create custom "self.find..." methods. However I would hesitate getting too fancy here. If you have a complicated search which needs to dynamically build up the conditions based on the passed parameters, then I wouldn't use named_scope for this.
@Mauricio, to echo Clemens, I doubt performance will be much of an issue. There's probably a slight overhead on the ruby side to get the scoping stuff figured out, but beyond that it seems efficient.
@Mike:
/Library/Ruby/Site/1.8/rubygems.rb:139:in `activate':Gem::Exception: can't activate activesupport (>= 1.4.1), already activated activesupport-2.0.991]
I have run into this error frequently. Two things you can do to fix it:
1. If it is an option for you, run 'sudo gem cleanup' to clean out old versions of rails gems.
2. Look for code in your app that extends ActiveRecord. The "Oracle find_rows" patch and the Oracle "prefetch_primary_key" patch are two common culprits. Check environment.rb for anything that messes with AR.
Is your project open-source or sharable? If so, send me a pointer to your repository and I'll take a look.
Foo:
I would guess that yes, pretty much all finds will be replaced by named scopes in near future. Think of your own experience and some of Ryan's previous screencasts: You shouldn't really have finds anyway as soon as you're using find conditions. Like, say, you'd have find_all_activated instead of find(:all, :conditions => "activated_at IS NOT NULL")
Mauricio:
As far as I can see in the source, there's not too much going on metaprogramming-wise. So I guess it's safe to say that scoping doesn't cause performance issues. And as Marcel points out, named scopes produce really clean and efficient SQL - so no problem there either. Plus, scopes have existed before in Rails (with_scope - see here: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001416).
Ryan:
Best episode so far, in my opinion. And not only because it was so long! ;-)
Wow, this looks really cool. I especially like the efficiency of the generated SQL query when you concatenated the named_scopes. Really amazing and definately a great feature for bigger, data-filled applications.
Thanks for another great screencast!
What about the performance cost of this "magic"?
Very interesting screencast ! Cool new feature ! Thanks for this tip !
Thanks for these Rails 2.1 tips.
Another great screencast.
Does this essentially make all self.finds obsolete? Will the new conventions be named_scope instead of def self.find_some_criteria?
Wow, and I thought working with the DB couldn't get any easier, but this is seriously cool. Thanks for this excellent overview.
I wonder if these named scopes can be used in conjunction within existing search plugins like acts_as_ferret in one line...
I am having problems with this one, where my .js.rjs template isn't being used, and instead there's an error saying that Rails apparently expects a .js.erb template file instead.
Here's the error I get:
ActionController::MissingTemplate (Missing template javascripts/hide_announcements.js.erb
Here's the line from my routing file:
map.connect ":controller/:action.:format"
And here's my controller action method:
def hide_announcements
session[:announcement_hide_time] = Time.now
end
What could be causing Rails to think that the template file should be an ERB file instead of an RJS file?
Having trouble adding authentication to the exception_logger controller? See:
http://blog.lawrencepit.com/2008/05/07/recording-rails-exceptions/
I've turned this into a rails plugin which also support Chronic. See http://lawrencepit.com/2008/05/19/rails-plugin-validates_as_time/
anyone know if there is a plugin for git that i can use for eclipse? i've gotten used to right-click -> commit and team -> revert etc. and doing those things on the command line feels like a step backwards...
I'm not sure what the original reason was for this, but it's made my life a lot easier when dealing with big datasets. For example I have an app with a cache of geographic information with 100K records which is a pain to constantly restore. While I'm still in development I frequently make changes to earlier migrations and just re-run them on my local, leaving the big dataset intact, without cluttering up the app with pre-production migrations.
I used to do this by going to the console and instantiating the class directly, but that was a pain.
@QuBit thanks for bringing this up! I was originally planning on mentioning it in this episode, but I couldn't think of a good scenario on when I'd want to do this.
If I need to remove an older migration, I much prefer creating a new migration which does this, that way it applies on deployment and for any other developers who may have the code.
Does anyone know the reasoning behind the db:migrate:up/down additions?
Great webcast, as usual. I following along successfully until the sequence that moves the fields_for statement to the partial and creates the link_to_function statement in the view. Now I an RJS error. Under IE6 I get an unspecified object error; under Firefox I get "Reference error: Insertion is not defined". In the interpolated javascript statement, all the html brackets are translated to escape sequences, e.g. <a href="#" onclick="try {
new Insertion.Bottom("tasks", "\n \u003Cp\u003ETask: \u003Cinput id=\"project_task_attributes__name\" name=\"project[task_attributes][][name]\" size=\"30\" type=\"text\" /\u003E \u003C/p\u003E\n\n");
} ...
I'm using Rails 2.0.2 and Ruby 1.8.6. Can someone suggest where I may have lost my way or how I can go about tracking down the problem, please?
I hope time zone support is good in Active Resource for rails 2.1. A lot of bugs existed in 2.0.2.
So the due_at_string that I would have is:
http://pastie.caboo.se/198554
Rails has extended the Time class so you can do to_s(:db). See also http://api.rubyonrails.com/classes/ActiveSupport/CoreExtensions/Time/Conversions.html
Instead of using :db which also includes seconds (which seems a bit too much) you could also try :long. Or make up your own string like to_s("%Y-%m-%d %H:%M")
In Rails 2.1 you can now do migrations much more easier by the rake-tasks db:migrate:up, db:migrate:down, ...
this enables you to specify the version-number (now the timestamp) to migrate a single migration up and down :)
so if you want to delete migration number 15 out of 30 you don't have to rollback all the way or write a new one (31) which revert number 15.
example:
rake db:migrate:up VERSION=20080402122523
found in:
http://ryandaigle.com/articles/2008/4/2/what-s-new-in-edge-rails-utc-based-migration-versioning
lg