Merge nested params via link_to - ruby-on-rails-3

I'm using nested params (via ransack nested as q) alongside normal params to build links on a page and am having trouble with getting the two to play nicely when I try and merge the nested params with the other params.
For example if I have:
{"freq"=>"weekly", "loan_amount"=>"350000",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"}}
and try and build a link to change the param "lowEquity_true" with
rates_url(params[:q].merge(:lowEquity_true => '0'))
then I end up with the new params below, which looks like its updated q but dropped the rest of the params.
{"lowEquity_true"=>"0", "s"=>"rate asc"}
If I instead try to merge q & merge into the other params it doesn't update q, and just merges what was in q into the other params instead
rates_url(params[:q].merge(:lowEquity_true => '0').merge(params))
{"freq"=>"weekly", "loan_amount"=>"350000", "lowEquity_true"=>"0",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"},
"s"=>"rate asc"}
I've tried all sorts of various combinations and don't appear to be getting anywhere so am sure that I'm missing something basic!

You are doing it wrong.
Let me explain with an example :
params = {:a => 1, :b => 2, :q => {:x => 24, :y => 25}}
At this point, params[:q] is
{:x=>24, :y=>25}
If I do,
params[:q].merge(:x => 99)
then my params[:q] will become
{:x=>99, :y=>25}
and this is what you are supplying as an argument to rates_url(params[:q].merge(:lowEquity_true => '0'))
that's why only {"lowEquity_true"=>"0", "s"=>"rate asc"} is passed to rates_url as parameters.
Now, if you do something like
params[:q].merge(:x => 99).merge(params)
then params[:q].merge(:x => 99) gives you {:x=>99, :y=>25} and then it merges {:x=>99, :y=>25} into the original params {:a => 1, :b => 2, :q => {:x => 24, :y => 25}}
, so this results into
{:x=>99, :y=>25, :a=>1, :b=>2, :q=>{:x=>24, :y=>25}}
Now, let me explain you what you should do :-
You params is
{"freq"=>"weekly", "loan_amount"=>"350000",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"}}
So, you should do :
params[:q].merge!(:lowEquity_true => '0')
rates_url(params)
That's it
I hope you khow the difference between merge and merge! :-
merge! is destructive, it will modify the original paramter where as merge will not unless you take it in a variable and use it.
Alternatively, if you want to do the same thing stated above in a single line then, just do
rates_url(params.merge!(:q => {:lowEquity_true => '0', "s"=>"rate asc"}))
OR
rates_url(params.merge(:q => params[:q].merge(:lowEquity_true => '0')))

Related

Is it possible to run findFirst() with IN clause in Phalcon?

I would like to use an IN clause in findFirst but it doesn't seem to work?
Expected code, or something similar:
$item = Item::findFirst([
'conditions' => 'categories IN :cats: AND released < :now:',
'order' => 'id ASC',
'bind' => [
'cats' => $this->categories,
'released' => time()
],
]);
I tried using bindTypes but there's no such "list" or "array" type (also, that would get a lot more verbose than expected)...
I know I can do it through the query builder, but I was looking to keep it a bit more idiomatic:
$item = Item::query()
->inWhere('categories', $this->categories)
->andWhere('released < :now:', ['now' => time()])
->orderBy('id ASC')
->limit(1)
->execute()
->getFirst();
You can bind the array and IN clause like this:
$result = ModelName::findFirst([
'conditions' => 'id in ({cats:array})',
'bind' => array('cats' => [3, 5, 8])
]);
Note that the above example will get the first record with id 3. However if you use find() you will get the items with 3, 5 and 8.
More examples in the docs (at the bottom of this section).

silverstripe query does not work, best way to debug?

I have this silverstripe query that does not work ( it outputs all messages and not the ones with the date range )
What would be the best way to tackle this query?
Im fairly new to silverstripe and havent been able to find information on how to print the raw query.
return = Message::get()
->filter(array(
'IsPublished' => true,
'StartPublication:LessThanOrEqual' => date('Y-m-d'),
'Priority' => array('High', 'Normal')
))
->where("\"StopPublication\" >= ".date('Y-m-d')." OR \"StopPublication\" IS NULL")
->sort('StartPublication', 'DESC')->limit($this->getLimit());
The correct answer is to not use where() - this is a trap method that a lot of learners fall into (presumably due to the name). It's intended basically only for very complex things that the ORM just can't handle.
You're calling filter at least, which is the correct thing. But what you want instead of where() is filterAny():
Message::get()
->filter([
'IsPublished' => true,
'StartPublication:LessThanOrEqual' => 'now',
'Priority' => ['High', 'Normal']
])
->filterAny([
'StopPublication:GreaterThanOrEqual' => 'now',
'StopPublication' => null
])
->sort('StartPublication', 'DESC')
->limit($this->getLimit());
As the other answer already specifies, do not use an = on the return (or put a $ in front of return to make it a variable), and to return the query itself use $datalist->sql() http://api.silverstripe.org/3.1/class-DataList.html#_sql
But - seeing the docs on SQLQuery is wrong, because you're not using SQLQuery. You're using the ORM, so this doc page is far more relevant: http://docs.silverstripe.org/en/3.1/developer_guides/model/data_model_and_orm/#filterany
For starts return = Message::get() its just return Message::get()
I assume that you have set php error reporting so that it outputs errors and SS is also in development mode so it won't hide error outputs.
The answer to your question is to to do either:
to output it to the output html:
Debug::dump(Message::get()
->filter(array(
'IsPublished' => true,
'StartPublication:LessThanOrEqual' => date('Y-m-d'),
'Priority' => array('High', 'Normal')
))
->where("\"StopPublication\" >= ".date('Y-m-d')." OR \"StopPublication\" IS NULL")
->sort('StartPublication', 'DESC')->limit($this->getLimit())->sql());
or output it to the project roots log file
Debug::log(Message::get()
->filter(array(
'IsPublished' => true,
'StartPublication:LessThanOrEqual' => date('Y-m-d'),
'Priority' => array('High', 'Normal')
))
->where("\"StopPublication\" >= ".date('Y-m-d')." OR \"StopPublication\" IS NULL")
->sort('StartPublication', 'DESC')->limit($this->getLimit())->sql());
See http://docs.silverstripe.org/en/developer_guides/model/sql_query/

complex search with thinking sphinx

I'd like to do a complex search with thinking sphinx:
Search for users which:
-> live in a city (city_id attribute)
-> or has hability to move to a city (mobile_cities association)
-> or live at a maximum distance from a lat/long point, the maximum distance is different for each user and set in a mobility_distance attribute.
For now I did that with 3 differents search, I volontary set a big per_page number, then i merge the 3 results on a single array, an then paginate this array :
#users living in the #city
search_set_living = search_set.merge({:city_id => #city.id })
users_living = User.search :with => search_set_living.dup,
:page => 1, :per_page => 1000
#users declaring hability to move to the #city
search_set_mobile = search_set.merge({:mobile_cities_ids => #city.id })
users_mobile = User.search :with => search_set_mobile.dup, :page => 1, :per_page => 1000
#users living at a maximum distance from the origin point(custom distance for each user, max 30km)
search_set_around = search_set.merge({"#geodist" => 0.0..30_000.0})
users_around = User.search :geo => [#search_latitude * Math::PI / 180 , #search_longitude * Math::PI / 180],
:with => search_set_around.dup,
:page => 1, :per_page => 1000
users_around_filtered = users_around.dup.delete_if{|user| (user.mobility_distance * 1000 )< user.sphinx_attributes['#geodist'] }
#merge the 3 results in a array
all_users = (users_mobile.flatten + users_around_filtered.flatten).uniq
#look for facets and paginate the array
#facets = User.facets :with => {:user_id => all_users.map(&:id)}
#users_to_display = all_users.paginate(:page => params[:page], :per_page => 10)
This is working fine but i'm not satisfied:
-performance are not so good,
-I want the ability to sort on multiple attributes like this :order => "created_at DESC, #relevance DESC"
I want to do the exact same search but in a single sphinx's search.
I know that I should use the "OR Logic with Attribute Filters" from the docs but I don't know how to mix it with a geo_search call...
I really have no idea how to do that,
can you guys help me ?
Many thanks,
The :sphinx_select option is definitely your friend here, as you've guessed. Let's piece it together bit by bit:
logic = [
"city_id = #{#city.id}",
"IN(mobile_cities_ids, #{#city.id}",
"GEODIST(lat, lng, #{lat}, #{lng}) < (mobility_distance * 1000)"
]
User.search :sphinx_select => "*, #{logic.join(" OR ")}) AS valid",
:with => {:valid => true}
Add pagination as you like, tweak the attribute names if needed (maybe your lat/lng attributes are named something else). I don't think you need the IF call around that custom attribute like in the docs, but if things aren't working when they should be, maybe give it a shot. Should be good in a facets call too.
Great ! Thank you so much. I just needed to correct a little your syntax (some parenthesis missing) in order to get it work.
I had to add per_page and page arguments too, don't know really why.
logic = ["city_id = #{#city.id}",
"IN(mobile_cities_ids, #{#city.id})",
"GEODIST(latitude, longitude, #{#search_latitude * Math::PI / 180}, #{#search_longitude * Math::PI / 180}) < (mobility_distance * 1000)"]
search_set_logic = search_set.merge({:valid => true})
#users_to_display = User.search :sphinx_select => "*, (#{logic.join(" OR ")}) AS valid",
:with => search_set_logic.dup,
:sort_mode => :extended,
:order => "visibility DESC, last_login_at DESC",
:page => params[:page], :per_page => 10

Configuring rails database query so that blank string parameters are ignored

I'm making a rails application so that users can search a database of midi records and find midi files that correspond to the attributes that I've given them.
For example, a user might enter data into an html form for a midi file with name = "blah" composer= "buh" and difficulty = "insane".
This is all fine and well, except that I would like when the user enters no data for a field, that field is ignored when doing the select statement on the database.
Right now this is what my select statement looks like:
#midis=Midi.where(:name => params[:midi][:name],
:style => params[:midi][:style],
:numparts => params[:midi][:numparts],
:composer=> params[:midi][:composer],
:difficulty => params[:midi[:difficulty])
This works as expected, but if for example he/she leaves :composer blank, the composer field should not considered at all. This is probably a simple syntax thing but i wasn't able to find any pages on it.
Thanks very much!
Not sure if Arel supports that directly, but you could always do something like:
conditions = {
:name => params[:midi][:name],
:style => params[:midi][:style],
:numparts => params[:midi][:numparts],
:composer=> params[:midi][:composer],
:difficulty => params[:midi[:difficulty]
}
#midis=Midi.where(conditions.select{|k,v| v.present?})
Try this:
# Select the key/value pairs which are actually set and then convert the array back to Hash
c = Hash[{
:name => params[:midi][:name],
:style => params[:midi][:style],
:numparts => params[:midi][:numparts],
:composer => params[:midi][:composer],
:difficulty => params[:midi][:difficulty]
}.select{|k, v| v.present?}]
Midi.where(c)

Is this the right way of using ThenFetch() to load multiple collections?

I'm trying to load all the collections eagerly, using NHibernate 3 alpha 1. I'm wondering if this the right way of using ThenFetch()?
Properties with plural names are collections. The others are just a single object.
IQueryable<T> milestoneInstances = Db.Find<T, IQueryable<T>>(db =>
from mi in db
where mi.RunDate == runDate
select mi).Fetch(mi => mi.Milestone)
.ThenFetch(m => m.PrimaryOwners)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.SecondaryOwners)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.Predecessors)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.Function)
.Fetch(mi => mi.Milestone)
.ThenFetchMany(m => m.Jobs)
.ThenFetch(j => j.Source)
;
I thought of asking this in the NHibernate forums but unfortunately access to google groups is forbidden from where I am. I know Fabio is here, so maybe the guys from the NHibernate team can shed some light on this?
Thanks
Apparently, there's no "right" way to use ThenFetch in such a case. Your example works fine but SQL produced contains many joins to Milestone, which isn't that right.
Using IQueryOver instead of IQueryable allows you to use complex syntax in Fetch:
Fetch(p => p.B)
Fetch(p => p.B.C) // if B is not a collection ... or
Fetch(p => p.B[0].C) // if B is a collection ... or
Fetch(p => p.B.First().C) // if B is an IEnumerable (using .First() extension method)
So in your case it would be:
query // = session.QueryOver<X>()
.Fetch(mi => mi.Milestone).Eager
.Fetch(mi => mi.Milestone.PrimaryOwners).Eager
.Fetch(mi => mi.Milestone.SecondaryOwners).Eager
.Fetch(mi => mi.Milestone.Predecessors).Eager
.Fetch(mi => mi.Milestone.Function).Eager
.Fetch(mi => mi.Milestone.Jobs).Eager
.Fetch(mi => mi.Milestone.Jobs.First().Source).Eager
The one thing you are missing is that you should use FetchMany() and ThenFetchMany() is the child property is a collection.
IQueryable<T> milestoneInstances = Db.Find<T, IQueryable<T>>(db =>
from mi in db
where mi.RunDate == runDate
select mi);
var fetch = milestoneInstances.Fetch(f => f.Milestone);
fetch.ThenFetch(f => f.PrimaryOwners);
fetch.ThenFetch(f => f.SecondaryOwners);
//...
As leora said, make sure when fetching children collections that you use
FetchMany()
ThenFetchMany()
A new Fetch, should pick up from the root, but this does not always happen. Sometimes you need to create them as separate queries or use Criteria Futures to batch up a multiple fetch.