Rails 3 noob - difficulty populating an array - ruby-on-rails-3

UPDATED WITH SOLUTION FROM BELOW PRODUCES NEW ERROR.
Users invite others to review their work. To track this, the invitations table has reviewer_id and reviewee_id fields. The model contains:
belongs_to :user
has_many :users
To display all invitations for a user we first get all the invitations:
#invitations = Invitation.where("reviewee_id = ?", current_user.id ).select(:reviewer_id).order("updated_at")
Then we get all the users who were invited: (this part is wrong)
#reviewers = []
#invitations.each do |i|
#reviewers << User.where("id = ?", i.reviewer_id )
end
This current solution produces the following:
undefined method `first_name' for []:ActiveRecord::Relation
I did a test with the following code to see what is in #reviewers:
<% #reviewers.each do |r| %>
<%= r.id %><br>
<% end %>
Instead of returning the ids it returned:
2173491700
2173491200
2173490540
So the array is not getting populated appropriately.
I am grateful for your help and most grateful for specific code.

You want to gather up all the IDs then pass them to where.
reviewer_ids = #invitations.collect { |i| i.reviewer_id }
#reviewers = User.where(:id => reviewer_ids)
This grabs all the reviewers in a single database call.

Do the following to get the reviewers:
#reviewers = []
#invitations.each do |i|
#reviewers << User.where("id = ?", i.reviewer_id )
end
The << adds elements to the array. And, before the iteration we create an array.

Related

Get users of search products search results

I would know the best way to get all users who have products of the search result
My controller
#users = User.all // i must change here ?
if params[:product_type].present? && params[:location].present? && params[:discount].present?
#products = Product.near(params[:location], 1, units: :km).where(product_type: params[:product_type], discount: params[:discount])
end
My view
<% #products.each do |p| %>
<%= p.user.company_name %>
<% end %>
My method work but if a company have 2 products its display it 2 times
Assuming the following:
User has_many :products
Product belongs_to :user
You can do:
#users_from_product_results = User.where(id: #products.pluck(:user_id))
If several products refer to the same user, this User will be listed only once.
Rather than making 2 separate AR calls, use includes(eager_loading) which gives both products and users in a single query.
if params[:product_type].present? && params[:location].present? && params[:discount].present?
#products = Product.near(params[:location], 1, units: :km).where(product_type: params[:product_type], discount: params[:discount])
.includes(:user)
end
And in the view you could use #products.map(:users).uniq which gives all the users associated with the products.

Why isn't my search method working in Ruby on Rails?

In my Ruby on Rails application, I have a cinema system and am trying to return the screen a showing is in when a user searches for the showing.
To display the search drop down I am using this code in my _application.html.erb:
<%= render( :partial => '/screen_lookup', :locals => {:showings => #showings = Showing.all, :my_path => '/screens/display_screens_by_showing' })%>
Which renders the search from the _screen_lookup.html.erb:
<%= form_tag my_path, :method=>'post', :multipart => true do %>
<%= select_tag ('showings_id'),
options_from_collection_for_select(#showings, :id, :showing_times, 0 ),
:prompt => "Showings" %>
<%= submit_tag 'Search' %>
<% end %>
And uses the display_screens_by_showing in the screens_controller:
def display_screens_by_showing
#screens = Screen.showing_search(params[:showing_id])
if #screens.empty?
# assign a warning message to the flash hash to be displayed in
# the div "feedback-top"
flash.now[:alert] = "There are no films of that genre."
# return all products, in alphabetical order
#screens = Screen.all
end
render :action => "index"
end
And this searches using the method in the screen.rb model:
def self.showing_search(showing_id)
screen = Showing.where("id = ?", showing_id).screen_id
self.where("id = ?", screen)
end
Now, the problem I am having is that because a showing belongs_to a screen, and a screen has_many showings, I need to be able to search for the showing, and store that showing's screen_id in a variable to search for the screen that showing is in with, which I have tried doing in the model:
screen = Showing.where("id = ?", showing_id).screen_id
self.where("id = ?", screen)
But the error I am getting is:
NoMethodError in ScreensController#display_screens_by_showing
undefined method `screen_id' for #<ActiveRecord::Relation []>
These are the model relationships:
showing.rb:
class Showing < ActiveRecord::Base
belongs_to :screen
end
screen.rb:
class Screen < ActiveRecord::Base
has_many :showings
end
What code will get my search working?
The problem is that where doesn't return a record, it returns a relation that can be enumerated or chained, instead you want to use find or find_by to return a single record, which is kind equivalent to where + first
screen = Showing.find(showing_id).screen_id
which is sort of like doing
screen = Showing.where(id: showing_id).first.screen_id
If you want to pass a hash you can use find_by which will be like this
screen = Showing.find_by(id: showing_id).screen_id
PS:
I'm not sure what you're doing exactly, but i think those two lines can be merged into a single query ( not sure what it should be returning, but I'm assuming a screen )
def self.showing_search(showing_id)
Showing.find(showing_id).screen
end

Advanced Rails ActiveRecord query using scope

I've been trying for a while to find the best way of querying for the desired result, but I always end up failing at some poing in the query.
Simplified database structure:
User:
id (integer)
first_name (string)
last_name (string)
CourseType:
title (string)
slug (string)
Course:
belongs_to :user
belongs_to :course_type
week (integer)
sold (float)
My controller is calling a scope:
#users = User.sales_results(week)
And here's the scope in my model:
scope :sales_results, lambda { |week|
joins(:courses => [:course_type])
.select("
users.id, users.first_name, users.last_name,
SUM(courses.sold) as total_sold,
COUNT(courses) as num_classes
")
.where("courses.week = ?", week)
.group('users.id')
}
This works fine, and I can use it in my template to show the total amount sold. Although I also want to show a second column where the value sold for some specific types of courses are summed up in. Something like this:
<% #users.each do |user| %>
<%= user.total_sold %>
<%= user.total_sold.where("course_types.slug IN ('H', 'S')") # not possible, but similar to what I desire %>
<% end %>
Update
I ended up adding another scope
scope :sales_results_for_types, lambda { |week, types|
sales_results(week).except(:group).where("course_types.slug IN (?)", types)
.group('users.id')
}
Then calling both scopes in my controller
#users = User.sales_results(...)
#users_filtered = User.sales_results_for_types(...)
Lastly iterating both results at the same time
<% #users.zip(#users_filtered).each do |user, filtered| %>
<%= filtered.total_sold %>
<%= user.total_sold %>
Until I figure out something better. Thanks guys for leading me on the right track.
If you are ok with having another query, you can use this
user.joins(courses: :course_type).sum(:sold, group: 'course_types.slug')
which will give you a hash where the keys are the slugs, and the values are the sums.

Duplicating a record with associated images using Carrierwave

I have an app which you can store order/invoices in. I'm building a simple feature where you can duplicate invoices for my customers. I wrote this method in my Order.rb model which:
Takes the invoice, duplicates the associated lineitems, adds the new OrderID into them...and does the same for associated images.
def self.duplicate_it(invoice)
new_invoice = invoice.dup
new_invoice.save
invoice.lineitems.each do |l|
new_lineitem = l.dup
new_lineitem.order_id = new_invoice.id
new_lineitem.save
end
invoice.images.each do |i|
new_image = i.dup
new_image.order_id = new_invoice.id
new_image.save
end
return new_invoice
end
Unfortunately, you can't just .dup the image because there's all this associate expiration stuff since I'm storing images on S3. Is there a way to regenerate the image maybe using its image_url?
The error I get when running this is below. Which tells me not all the associated image information is dup'd correctly.
Showing /Users/bruceackerman/Dropbox/printavo/app/views/orders/_image-display.erb where line #3 raised:
undefined method `content_type' for nil:NilClass
Extracted source (around line #3):
1: <% #order.images.each do |image| %>
2: <% if image.image && image.image.file %>
3: <% if image.image.file.content_type == "application/pdf" %>
4: <%= link_to image_tag("/images/app/pdf.jpg",
5: :class => 'invoice-image'),
6: image.image_url,
i think you can do the following
invoice.images.each do |i|
new_image = new_invoice.images.new({ order_id: new_invoice.id })
new_image.image.download!(i.image_url)
new_image.store_image!
new_image.save!
end
This is actually how I did it for each lineitem on an order:
def self.duplicate_it(invoice)
new_invoice = invoice.dup :include => {:lineitems => :images} do |original, kopy|
kopy.image = original.image if kopy.is_a?(Image)
end
new_invoice.save!
return new_invoice
end
It is a bit late however this is my solution. I have had far too many problems with .download!
if #record.duplicable?
new_record = #record.dup
if new_record.save
#record.uploads.each do |upload|
new_image = new_record.uploads.new({ uploadable_id: new_record.id })
new_image.filename = Rails.root.join('public'+upload.filename_url).open
new_image.save!
end
end
Here is my upload.rb
class Upload < ActiveRecord::Base
belongs_to :uploadable, polymorphic: true
mount_uploader :filename, ImageUploader
end
Hope it helps!

rails OR query based on multiple checkbox selections

This seems like it should be a common problem but I'm having trouble finding an answer. Basically I want to have a form with 10 or so checkboxes which I'm creating with check_box_tag. When the form is submitted I want to generate a query that return all records that match ANY of the checked selections. So, the number of checked selections will vary.
So, for example, if I have
class Book < ActiveRecord::Base
belongs_to :author
end
I want to generate something like
Book.where("author_id = ? or author_id = ?", params[authors[0]], params[authors[1]]) if there are two boxes checked, etc.
Thanks for any insight.
Will this work for you?
Book.where(author_id: [array_of_author_ids])
You need to collect author_ids from params first
I recently had to do something similar, this is how I achieved this. It's pretty clever (at least I think so. :))
I created a query model that serializes the query column (text field) in JSON. I use a form to get the query data from the user with selection fields.
class BookQuery < ActiveRecord::Base
has_many :books
# loop through each foreign key of the Book table and create a hash with empty selection
def self.empty_query
q = {}
Book.column_names.each do |column_name|
next unless column_name.ends_with?("_id")
q.merge column_name => []
end
end
end
I'm using Author as an example below:
<%= form_for #book_query do |f| %>
<% for author in Author.all %>
<%= check_box_tag "book_query[query][author_ids][]", author.id, false%>
<%= author.name %>
<% end %>
<%= f.submit "Save Query" %>
<% end %>
When this form is submitted you ended up with parameters like this:
When the form is submitted it generates this parameter:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XXXXXXXXXXX", "book_query"=>{"query"=>{"author_ids"=>["2", "3"]}}, "commit"=>"Save Query"}
Now in the BookQuery controller's create action you can just do what create function always does:
def create
#book_query = BookQuery.build(params[:book_query])
if #book_query.save
flash[:success] = "Book query successfully saved."
redirect_to ...
else
flash[:error] = "Failed to save book query."
render :new
end
end
But by default rails serializes the data in hash type:
1.9.3p194 :015 > pp BookQuery.find(9).query
BookQuery Load (0.7ms) SELECT "book_queries".* FROM "book_queries" WHERE "book_queries"."id" = $1 LIMIT 1 [["id", 9]]
"--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nauthor_ids:\n- '2'\n- '3'\n"
=> "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nauthor_ids:\n- '2'\n- '3'\n"
In BookQuery model, add the following:
serialize :query, JSON
But rail would change the IDs to string:
1.9.3p194 :018 > query = JSON.parse(BookQuery.find(10).query)
BookQuery Load (0.5ms) SELECT "book_queries".* FROM "book_queries" WHERE "book_queries"."id" = $1 LIMIT 1 [["id", 10]]
=> {"author_ids"=>["2", "3"]}
1.9.3p194 :019 > query["author_ids"]
=> ["2", "3"]
What I did then is override the attribute accessors in BookQuery model:
The below has to be done because the hash returns strings, not ids in integer.
def query=(query)
query.each_pair do |k, v|
if query[k].first.present?
query[k].map!(&:to_i)
else
query.except!(k)
end
end
write_attribute(:query, query)
end
# just want to avoid getting nil query's
def query
read_attribute(:query) || {}
end
To find book with this query, you can simply add this function to your Book model:
def self.find_by_book_query(book_query, options = {})
options[:conditions] = book_query.query
find(:all, options)
end
Now you get a customizable query string based on the model definition Book and everything works like the Rails way. :)