Rails Controller Display Only Year - ruby-on-rails

In my rails app I have a movies table, a movie has a release_date (date/month/year).
I also have a years table which will display all movies from a specific year
If i create the year 2013, I would like to display all movies from the year 2013
In my years controller, in the :show action, I have this
#movies = Movie.where(:release_date => :ynumber)
But it doesn't show any movies from that year, even though there are movies with a release_date 2013.
I assume that it isnt showing because when I call :release_date, it is displaying (date/month/year). How can I have it display ONLY year?
I've tried using .strftime("%Y") and .strptime("%Y") but these dont seem to work
PS: I am using SQLite3 in development mode and PostgreSQL in production mode
Years#show
def show
#year = Year.find(params[:id])
#those i've tried seperately which havent worked
#movies = Movie.where("extract(year from release_date) = ?", ynumber)
#movies = Movie.where("extract(year from release_date) = ?", :ynumber)
#movies = Movie.where("extract(year from release_date) = ?", 'ynumber')
#movies = Movie.where("extract(year from release_date) = ?", ':ynumber')
#movies = Movie.where("extract(year from release_date) = ?", params[:ynumber])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #year }
end
end

To find all movies by year try this:
1) MySQL:
#movies = Movie.where("YEAR(release_date) = ?", 2013)
2) SQLite3:
#movies = Movie.where("strftime('%Y', release_date) = ?", 2013)
3) PostgreSQL:
#movies = Movie.where("extract(year from release_date) = ?", 2013)

You also can use release_date .to_date.year to get the year, since you have an standard format (date/month/year).
So you can filter using this, similar to Rajarshi Das's suggestion:
#movies = Movie.all.select{|mov| mov.release_date.to_date.year == params[:ynumber]}
OR
#movies = Movie.scoped.select{|mov| mov.release_date.to_date.year == params[:ynumber]}

Related

How can I query mulltiple tables and order by date in my view

I have a controller with 2 queries :
def index
#invoices = current_company.invoices.order(billed_at: :desc)
#user_invoices = current_user.invoices.order(billed_at: :desc)
end
in my view I have a table with a if to render the table and sometimes I have both to display,
I have a column date in this table,
How can I sort this table by date ?
Simple Rails way - just connect them and sort:
def index
#invoices = current_company.invoices.order(billed_at: :desc)
#user_invoices = current_user.invoices.order(billed_at: :desc)
#all_invoices = (#invoices + #user_invoices).sort_by {|a| a.created_at}.reverse
end
In your view:
- #all_invoices.each do |invoice|
= invoice.id
So simple!
Alternatively if you want to display either company OR user invoices:
def index
if current_company.present?
#invoices = current_company.invoices.order(billed_at: :desc)
elsif current_user.present?
#user_invoices = current_user.invoices.order(billed_at: :desc)
end
end
You could do a union of the two queries and order that...
def index
invoices = current_company.invoices
user_invoices = current_user.invoices
#all_invoices = Invoice.from("(#{invoices.to_sql} UNION #{user_invoices.to_sql}) AS invoices").order(billed_at: :desc)
end
Then you just iterate through #all_invoices. You could test if the invoice is company or user by an if statement if invoice.company == current_company

Issues Creating Custom Rails Method

I have an app with the following structure:
A mealplan includes one recipe for each day of the week.
A recipe has_many ingredients.
A grocery is one item on the user's grocery list.
I want to create a custom method so that when a button is clicked, it runs Grocery.create on each ingredient from the recipes on the mealplan.
I currently have the following mealplans#index method, so you can see how they're defined. (All of this is happening on the index view:
def index
#mealplans = Mealplan.where(user_id: current_user.id)
#mealplan = Mealplan.new
#recent = Mealplan.where(user_id: current_user.id).where("created_at > ?", Time.now.beginning_of_week).order("week_starting").last
#recipes = Recipe.where(user_id: current_user.id)
#monday = Recipe.where(id: #recent.monday)[0] if #recent.present?
#tuesday = Recipe.where(id: #recent.tuesday)[0] if #recent.present?
#wednesday = Recipe.where(id: #recent.wednesday)[0] if #recent.present?
#thursday = Recipe.where(id: #recent.thursday)[0] if #recent.present?
#friday = Recipe.where(id: #recent.friday)[0] if #recent.present?
#saturday = Recipe.where(id: #recent.saturday)[0] if #recent.present?
#sunday = Recipe.where(id: #recent.sunday)[0] if #recent.present?
end
I also have a dummy mealplans#add_to_list method set up in the controller, but I feel like doing it this way violates the "skinny controllers, fat models" principle of rails.
Can anyone clue me in to the "railsiest" way to accomplish this task, according to best practices?
Check gem "nested_form" gem for creating multiple records.
For better implementation create scope under Mealplan model.
class Mealplan < ActiveRecord::Base
scope :recent, ->(uid) { where(user_id: uid).where("created_at > ?", Time.now.beginning_of_week).order("week_starting").last}
# find the day name of recent Mealplan
def recent_day_name
created_at.strftime("%A")
end
end
In controller you can use this scope like this:
def index
#mealplan = Mealplan.new
#recent = Mealplan.recent(current_user.id)
if #recent
recent_day = #recent.recent_day_name
#day = Recipe.find(id: #recent.send(recent_day))
end
end
There is no needs to create #mealplans and #recipes instance variable on controller site:
#mealplans = Mealplan.where(user_id: current_user.id)
#recipes = Recipe.where(user_id: current_user.id)
You can get the mealplans and recipes details from current_user object.

Merging controller lines

In my pages_controller.rb, I have the following defined for a user's profile page:
def profile
if (User.find_by_username(params[:id]))
#username = params[:id]
#users = User.all.where("id = ?", User.find_by_username(params[:id]).id)
#posts = Post.all.where("user_id = ?", User.find_by_username(params[:id]).id)
else
# redirect to 404 (root for now)
redirect_to root_path, :notice=> "User not found"
end
#newPost = Post.new
end
As displayed above, I use this line to show posts on a profile page that only belong to that user:
#posts = Post.all.where("user_id = ?", User.find_by_username(params[:id]).id)
However, I also want to add this line, which will only show posts that are less than a day old:
#posts = Post.where('created_at >= :seven_days_ago', seven_days_ago: Time.now - 7.days)
How do I merge these two together? I thought I can just do this:
#posts = Post.all.where("user_id = ?", User.find_by_username(params[:id]).id, 'created_at >= :seven_days_ago', seven_days_ago: Time.now - 7.days)
But that isn't allowed. What's the proper fix?
Thanks!
#posts = Post.all.where("user_id = ?", User.find_by_username(params[:id]).id)
#posts = Post.where('created_at >= :seven_days_ago', seven_days_ago: Time.now - 7.days)
You could fix it by chaining two where.
#posts = Post.all.where("user_id = ?", User.find_by_username(params[:id]).id)
.where('created_at >= :seven_days_ago', seven_days_ago: Time.now - 7.days)
More, you can use subquery instead of two separate query
#posts = Post.all.where(:user_id => User.where(:username => params[:id]))
.where('created_at >= :seven_days_ago', seven_days_ago: Time.now - 7.days)
You got it almost right, should've use key arguments, though.
Here is the working solution
Post.where(user_id: User.find_by_username(params[:id]).id).where('created_at > ?', 7.days.ago)

How to merge two objects in Ruby on Rails

I'm trying to retrieve from the database two contents: the first one with the field source equal to "imported" (which means that we import it from the excel spreadsheet), and the second one with source != imported (we create it from scratch). Attached is my code:
def index
add_breadcrumb 'Projects', projects_path
add_breadcrumb #project.name, #project
add_breadcrumb "List #{#category.display_name} Content", project_category_contents_path(#project, #category)
#contents_imported = Content.of_project(#project).with_category(#category).imported.order('contents.created_at asc')
#contents_not_imported = Content.of_project(#project).with_category(#category).not_imported.order('contents.created_at desc')
#page = params[:page]
#contents = #contents_not_imported << #contents_imported
#q = #contents.search(params[:q])
#content = #q.result(distinct: true).page(#page).per(20)
end
#contents_imported = Content.of_project(#project).with_category(#category).imported.order('contents.created_at asc')
#contents_not_imported = Content.of_project(#project).with_category(#category).not_imported.order('contents.created_at desc')
And I want to combine the two results before showing it:
#contents = #contents_not_imported << #contents_imported
but it didn't work. How can I do that?
If both of them are arrays and are having same type of objects you can do Result = Arr1 | Arr1
That also removes the duplicates. Its like boolean UNION. In your case #contents = #contents_not_imported | #contents_imported
The problem is that you want to concatenate results, but you also want to continue treating the combined results as an ActiveRelation (call .search on it). Here's a simpler approach that avoids the need for concatenation in the first place. You will need a more complex ORDER BY clause to accomplish this, however:
#page = params[:page]
#contents = Content.of_project(#project).with_category(#category).
order('CASE WHEN source <> "imported" THEN contents.created_at END desc, CASE WHEN source = "imported" THEN contents.created_at END asc')
#q = #contents.search(params[:q])
Concatenating the arrays is done with the plus sign
You are getting undefined method search for Array because, concatenating will return you an array. And you can't call search method on that Array
EDIT
def index
add_breadcrumb 'Projects', projects_path
add_breadcrumb #project.name, #project
add_breadcrumb "List #{#category.display_name} Content", project_category_contents_path(#project, #category)
contents_imported_ids = Content.of_project(#project).with_category(#category).imported.order('contents.created_at asc').map(&:id)
contents_not_imported_ids = Content.of_project(#project).with_category(#category).not_imported.order('contents.created_at desc').map(&:id)
#page = params[:page]
contents_ids = contents_imported_ids + contents_not_imported_ids
contents = Content.where(content_ids)
#contents = content_ids.collect{|id| contents.detect{|c| c.id == id}}
#q = #contents.search(params[:q])
#content = #q.result(distinct: true).page(#page).per(20)
end
Just create a new Relation with the conditions of imported or not imported, after that, order all the records (if order is important to #contents and #content):
def index
add_breadcrumb 'Projects', projects_path
add_breadcrumb #project.name, #project
add_breadcrumb "List #{#category.display_name} Content", project_category_contents_path(#project, #category)
#contents_imported = Content.of_project(#project).with_category(#category).imported.order('contents.created_at asc')
#contents_not_imported = Content.of_project(#project).with_category(#category).not_imported.order('contents.created_at desc')
#page = params[:page]
imported = #contents_imported.where_values.reduce(:and)
not_imported = #contents_not_imported.where_values.reduce(:and)
#contents = Content.where(imported.or(not_ipmorted)).order('CASE contents.imported WHEN true THEN contents.created_at asc ELSE contents.created_at desc END')
#q = #contents.search(params[:q])
#content = #q.result(distinct: true).page(#page).per(20)
end
Now you can call Ransack#search on #contents because it is an ActiveRecord::Relation. I assume that the imported scope take a field contents.imported with value true.
If I wrote this without errors, this must works.

Rails - Find or Create based on TimeStamp? possible?

In my controller I'd like to do something like the following:
#book = Book.find(:all, :conditions = > [" created_at > with in the last 1 minute "]
if #book.nil?
# Just incase we didn't create a book, we'll initialize one
#book = Book.create()
end
#chapters = #book.chapters.build etc.............
* In sum, when the user is uploading a chapter, if they've recently created a book, I want the chapter to automatically go to that book and to make a new book.
Thoughts? thank you all
Hi Your code may be something like
time = Time.now
#book = Book.find(:all, :conditions = > [" created_at >= '#{Time.utc(time.year, time.month, time.day, time.hour, time.min - 1)}'"]) // .first if you're sure that it'll return just one record
if #book.blank? //.blank? not .nil? because the result of find is [] not nil
# Just incase we didn't create a book, we'll initialize one
#book = Array.new() //if you're sure that find'll return just one book you may don't change your code here
#book.first = Book.create()
end
//if you're sure that find'll return just one book you may don't change your code here
#book.each do |book|
#chapters = #book.chapters.build etc.............
end
if you're looking for a book created by some user you must pass user_id to this method and your conditions'll be
:conditions = > [" created_at >= '?' AND author_id = ?", Time.utc(time.year, time.month, time.day, time.hour, time.min - 1), params[:author_id]])

Resources