Nice new features. I upgraded to Rails 2.1.0 earlier. Really easy. Looking forward to using named scopes. Just need to find a nice bit to use it for.
Would you consider giving feedback on some code I've written if you have 5 mins? I'm new to Rails (3-4 weeks) and I've gotten to the point where I have some untidy spots I can't optimize with my current knowledge. Email sent with this comment. Understand if you're too busy.
Keep up the great work.
Ryan, your screencasts get better as your skill increases. It's a lovely thing to watch.
I've been thinking about your cleaning up the `scope = scope.scoped …` code, so I'll throw out some ideas.
We can definitely use inject() to remove all of the assignments. I also like the idea of staring with the model class itself, instead of an empty scope. Using just that much, we can build something like:
….inject(Product) do |scope, …|
scope.scoped …
end
The tricky part here is knowing what to iterate over. Obviously, the find() parameters are needed, but you also only conditionally apply them. That made me want to try something like:
[ minimum_price.blank?, {:conditions => ["products.price >= ?", minimum_price],
… ].inject(Product) do |scope, (cond, params)|
scope.scoped params unless cond
end
Of course, that's just way too ugly. I haven't come up with the best way to improve that yet though.
Whenever I see code like this I think blocks to clean it up.
I'd look at doing something along the lines of:
Product.scoped({}) do |scope|
scope.conditions "products.name LIKE ?", "%#{keywords}%" unless keywords.blank?
scope.conditions "products.price >= ?", minimum_price unless minimum_price.blank?
end
def scoped(conditions, &block)
....
end
I'll leave it up to everyone else to do the hard work. :)
@James, interesting idea. I hadn't thought of using inject here.
The problem with just starting with Product instead of Product.scoped is that if no further scopes are applied to it then it will simply return Product, not a scope. This means you can't iterate through the result or treat it like an array as you can with a scope.
@Jim, what kind of object is being passed to the block here? It can't be a normal named scope because calling further named scopes upon it returns a new named scope, it doesn't change the existing scope.
This means we would have to set up a new object which gathers up the called methods and then reapplies them to the scope. That solution seems a bit too complicated.
Any idea how this could be combined with will_paginate?
As always: nice screencast Ryan :)
A suggestion for future screencast(s): Tests. I think this is a very interesting topic, because i think there are a lot of programmers out there not testing at all... There are also a lot of testing frameworks, and it all can be a bit confusing at first...
Just a suggestion ;)
Ben
@Matt, since we're using scopes here I *think* you can just call ".paginate" on the resulting scope and it will just work. I haven't tested this yet.
@Ben, good point. Testing is something I hope to cover more in future episodes.
Very interesting.. Thanks again ryan..
Will be interesting to see, if future episodes included testing as part of the vidoe..
I know there is bunch of condition builders out there, wouldn't it cool if your named_scope tutorials end up being a simple condition builder implemented on rails 2.1
One way to remove the duplication is by making a mixin module that you call like this inside of ProductSearch:
searches Product, {
:minimum_price => "products.price >= ?",
:maximum_price => "maximum_price <= ?",
:category_id => "products.category_id = ?"
}
(There is not much room in this comment window). The module code would have this:
scope = klass.scoped({})
non_blanks = scopes.keys.select { |attr| !send(attr).blank? }
...
You can then use inject or a simple each to build up the scope inside of a loop over 'non_blanks'. To support the 'LIKE' statement, it would need to support lambda.
Hey Ryan,
how could you make this OR instead of AND. For example I want to search some products and I want to search that my keyword is in the name OR the description OR the manufacture OR the distributor etc..
I just made one big find statement, and that's working for me... but I'd like to make it OR OR OR and hten have the ands at the end so that I could to the above, and also price below X amount, etc..
Thanks in advance if you have a chance.
I'd drop the named_scope on AR::Base and do this:
# using a class variable here for convenience only...
@@searches = {
keywords => "products.name LIKE CONCAT('%', ?)",
minimum_price => "products.price >= ?",
maximum_price => "products.price <= ?",
category_id => "products.category_id = ?"
}
def find_products
@@searches.inject(Product.scoped({})) { |scope, search| search.first.blank? && scope || scope.scoped(:conditions => search.reverse) }
end
(http://pastie.org/216179)
oops or rather
@@searches = {
:keywords => "products.name LIKE CONCAT('%', ?)",
:minimum_price => "products.price >= ?",
:maximum_price => "products.price <= ?",
:category_id => "products.category_id = ?"
}
etc.
A big issue i've come accross is that performing joins are not merged when using named scopes. In this example where we're doing a complex search, joining to other tables to filter results is really common. Just think of say, tags or particular codes.
Ryan, I noticed you posted in response to:
http://blog.teksol.info/2008/5/26/why-are-activerecord-scopes-not-merged
There aren't any active tickets in lighthouse for this unfortunately.
How to combine this with find_tagged_with from acts_as_taggable_on_steroids??
Just some bits I created after watching this episode:
With the presented conditions scope, you can only pass and array. I liked to pass any possible conditions "variant" (e.g. a hash) along and accomplished this with this little hack:
named_scope :conditions, proc { |*args| h = {}; h.store(:conditions, *args); h }
This way you say e.g.: SomeModel.conditions :some_attribute => value
@Davis:
:conditions => "products.id IN (SELECT taggable_id FROM taggings WHERE tag_id IN(?) AND taggable_type = 'Products'", tag
oops, didn't closed the parenthesis after the subselect
tag is a Tag object which will be turned into an integer in this statement.
I'm having an issue with concatened scopes:
I use Thomas' named_scope :conditions, proc { |*args| h = {}; h.store(:conditions, *args); h }
and I also have many named scopes already defined. I wish I could send a hash to a class method so that parsed arguments are wether sent to the right scope else to the default "conditions" method.
def self.search(*args)
args.extract_options!.stringify_keys.to_a.inject(scoped({})) do |scope, search|
scopes.include?(search.first) ? scope.send(search.first, search.last) : scope.conditions(search.first => search.last)
end
end
But this is really unstable, I can only mix a defined scope and the "conditons" and nothing more.
# this works
User.search( :name => "foo", :city_begins_with => "c")
# this not:
User.search( :name => "foo", :city => "chicago")
# this not either
User.search( :name_begins_with => "f", :city_begins_with => "c")
Any idea ?
<code>
named_scope :published, {:order => "created_at DESC", :conditions => {:published => true}}
named_scope :newest, { :order => "created_at DESC" }
def self.search(params)
params.inject(Video) {|v,val| v.send(val) } == Video.newest.published
end
</code>
Seems to work just as fine, without any need of hacking AR::Base :)
Forgot the call itself,
Video.search(["published","newest"]).
You may also want to check if Video responds_to? the method you're sending and if it's allowed (keep an array of allowed methods or something in the class itself)
The previous code example was really bad since I was so excited, hence a better one follows:
http://pastie.org/231945
Please replace the previous example with this one :)
My bad, seems as if my test cases didn't take into account the joining issues related to named_scopes. The code doesn't seem to work at all. Might be a start though, I'm going to continue digging :(
Alright, finally got it working!
http://pastie.org/231954
Cheers
Ryan,
Love the episode. One quick question: What is the key-combination in TextMate to do the multi-line block select and replace it with "conditions" where it added that on each line?
I don't even know what you'd call that, otherwise I'd probably be able to figure it out.
Thanks!
I would do something like this:
cons = {:keywords=>["products.name LIKE ?","%#{keywords}%"], :minimum_price=>["products.price >= ?",minimum_price], :maximum_price=>["products.price <= ?",maximum_price], :category_id=>["products.category_id = ?",category_id]}
scope = Product.scoped({})
cons.each{|attribute,carray| scope = scope.conditions carray[0], carray[1] unless send(attribute).blank?}
scope
I just built and released a little Rails plugin that uses this technique to easily search your models using a scope. This can be really useful if you want to combine a search with another scope or will_paginate:
class MyModel < ActiveRecord::Base
named_scope :my_scope, { ... }
searchable_on :several, :fields
end
MyModel.my_scope.search_for("search query").paginate( ... )
Get the source at http://github.com/wvanbergen/scoped_search/
One thing I've noticed with these 'scope' methods is that performing count doesn't work if you're selecting from more than one table (eg :from => 'table1, table2') It only uses the first table. It seems I have to use the size method on the returned array since the actual scope is handled properly. Where would I report a bug like this?
Hello, first, thank you Ryan, you are doing a very good job here!
I just want to point out that the link to the "EPISODE 111" LEADS actually to "EPISODE 108".
But many people here are smart enough to get through that issue.
Very useful! However, can you use scopes when you have polymorphic associations? The :include method wouldn't work, so is there anything you can do?
this cleans up the code very nice.
useful screencast again ryan
go on with the show - we love it ;-)
Great tutorial, as usual!
Yes, anonymous scopes work well with will_paginate, as:
scope.paginate(:per_page => 18, :page => page, :order => 'updated_at desc')
Just a small typo:
The notes say scope.conditions where it should be "scope.scoped conditions". I have just used info in the (excellent) screencast but this time I just checked the notes for reference.
Never mind, my memory is dodgy. The syntax in the notes is the refined version. Sorry :-)
Not sure if this will even get looked at after all those spam comments, but here it is:
Is there any way to add a dynamic order to the "find." For example:
def find_products
scope = Model.scoped({:order => sort_order('attribute_1')})
scope = scope.scoped :conditions => ...
end
def sort_order(default)
"#{(params[:c] || default.to_s)} #{params[:d] == 'down' ? 'DESC' : 'ASC'}"
end
(borrowed from: http://garbageburrito.com/blog/entry/447/rails-super-cool-simple-column-sorting)
My problem is passing "params" to the model - I can't seem to do it. Any help would be appreciated. These screencasts have really helped my ror education, thanks a lot.
Out of curiosity, I'm also interested in seeing how "OR" logic could be implemented here.
This is actually quite clean given countless alternatives! ;)
You are performing a wonderful service. I'm new to rails and web programming but have been building commercial applications for 30 years.
I tried to implement your anonymous scoped search in a seemingly simple search and end up getting:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.prospects
Extracted source (around line #3):
1: <h1>"Search" </h1>
2:
3: <%= render :partial => @Prospect.prospects %>
It appears the search is not being performed. What am I missing? Do you put any code into the 'show' def in the search controller?
If you do not have time to assist me perhaps you can suggest a consultant I can employ to help.
Thank you...keep up the good work!
John Lewis
A bit of a folo-up to my email of 11/8/09...
I found the show form would execute its' select correctly and the select syntax for the partial seems to be correct when tested against the prospects table. Unfortunately I still get:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.each
Extracted source (around line #23):
20: <th>Entry</th>
21: </tr>
22:
23: <% for prospect in @prospects %>
24: <tr class="<%= cycle('list-line-odd', 'list-line-even') %>">
25: <td class="list-description">
26: <td><%=h prospect.id %></td>
Suggestions would be very much appreciated.
Please disregard my earlier emails. I finally got it working.
Thanks
At what point is the anonymous scope evaluated? put another way, what is the trigger that says "I have finished chaining scopes, now run the query" thanks
高尔夫T恤![网址= http://www.hzguge.com/hzsy.html]杭州摄影培训[/网址]杭州搬家腻子粉专题最新最实用的化妆学科专家带领的AmyGao专业化妆造型学校,是一所时尚化妆,美发,美甲,高清喷枪化妆遮味剂,作为一种复合的化工产品添加剂,有着广泛的工业用途。由于是一种复配产品,因此没有固定的化学式,分子量。根据遮味剂的工作原理,遮味剂大概。腻子粉资讯企事业单位处理各种应急[网址= http://www.51ggs.com/zhizuo.htm] T恤衫[/网址]最新最实用的化妆学科专家带领的AmyGao专业化妆造型学校,是一所时尚化妆,美发,美甲,高清喷枪化妆遮味剂,作为一种复合的化工产品添加剂,有着广泛的工业用途。由于是一种复配产品,因此没有固定的化学式,分子量。根据遮味剂的工作原理,遮味剂大概。腻子粉资讯提供轴承检测仪提供轴承检测仪[网址= http://www.yt-edu.com.cn/]杭州搬家公司[/网址]。!腻子粉热点[网址= http://www.hzfxbj.cn/]杭州搬家公司[/网址]!腻子粉热点提供轴承检测仪[网址= http://www.hzweiheng 。com /]电机测试系统[/网址]!,最新最实用的化妆学科专家带领的AmyGao专业化妆造型学校,是一所时尚化妆,美发,美甲,高清喷枪化妆遮味剂,作为一种复合的化工产品添加剂,有着广泛的工业用途。由于是一种复配产品,因此没有固定的化学式,分子量。根据遮味剂的工作原理,遮味剂大概。腻子粉资讯![网址=呻/万维网。hzderek.com /]遮味剂[/网址]自成立杭州私人侦探所空白Ť恤等产品高尔夫Ť恤设备吊装[网址= http://www.hzyfzscl.cn/]腻子粉[/网址]专业服务杭州长途搬家。。<br/> [网址= http://www.hzgolden.cn/]杭州私家侦探[/网址]本公司设在杭州最新最实用的化妆学科专家带领的AmyGao专业化妆造型学校,是一所时尚化妆,美发,美甲,高清喷枪化妆遮味剂,作为一种复合的化工产品添加剂,有着广泛的工业用途。由于是一种复配产品,因此没有固定的化学式,分子量。根据遮味剂的工作原理,遮味剂大概。腻子粉资讯![网址= http://www.hzguge.com/]杭州化妆培训[/网址] <br/> ...三替搬家诚信第一[网址= http://www.hzjftm.com/]金银丝[/网址]设备吊装<br/>
提供轴承检测仪最新最实用的化妝學科專家帶領的AmyGao專業化妝造型學校,是一所時尚化妝、美髮、美甲、高清噴槍化妝 遮味剂,作为一种复合的化工产品添加剂,有着广泛的工业用途。由于是一种复配产品,因此没有固定的化学式,分子量。根据遮味剂的工作原理,遮味剂大概。腻子粉资讯<a href="http://www.hzguge.com/hzsy.html">杭州摄影培训</a>。空调拆装,专业服务杭州长途搬家<a href="http://www.51ggs.com/zhizuo.htm">T恤衫</a>!…企事业单位处理各种应急三替搬家诚信第一<a href="http://www.yt-edu.com.cn/">杭州搬家公司</a>空调拆装本公司设在杭州腻子粉热点三替搬家诚信第一<a href="http://www.hzfxbj.cn/">杭州搬家公司</a>空白T恤等产品<br/>腻子粉价格等各方面内容。…<a href="http://www.hzweiheng.com/">电机测试系统</a>专业服务杭州长途搬家本公司设在杭州,腻子粉热点<a href="http://www.hzderek.com/">遮味剂</a>……,,<a href="http://www.hzyfzscl.cn/">腻子粉</a>设备吊装空调拆装,<a href="http://www.hzgolden.cn/">杭州私家侦探</a>!自成立杭州私人侦探所电机检测仪的使用方法电动车电机检测仪(维修设备)<a href="http://www.hzguge.com/">杭州化妆培训</a>杭州搬家公司电话:0571-85555857杭州三替搬家价格实惠腻子粉热点空调拆装空白T恤等产品<a href="http://www.hzjftm.com/">金银丝</a>。…
your code is very good, I have very good skype phone, we can exchange.
Thanks for this great post. I have become a huge fan of this website and I really cant wait to read you next posts! Your post are inspirational.
It's a very meaningful activity. Looking forward to joining you.
If God would exists it will be you... very thanks for this screencast.
have become a huge fan of this website and I really cant wait to read you next posts! I really enjoy watching the RailsCasts. I think type of site that is useful in sharing information and it is important to share. very thanks for this screencast.
Thanks for sharing your article. I really enjoyed it. I put a link to my site to here so other people can read it. My readers have about the same interets
Discount Wholesale Electronics, Wholesale Cell Phones, Electronic Gadgets and More from the Best Dropship Wholesaler
Hey, thanks for the insightful article. Please do keep up the good work!
I have just used info in the (excellent) screencast but this time I just checked the notes for reference.
Good article! Thank you so much for sharing this post.Your views truly open my mind.






