This mapping worked:
#fbc = FbComments.where("reviewee_id = ?", current_user.id)
#users = User.order("last_name")
#fb_comments = #fbc.map! { |fb| [fb, #users.find_by_id(fb.user_id)] }
So two arrays are mapped... one with comments and one with the user data of the person that made the comments. But I also need the user's profile picture data. Do i change the original mapping method to include a third array somehow (e.g. #fbc + #users + #pictures), or do i have to map another array on the result of mapping the first two (e.g. #fb_comments + #pictures)?
Profile pictures, like comments, have a user_id that is matched to the id of the user who made the comments.
Thanks.
I'm not sure why you're doing this the way you are. Why not use a join (.includes) to get everything in one query?
#fbc = FbComments.where("reviewee_id = ?", current_user.id).includes(:user => :picture)
#fbc.first.user # => The first user in the results
#fbc.first.user.picture # => The first user's picture
(I'm assuming here that profile picture data is its own model called Picture. Change it to fit your app if necessary.)
Take a look at the documentation and scroll down to "Eager loading of associations."
Related
I am not sure why I can't get the columns from my other tables via my relations. I was thinking is it because of my scope? After i had a default scope in my models, everything seems to be out of place, even if i use resetscope() at some places. Some sections I can't get to my relation columns; when that happens, I'd have to use Model::model->findbypk(n)->name.. that doesn't look pretty.
the id shows if i don't have the relations, but the name is blank when i put the relation name.
CHtml::listData(Model::model()->findAll(),'product_id','main.product_name'),
my model defaultscope is pretty basic:
return array(
'condition'=>'store_id1=:store_id OR store_id2=:store_id' ,
'params' => array(':store_id' => $store_id)
);
You can change the way you use your model like below:
Model::model()->with('main')->findAll();
For my app:
user has_many images, image belongs_to user
image has_one location, location belongs_to image
Perhaps the location's fields should just be part of the image. But regardless, I'm trying to write this query in Rails:
SELECT image.caption, location.latitude, location.longitude
FROM image, location
WHERE location.image_id = image.id
AND image.user_id = 5
or alternatively, if it's easier:
SELECT image.*, location.*
FROM image, location
WHERE location.image_id = image.id
AND image.user_id = 5
How would I write this as an ActiveRecord query?
I think you want to read about Eager Loading Associations.
#images = Image.includes(:location).where("images.user_id = ?", 5)
This will find Image instances where user_id = 5. It then runs a 2nd query that will JOIN and build the associated Location instance (thats what the .includes(:location) will do for you).
This more closely matches your alternative query, as it does select all columns from images and location tables.
You can build an Array based on this containing a hash with only the keys you're interested in through something like this.
#hash_object = #images.collect { |i| { caption: i.caption, latitude: i.location.latitude, longitude: i.location.longitude } }
If you want to build this with only a single query, you can use .joins(:location) in combination with .includes(:location)
Image.joins(:location).includes(:location).where("images.user_id = ?", 5)
Important: This will omit Image instances who have no assoicated Location. You can modify the joins() a bit to help with this, but the above will have this omission.
If you really want only specific columns to be selected, read up on Selecting Specific Columns though there are warnings for the use of this
If the select method is used, all the returning objects will be read only.
and
Be careful because this also means you’re initializing a model object with only the fields that you’ve selected.
In Rails master (not out in 3.2.11) you can pass multiple columns to .pluck() but this appears to only be restricted to a single table (you wouldn't be able to get the locations table's :latitude and :longitude when plucking from Image). It's good to know about though.
I have a model Page, which can have Posts on it. What I want to do is get every Page, plus the most recent Post on that page. If the Page has no Posts, I still want the page. (Sound familiar? This is a LEFT JOIN in SQL).
Here is what I currently have:
Page.objects.annotate(most_recent_post=Max('post__post_time'))
This only gets Pages, but it doesn't get Posts. How can I get the Posts as well?
Models:
class Page(models.Model):
name = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add = True)
enabled = models.BooleanField(default = True)
class Post(models.Model):
user = models.ForeignKey(User)
page = models.ForeignKey(Page)
post_time = models.DateTimeField(auto_now_add = True)
Depending on the relationship between the two, you should be able to follow the relationships quite easily, and increase performance by using select_related
Taking this:
class Page(models.Model):
...
class Post(models.Model):
page = ForeignKey(Page, ...)
You can follow the forward relationship (i.e. get all the posts and their associated pages) efficiently using select_related:
Post.objects.select_related('page').all()
This will result in only one (larger) query where all the page objects are prefetched.
In the reverse situation (like you have) where you want to get all pages and their associated posts, select_related won't work. See this,this and this question for more information about what you can do.
Probably your best bet is to use the techniques described in the django docs here: Following Links Backward.
After you do:
pages = Page.objects.annotate(most_recent_post=Max('post__post_time'))
posts = [page.post_set.filter(post_time=page.most_recent_post) for page in pages]
And then posts[0] should have the most recent post for pages[0] etc. I don't know if this is the most efficient solution, but this was the solution mentioned in another post about the lack of left joins in django.
You can create a database view that will contain all Page columns alongside with with necessary latest Post columns:
CREATE VIEW `testapp_pagewithrecentpost` AS
SELECT testapp_page.*, testapp_post.* -- I suggest as few post columns as possible here
FROM `testapp_page` LEFT JOIN `testapp_page`
ON test_page.id = test_post.page_id
AND test_post.post_time =
( SELECT MAX(test_post.post_time)
FROM test_post WHERE test_page.id = test_post.page_id );
Then you need to create a model with flag managed = False (so that manage.py sync won't break). You can also use inheritance from abstract Model to avoid column duplication:
class PageWithRecentPost(models.Model): # Or extend abstract BasePost ?
# Page columns goes here
# Post columns goes here
# We use LEFT JOIN, so all columns from the
# 'post' model will need blank=True, null=True
class Meta:
managed = False # Django will not handle creation/reset automatically
By doing that you can do what you initially wanted, so fetch from both tables in just one query:
pages_with_recent_post = PageWithRecentPost.objects.filter(...)
for page in pages_with_recent_post:
print page.name # Page column
print page.post_time # Post column
However this approach is not drawback free:
It's very DB engine-specific
You'll need to add VIEW creation SQL to your project
If your models are complex it's very likely that you'll need to resolve table column name clashes.
Model based on a database view will very likely be read-only (INSERT/UPDATE will fail).
It adds complexity to your project. Allowing for multiple queries is a definitely simpler solution.
Changes in Page/Post will require re-creating the view.
In a Rails application, I have a particular form with many fields for editing a resource. Since I also want to log what was changed for this particular resource, I need to know which params changed.
Currently in this form, I have duplicated every field in the form with hidden field tags, so in the controller every field is compared to the corresponding hidden field to determine if the value was changed. But it's a LOT of work in the view and in the controller.
Being relatively new to Rails, I'm finding all kinds of Rails "magic" as I go along, so I wonder: does the framework provide a way to do this for me? Or is this pretty much the only way?
ActiveModel has exactly what you're looking for, take a look at the examples in the docs...
http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
person = Person.find_by_name('Uncle Bob')
person.changed? # => false
person.name = 'Bob'
person.changed? # => true
person.name_changed? # => true
person.name_was # => 'Uncle Bob'
person.name_change # => ['Uncle Bob', 'Bob']
person.name = 'Bill'
person.name_change # => ['Uncle Bob', 'Bill']
You can try Dirty attributes for this task.
#post.attributes = params[:post]
#post.changed # or changes if you want to see what values on which was changed
More on that you can find by this link: http://ar.rubyonrails.org/classes/ActiveRecord/Dirty.html
Also if you want to use versioning or auditing, try this gems (best ones are: paper_trail, acts_as_audited, vestal_versions):
https://www.ruby-toolbox.com/categories/Active_Record_Versioning
https://www.ruby-toolbox.com/categories/Active_Record_User_Stamping
I am developing a Rails 3 application (which acts as an API) and I allow
people to send in a bunch of email address (via an iPhone app) and then automatically
search the database for matching members (through emails).
It is working now (below code) but is returning doubles if the user sends in more than one copy of each email.
#users = #emails.find_all {|profile| User.find_by_email(profile['email']) }
I want the api to only return one unique copy of the user if found.
I tried the below code but it does not work.
#users = #emails.find_all {|profile| User.select('DISTINCT email').where("LOWER (email) = ?", "%#{profile['email']}%") }
How can the first line of code above be changed to distinct lookups?
Thankful for all input!
I would take a look at
How do I get the unique elements from an array of hashes in Ruby?
a = [{:a => 1},{:a => 2}, {:a => 1}]
a.inject([]) { |result,h| result << h unless result.include?(h); result }
# this returns : [{:a=>1}, {:a=>2}]
Seems like to be what you need!