I am using the following to output a list of JSON records:
#team.people.to_json(
:include => [:user, :statistics => {:include => :attribute}]).html_safe
However, I would like to only include statistics that have a certain type_id set on them. Essentially a left outer join with the users and the statistics, where the a type_id on the statistic equals some number.
I can think of at least a couple options:
In the Person model, override to_json (or, perhaps better yet, serializable_hash) and do your conditional there.
Instead of {:include => :attribute} do {:methods => :foo} and do your conditional in foo.
Here's an example of where I overrode serializable_hash, if it helps:
def serializable_hash(options={})
options = {
:methods => [
'client',
'services',
'products',
'has_payments',
]}.update(options)
super(options)
end
I could imagine something above options = where you set the methods array to one thing if type_id is the number you're looking for, or to something else otherwise.
Related
If I wanted to eagerly load a collection in rails and render it in json, I would have to do something like this.
#photos = #event.photos.to_json(:include =>
{:appearances => {:include => :person}}
)
What if I wanted to map this collection? As you can see it's no longer a collection, but a json string. Prior to this necessary eager loading, I was doing the following:
#photos = #event.photos.map{|photo|
photo['some_funky_stuff'] = photo.funky_calculation
photo
}
But, I can't seem to be able to do the two things together:
#event.photos.map{|photo|
photo['some_funky_stuff'] = photo.funky_calculation
photo
}.to_json(:include =>
{:appearances => {:include => :person}}
)
The above does not show 'appearances' ( the eagerly loaded join record )... How do I do these two together? Many thanks!
You may have the term "eager loading" mixed up a little bit. As previous answers have mentioned, you need to use it on the association for it to be eager loaded. However, when you use :include in the to_json call, you will still end up with the same result, no matter if it is eager or not.
But to answer your question, for the to_json method to both include the appearances and the funky_calculation you can combine it with :methods instead. Try it like this:
#photos = #event.photos.to_json(
:include => {:appearances => {:include => :person}},
:methods => [: funky_calculation]
)
And if you want increased performance (eager loading), then use include on the associations as well:
#photos = #event.photos.includes(:appearances => :person).to_json(
:include => {:appearances => {:include => :person}},
:methods => [: funky_calculation]
)
You could eager load using includes after has_many association
#photos = #event.photos.includes(:appearances => [:person]).to_json
You might want to try using joins() or includes() on photos, instead as an option to to_json().
http://guides.rubyonrails.org/active_record_querying.html#using-array-hash-of-named-associations
I am using rails 3 with mongoid.
I have a collection of Stocks with an embedded collection of Prices :
class Stock
include Mongoid::Document
field :name, :type => String
field :code, :type => Integer
embeds_many :prices
class Price
include Mongoid::Document
field :date, :type => DateTime
field :value, :type => Float
embedded_in :stock, :inverse_of => :prices
I would like to get the stocks whose the minimum price since a given date is lower than a given price p, and then be able to sort the prices for each stock.
But it looks like Mongodb does not allow to do it.
Because this will not work:
#stocks = Stock.Where(:prices.value.lt => p)
Also, it seems that mongoDB can not sort embedded objects.
So, is there an alternative in order to accomplish this task ?
Maybe i should put everything in one collection so that i could easily run the following query:
#stocks = Stock.Where(:prices.lt => p)
But i really want to get results grouped by stock names after my query (distinct stocks with an array of ordered prices for example). I have heard about map/reduce with the group function but i am not sure how to use it correctly with Mongoid.
http://www.mongodb.org/display/DOCS/Aggregation
The equivalent in SQL would be something like this:
SELECT name, code, min(price) from Stock WHERE price<p GROUP BY name, code
Thanks for your help.
MongoDB / Mongoid do allow you to do this. Your example will work, the syntax is just incorrect.
#stocks = Stock.Where(:prices.value.lt => p) #does not work
#stocks = Stock.where('prices.value' => {'$lt' => p}) #this should work
And, it's still chainable so you can order by name as well:
#stocks = Stock.where('prices.value' => {'$lt' => p}).asc(:name)
Hope this helps.
I've had a similar problem... here's what I suggest:
scope :price_min, lambda { |price_min| price_min.nil? ? {} : where("price.value" => { '$lte' => price_min.to_f }) }
Place this scope in the parent model. This will enable you to make queries like:
Stock.price_min(1000).count
Note that my scope only works when you actually insert some data there. This is very handy if you're building complex queries with Mongoid.
Good luck!
Very best,
Ruy
MongoDB does allow querying of embedded documents, http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-ValueinanEmbeddedObject
What you're missing is a scope on the Price model, something like this:
scope :greater_than, lambda {|value| { :where => {:value.gt => value} } }
This will let you pass in any value you want and return a Mongoid collection of prices with the value greater than what you passed in. It'll be an unsorted collection, so you'll have to sort it in Ruby.
prices.sort {|a,b| a.value <=> b.value}.each {|price| puts price.value}
Mongoid does have a map_reduce method to which you pass two string variables containing the Javascript functions to execute map/reduce, and this would probably be the best way of doing what you need, but the code above will work for now.
I have 2 simple named scopes defined as such:
class Numbers < ActiveRecord::Base
named_scope :even, :conditions => {:title => ['2','4','6']}
named_scope :odd, :conditions => {:title => ['1','3','5']}
end
if I call Numbers.even I get back 2,4,6 which is correct
if I call Numbers.odd I get back 1,3,5 which is correct
When I chain them together like this: Numbers.even.odd I get back 1,3,5 because that is the last scope I reference. So if I say Numbers.odd.even I would actually get 2,4,6.
I would expect to get 1,2,3,4,5,6 when I chain them together. One other approach I tried was this:
named_scope :even, :conditions => ["title IN (?)", ['2', '4','6']]
named_scope :odd, :conditions => ["title IN (?)", ['1', '3','5']]
But I get no results when I chain them together because the query it creates looks like this:
SELECT * FROM `numbers`
WHERE ((title IN ('1','3','5')) AND (title IN ('2','4',6')))
The 'AND' clause should be changed to OR but I have no idea how to force that. Could this be an issue with ActiveRecord??
It's an issue with how ActiveRecord handles scopes. When you apply multiple scopes, the results are joined together with AND. There is no option for using OR.
What you need to do instead is combine two result sets:
Numbers.even.all + Numbers.odd.all
Having trouble with AR 2.3.5, e.g.:
users = User.all( :select => "u.id, c.user_id", :from => "users u, connections c",
:conditions => ... )
Returns, e.g.:
=> [#<User id: 1000>]
>> users.first.attributes
=> {"id"=>1000, "user_id"=>"1000"}
Note that AR returns the id of the model searched as numeric but the selected user_id of the joined model as a String, although both are int(11) in the database schema.
How could I better form this type of query to select columns of tables backing multiple models and retrieving their natural type rather than String ? Seems like AR is punting on this somewhere. How could I coerce the returned types at AR load time and not have to tack .to_i (etc.) onto every post-hoc access?
It's unfortunately not going to happen very easily. All of the data from the DB connection comes to rails as strings, the conversion of types happens in each of the dynamic attribute methods that rails creates at runtime. It knows which attributes to convert to which type by the table's column-type meta-data that it retrieves when the app starts. Each model only has column meta-data for it's own columns, that's why it's own columns end up with correct type. There is no easy way to auto-convert to the correct types.
You could on the other hand, create a simple conversion method that would take a Hash and automatically convert the attributes.
Something like this:
users = User.all(:select => "cl, comments.c2", ...)
users = convert_columns(users, 'c2' => :integer, 'other_column' => :date)
def convert_columns(records, columns = {})
records.each do |rec|
columns.each do |col, type|
rec[col] = case type
when :int then rec[col].to_i
when :date then ........
....
end
end
end
end
Why are you using :from => "users" inside a User.method ?
The following will do an inner join (which is what you are doing anyways)
users = User.all(:include => :connections, :select => "users.id, connections.user_id", :conditions => {...})
This is going to be very heavy query for the database.
Faster query would be with the outer join though.
This will also return the keys as INT not STRING
A much faster alternative was
Connection.all(:include => :user, :conditions => {...}).collect {|e| [e.user_id, e.id] }
This gives you an array of arrays with the ids. If you are going to select "id, user_id" columns only, then it may not necessarily be as AR object. An array can be faster.
I hope I am not missing some point here. Suggest me, if I am.
If you want quick solution - try to use after_find callback and preset correct attributes types there:
class User < ActiveRecord::Base
after_find :preset_types
private
def preset_types user
user.user_id = user.user_id.to_i
end
end
I have a fairly large model and I want to retrieve only a select set of fields for each record in order to keep the JSON string I am building small.
Using :select with find works great but my key goal is to use conditional logic with an associated model. Is the only way to do this really with a lamda in a named scope? I'm dreading that perhaps unnecessarily but I'd like to understand if there is a way to make the :select work with a condition.
This works:
#sites = Site.find :all, :select => 'id,foo,bar'
When I try this:
#sites = Site.find :all, :select => 'id,foo,bar', :include => [:relatedmodel],
:conditions => ["relatedmodel.type in (?)", params[:filters]]
The condition works but each record includes all of the Site attributes which makes my JSON string way way too large.
Thanks for any pointers!
The to_json call supports :except and :only options to exclude/include model fields during serialization.
#sites.to_json(:only => [:name, :foo, :bar])
Call above serializes the Site objects with fields name and location.
#sites.to_json(:only => [:name, :location],
:include => { :relatedmodel => {
:only => [:description]
}
}
)
Call above serializes the Site objects with fields name, and location and contained RelatedModel objects with description field.