Issues Creating Custom Rails Method - ruby-on-rails

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.

Related

Rails Ransack - removing element from array

I'm learning how to use ransack, so I have a problem there I'm not sure if it is because the ransack or if it is because the array.
I have a form with 2 text fields (:search and :discipline). So I'm trying do a search using the 1º field parameter AND the 2º field parameter.
The idea is search for all elements that comes from the 1º parameter (field :search, and then remove all the elements that are different from the 2º parameter (field :discipline).
class PagesController < ApplicationController
def home
#rooms = Room.limit(6)
end
def search
if params[:search].present? && params[:search].strip != ""
session[:loc_search] = params[:search]
end
if params[:discipline].present? && params[:discipline].strip != ""
session[:loc_discipline] = params[:discipline]
end
arrResult = Array.new
if session[:loc_search] && session[:loc_search] != ""
#rooms_address = Room.where(active: true).near(session[:loc_search], 5, order: 'distance')
else
#rooms_address = Room.where(active: true).all
end
#search = #rooms_address.ransack(params[:q])
#rooms = #search.result
#arrRooms = #rooms.to_a
if (session[:loc_discipline] && !session[:loc_discipline].empty?)
#rooms.each do |room|
not_available = Room.where(
"(room_type != ?)",
session[:loc_discipline]
)
if not_available.length > 0
#arrRooms.delete(room)
end
end
end
end
end
My #arrRooms is returning NULL after I try do this #arrRooms.delete(room).
I dont know if have a better way to do this, but I'm trying do it like a tutorial that I found.
I assume that you're trying to show all rooms that are not available?
I think the best strategy is to load what you really want, and not loading everything an then deleting the things you don't need. Your code is really hard to read, I suggest you take a little tutorial like this: http://tryruby.org/levels/1/challenges/0, or this: https://www.codeschool.com/courses/ruby-bits
Try extracting code like where(active: true) into a scope like:
class Room < ActiveRecord::Base
scope :active, -> { where(active: true) }
scope :available, -> (discipline) { where.not(room_type: discipline) }
end
In your controller you can then make this:
def index
#rooms = Room.active.available(params[:discipline])
search_param = params[:search].present?
if search_param.present?
#rooms = #rooms.near(session[:loc_search], 5, order: 'distance')
end
#rooms = #rooms.ransack(params[:q]).result(distinct: true)
end
This is what I could guess out of your code.

One method for two models. How to pass name of model as variable to controller?

I have two methods in two different controllers (Posts & Boards). They are almost same. The difference is only model-instance-association name. To DRY this I think to write the method in module, but how to share it between Post and Board?
def init_post_comments
#user = current_user
a = #user.posts.pluck(:id) # not very nice...
b=params[:post_ids] ||= []
b = b.map(&:to_i)
follow = b - a
unfollow = a - b
follow.each do |id| # checkbox just checked
#post = Post.find_by_id(id)
if #post.users.empty?
#post.update_attribute(:new_follow, true)
end
#user.posts << #post
end
unfollow.each do |id| # if checkbox was unchecked
#post = Post.find_by_id(id)
remove_post_from_user(#post)# here we destroy association
end
if follow.size > 0
get_post_comments_data
end
redirect_to :back
end
UPDATE Ok, if I'll move the methods to model's concern how I should work with associations here? Here #user.posts.pluck(:id) and here #user.boards.pluck(:id) with what I can replace posts and boards so it can work with both of them?
So, I did it! I don't know if it's right way, but I DRY this code.
Two controllers:
posts_controller.rb
def init_comments
if Post.comments_manipulator(current_user, params[:post_ids] ||= []) > 0
#posts = Post.new_post_to_follow
code = []
#posts.each do |post|
group = post.group
code = code_constructor('API.call')
end
Post.comments_init(get_request(code), #posts)
end
redirect_to :back
end
boards_controller.rb
def init_comments
if Board.comments_manipulator(current_user, params[:board_ids] ||= []) > 0
#boards = Board.new_board_to_follow
code = []
#boards.each do |board|# подготовка запроса
group = board.group
code = code_constructor('API.call')
end
Board.comments_init(get_request(code), #boards)
end
redirect_to :back
end
As you can see they are absolutely same.
In models board.rb and post.rb - include CommentsInitializer
And in models\concerns
module CommentsInitializer
extend ActiveSupport::Concern
module ClassMethods
def comments_manipulator(user, ids)
relationship = self.name.downcase + 's'
a = user.send(relationship).pluck(:id)
b = ids.map(&:to_i)
follow = b - a
unfollow = a - b
follow.each do |id| # start to follow newly checked obj
#obj = self.find_by_id(id)
if #obj.users.empty?
#obj.update_attribute(:new_follow, true)
end
user.send(relationship) << #obj
end
unfollow.each do |id| # remove from following
#obj = self.find_by_id(id)
remove_assoc_from_user(#obj, user)#destroy relation with current user
end
follow.size
end
def comments_init(comments, objs)
i = 0
objs.each do |obj| # updating comments data
if comments[i]['count'] == 0
obj.update(new_follow: false)
else
obj.update(new_follow: false, last_comment_id: comments[i]['items'][0]['id'])
end
i += 1
end
end
def remove_assoc_from_user(obj, user)
user = user.id
if user
obj.users.delete(user)
end
end
end
My code works. If you know how to make it better please answer!

How can I iterate through a model then iterate again in my view?

I want to pull data for each of my users. I grab their person_id from my user table, then use each person's ID to figure out how many days each person has available, and show that in my view.
I'm not sure if I am doing this correctly because I am iterating in my controller then again in my view.
def how_many_days_users_have
#my_group = User.all.pluck(:person_id)
#my_group.each do |v|
#indirect_id_v = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:a_code).first
#v_range = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:ac).first
#v_range_taken = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:taken).first
#total_v_hours = #v_range.to_d - #v_range_taken.to_d
#total_v_days = #total_v_hours / 8
end
Then in my view I use this to show me this data:
%tr.trace-table
-#indirect_id_v.each do |idd|
%tr.trace-table
%td.trace-table{:style => 'border: solid black;'}= idd
-#total_v_days.each do |days|
%tr.trace-table
%td.trace-table{:style => 'border: solid black;'}= days
Okay, first things first, move some of that junk to your model, like so:
class Empaccrl < ActiveRecord::Base
def self.all_people
where(person_id: User.all.pluck(:person_id))
end
def self.active_people
all_people.where(is_active: 'Y')
end
def self.active_vacation_data
active_people.select(:person_id, :ac, :taken)
end
def total_v_hours
ac.to_d - taken.to_d
end
def total_v_days
total_v_hours / 8
end
end
Then you can use:
peoples_vacation_information = Empaccrl.active_vacation_data.all
peoples_vacation_information.map do |person|
p "person #{person.person_id} has #{person.total_v_days} vacation days"
end
Honestly, you don't even need all that, but I'm not sure why you are doing what you are doing, so I figured better be safe and add stuff. Whatever you don't need, just ignore.

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.

better way to build association in controller

I need a link in a show method of a parent class for creating associated models, so I have the code:
link_to "incomplete", new_polymorphic_path(part_c.underscore, :survey_id => survey.id)
in a helper.
This links to a part, which has new code like this:
# GET /source_control_parts/new
def new
get_collections
if params[:survey_id]
#s = Survey.find(params[:survey_id])
if #s.blank?
#source_control_part = SourceControlPart.new
else
#source_control_part = #s.create_source_control_part
end
else
#source_control_part = SourceControlPart.new
end
end
I know this is not very DRY. How can I simplify this? Is there a RAILS way?
How about this:
def new
get_collections
get_source_control_part
end
private
def get_source_control_part
survey = params[:survey_id].blank? ? nil : Survey.find(params[:survey_id])
#source_control_part = survey ? survey.create_source_control_part : SourceControlPart.new
end

Resources