How to access id in model - ruby-on-rails

I have a user class, which has_many resumes, each of which has many items. On my users/show page, I render multiple resumes, which is working. In my users_controller I have the following:
def show
...
#resumes = #user.resumes.paginate(page: params[:page])
#resume = #user.resumes.build if user_signed_in?
#resume_items = #user.res.paginate(page: params[:page])
#edu_items = #resume.edu.paginate(page: params[:page])
...
end
I defined the function res in my User model:
def res
Resume.where("student_id = ?", id)
end
And that worked quite well. However, I'm trying to do the same thing with the function edu in my Resume model:
def edu
Education.where("resume_id = ?", id)
end
but it's not working, #edu_items isn't being set to anything. Now I know it has to do with this method specifically, because if I change id to the id of a specific resume, that resume's items are rendered correctly, except across every resume. I know it's a simple fix, I've just been staring at it for way too long at this point and can't figure it out. Any advice would be amazing.
EDIT: #makaroni4: Instead of having #educations = #user.educations, I'd rather keep the items from each resume separate. Is it possible to define a method like the educations one that will make #educations = #resume.educations?
EDIT 2: I managed to get what I was trying to do to work, thanks for the advice. I solved it by doing away with the edu method altogether, and passing local variables to the partial:
<%= render :partial => 'shared/edu', :as => :educations, :locals => {:resume_educations => resume_item.educations} %>
shared/edu
<% if resume_educations.any? %>
<ol class="educations">
<%= render partial: 'shared/edu_item', collection: resume_educations %>
</ol>
<%= will_paginate #educations %>
<% end %>
Probably not the cleanest solution, but it seems to work.

I think that your model structure should look like:
class User < ActiveRecord::Base
has_many :resumes
def educations
Education.joins(:resume => :user).where(:users => { :id => id })
end
end
class Resume < ActiveRecord::Base
belongs_to :user
has_many :educations
end
class Education < ActiveRecord::Base
belongs_to :resume
end
So in your controller you can access them like:
#resumes = #user.resumes
#educations = #user.educations # all users educations, from all resumes
or
#educations = #resume.educations # educations for particular resume
And also I recommend you to read this article http://petdance.com/2012/04/the-worlds-two-worst-variable-names/ about variables naming, variables like resume_items and methods res and edu should say that you're doing smtg not in the right way.

It does not work, because the result of your edu method will always be empty.
In your code you are building a resume object:
#resume = #user.resumes.build if user_signed_in?
If you use build an object is created, but not saved to the database yet. This means that your #resume.id is nil. And thus the result of your edu method will be empty.
You could use the following to create the record in the database:
#resume = #user.resumes.create if user_signed_in?
But your edu method will still return an empty collection, because it's a new record and it won't be associated with any items yet.
Please expand on what you are trying to do exactly, because with this code #resume.edu will always be empty for the reason explained above.
Also: consider using the built-in Rails functionality instead of making your own methods.

Related

session aware model methods in to_json cause n+1 queries

This is an optimization question for an existing application, I've made the code generic to both make it annonmous and also easier to understand, instead of our proprietary models I'm describing a Forum discussion type situation. I've modified all this code for this example and not tested it, so if there are any typos I apologize, I'll try to fix them if they are pointed out to me.
Lets say I have a rails app with four models: Event, User, Forum, and Post.
the important relationships are as follows:
User has many events.
Forum has many posts.
Post has many events.
The front end is a single page javascript app, so all database data needs to be returned in json format.
Context:
when a User clicks on a post, an event is created with the name
'Show' which marks the post as no longer new.
The user needs to be
logged in to see which posts are new clicking on a forum calls the
following endpoint:
There are multiple users so the events able is a many to many relationship between posts and users.
example.com/forum/15/all_posts
heres the relevant code:
Forum Controller:
#forums_controller.rb
def all_posts
current_user = User.find(session[:user_id])
forum = Forum.includes(:posts).where(id: params[:id]).take
forum.posts.each do |post|
post.current_user = current_user
end
render json: forum.to_json(
include: [
{ posts: {
methods: [:is_new]
}}
]
)
end
Posts model:
#post.rb (posts model)
has_many :events
attr_accessor :current_user
def is_new
if current_user #user may not be logged in
!!self.events.where(user_id: current_user.id, name: 'Show').take
else
false
end
end
the model is where the action is at, so we've tried to keep logic out of the controller, but since the session is not available in the model we end up with this crazy work around of adding current_user as an attr_accessor so that methods can return the appropriate data for the user in question.... I don't like this but I've never come up with a better way to do it. We've repeated this pattern elsewhere and I would love to hear alternatives.
Here's my problem:
The call to is_new is used on the front end to determine what posts to hi-light but it's also triggering an n+1 scenario If there are 10 posts, this endpoint would net me a total 12 queries which is no good if my events table is huge. If I moved all the logic to the controller I could probably do this in 2 queries.
in short I have two questions:
MOST IMPORTANT: How can I fix this n+1 situation?
Is there a better way in general? I don't like needing an each loop before calling to_json I don't find this pattern to be elegant or easy to understand. at the same time I don't want to move all the code into the controller. What is the rails way to do this?
If working with scope is an option, I will try something like:
class Post < ApplicationRecord
scope :is_new, -> { where(user_id: current_user.id, name: 'Show') } if current_user.id?
end
If is a better option to send the current_user in your case, you can also do it:
class Post < ApplicationRecord
scope :is_new, ->(current_user) {...}
end
This is just pseudo-code to give an example:
First Answer
When I posted this I forgot you are rendering json from ForumsController.
Post
scope :for_user, -> (user = nil) do
includes(events: :users).where(users: {id: user.id}) if user
end
def is_new_for_user?(user = nil)
return true if user.nil?
self.events.empty?{ |e| e.name == 'Show' }
end
PostController
def index
#posts = Post.for_user(current_user)
end
posts/index.html.erb
...
<% if post.is_new_for_user?(current_user) %>
...
<% end
...
Second Answer
This is still pseudo-code. I didn't test anything.
Forum
scope :for_user, -> (user = nil) do
if user
includes(posts: [events: :users]).where(users: {id: user.id})
else
includes(:posts)
end
end
ForumsController
def all_posts
current_user = User.find(session[:user_id])
forum = Forum.for_user(current_user).where(id: params[:id]).take
render json: forum.to_json(
include: [
{ posts: {
methods: [:is_new_for_user?(current_user)]
}}
]
)
end

Updating Associated Objects in Rails with Method

I'm trying to update all market data from an API call.
I have a Platform that contains many Markets. The markets have the high, low, latest price, etc.
I can't iterate through the associated collection and call a method to update. Maybe I have the whole structure incorrect, I'm not sure.
I thought it made sense to use the market.update method to refresh the data with an API call.
class MarketsController < ApplicationController
def update
#platform = Platform.find(params[:platform_id])
#market = #platform.markets.find(params[:id])
#market.api_get_market_summary
#market.save
redirect_to #market.platform
end
Which works fine in the Platform view
<% #platform.markets.each do |market| %>
<%= market.market_name %>
<%= market.high %>
<%= market.low %>
<%= link_to 'Update', [market.platform, market],
:method => :put %>
<% end %>
I've tried every combination in the platform controller but I have no idea how I should be do this to update all the markets in the platform.
class PlatformsController < ApplicationController
def update
#platform = Platform.find(params[:id])
#platform.markets.each do |market|
market.update(:id => market.id) # this obviously doesn't work
end
redirect_to #platform
end
Should I be updating all the attributes here with the update_attributes function?
I call the market API update when the object is created so the data gets initialized there which is great.
How should I go about this?
Another part, if I added another platform, how would I handle the different API requests this one would use?
Having the following relationship Platform --- has_many --- Market, if you want to perform an action on the collection of markets, have you considered adding a callback on the Platform model?
class Platform < ApplicationRecord
has_many :markets
after_save :update_markets
...
private
def update_markets
markets.each do |market|
...
end
end
end
Notice that:
after_save runs both on create and update, but always after the more specific callbacks after_create and after_update, no matter the order in which the macro calls were executed.
I'm not sure what are you trying to do here market.update(:id => market.id) but if you're updating only one record on all the markets consider update_all here's a good source
Another part, if I added another platform, how would I handle the different API requests this one would use?
By adding another platform a new Platform object is created and stored with a different ID, when the request hits your controller:
#market = #platform.markets.find(params[:id])
Also, consider #market = Market.find(params[:id]) instead of the above.
You can add this to your Platform model
accepts_nested_attributes_for :markets

. I have a method (given below) that has a lot of queries. How can I optimize it?

I have a method (given below) that has a lot of queries. How can I optimize it?
def index
#users = User.all
#admin_users = User.where(role: 'admin')
#call_users = User.where(role: 'call_user')
#buy_users = User.where(role: 'buy_user')
#blocked_users = User.where(role: 'blocked_user')
end
All these depends upon how is the view page defined, if in the view page users can sort by different roles then it can written as
#users = User.where(role: params[:role])
Or if the goal is to display users with all these roles in view page, then you can pass the array of roles, which needs to be filtered out,.
#users = User.where(role: ['admin', 'paid_user', 'free_user', 'blocked_user'])
How can I optimize it?
Small question, big scope.
Enum
Firstly, you shouldn't be defining #user.role with a string.
What happens if you get the string wrong, or you wish to change the role? You need to store the roles separately to your db, allowing you to reference them (with an int). This can be achieved with either an enum or association:
#app/models/user.rb
class User < ActiveRecord::Base
enum role: [:admin, :call_user, :buy_user, :blocked_user]
end
An enum will give you access to a number of instance & class methods to optimize your queries:
#user = User.find x
#user.admin? #-> true
#user.call_user? #-> false
#user.buy_user? #-> false
#user.blocked_user? #-> false
User.admin #-> collection of admin users
User.call_user #-> collection of call_user users
User.buy_user #-> collection of buy_user users
In your case, it would get rid of the where clauses, allowing you to call:
def index
#users = User.all
#admin_users = User.admin
#call_users = User.call_user
#buy_users = User.buy_user
#blocked_users = User.blocked_user
end
ActiveRecord
Secondly, you don't have lots of queries; you have lots of definitions.
I learnt from #Marek Lipka that ActiveRecord doesn't query when you define a variable, only when you output that variable in the view. It's called "Lazy Loading":
Active Record queries return relations to be lazy. There's basically no reason to actually tell the database to execute a query until the very last possible minute.
What if you never actually needed to use that query at all? What if you want to make it more complex before executing it? Relations give you that flexibility and make much more efficient use of your database's valuable time.
This means that each time you define a variable, ActiveRecord will only hit the db when it's needed.
Thus, you could get away with scoping your #users variable:
def index
#users = User.all
end
#app/views/users/index.html.erb
<% roles = %i(admin call_user buy_user blocked_user) %>
<% roles.each do |role| %>
<%= role %>
<%= #users.where(role: role).each do |user| %>
<%= user.name %>
<% end %>
<% end %>
... or if you were using an enum:
#app/views/users/index.html.erb
<% User.roles.each do |role| %>
<%= role %>
<%= User.send(role).each do |user| %>
<%= user.name %>
<% end %>
<% end %>
DB
As described by #Sasidaran, the best way to achieve this is to let the database do the work.
Apart from his code, the best thing you can do is to make sure you're only hitting the database for the data you're going to use. Let the db server compute what's required, Rails should only be used as a conduit.

How to implement update controller method

I am working on web app development using ruby on rails. I want to enable users to upload images for their favorite food. I have food_item model and a food_image model. In the food_item model:
has_many :food_images
has_many :food_portions #this is the work done by another teammate
I also define in the food_item controller:
def food_item_params
params.require(:food_items).permit(:name, :category, :description, food_images_attributes: [:id, :food_item_id, :avatar]).tap do |whitelisted|
whitelisted[:portion] = params[:food_items][:portion]
whitelisted[:price] = params[:food_items][:price]
end
There are two issues:
You need to save your updated object
You should be doing this within the bounds of resources (although not essential)
Resourceful
The first step is to ensure you're using this with the correct routes etc.
You shouldn't have an add_images method in your controller - you could achieve what you need with edit/update:
#config/routes.rb
resources :food_items do
resources :images #-> if necessary, this should be its own controller rather than adding needless methods to your other controller
end
You should use the following controller setup:
#app/models/food_item.rb
class FoodItem < ActiveRecord::Base
accepts_nested_attributes_for :food_images
end
#app/controllers/food_items_controller.rb
class FoodItemsController < ApplicationController
def edit
#food_item = FoodItem.find params[:id]
#food_item.food_images.build
end
def update
#food_item = FootItem.find params[:id]
respond_to do |format|
if #food_item.update food_item_params
...
end
end
end
private
def food_item_params
params.require(:food_items).permit(:name, :category, :description, food_images_attributes: [:id, :food_item_id, :avatar]) #-> this is enough (no need to "whitelist")
end
end
This will give you the ability to load the following:
#url.com/food_items/:id/edit
#app/views/food_items/edit.html.erb
<%= form_for #food_item do |f| %>
= form_for #food_item, html: { :multipart => true } do |f|
= f.label :title
= f.text_field :title
= f.fields_for :food_images do |p|
= p.label :avatar
= p.file_field :avatar
.actions
= f.submit
<% end %>
This will submit to the "update" method, which should save the required object for you.
You should only upload one file at a time
If you need to upload multiple files, you'll need to use a gem such as cocoon to add them. Rails is great but not magical -- it has to build a single object with each fields_for.
I can explain more about this if required.
--
To give you context on why you should be using the edit / update methods for this, you need to look up the resourceful principle for object orientated programming.
This is built on the "resources" principle put forward at the inception of HTTP -- a standardized set of technologies which allow browsers to send and retrieve data from servers.
In short, it means there are certain conventions you should abide by to keep your app extensible.
Because Ruby/Rails is object orientated, everything you do in a well-tailored application should revolve around objects. Like resources, these allow you to create a system which is both flexible and extensible if done properly.
Thus, with your code, you have to remember that you're trying to add an image to the food items object. You should therefore be editing the food items object, updating it with the extra image; which the above code will help you achieve.
Like Badheka said: "why are you querying and just saving the #food_item?
Anyways, Notice that when you try to create food_images, you did:
food_item_params[:food_image]['avatar'].each do |a|
#food_image = #food_item.food_images.create!(:avatar=>a)
end
You were looping through the avatar in food_image attributes of food_item_params
However, if you check your food_item_params, you have:
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
With this, in all possibility, the structure of the data that this params is expecting will be something like:
{
food_items:
{
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: ""
}
}
}
while what is being returned by your food_item_params will be as follow:
{
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: ""
}
}
There are a number of issues with this:
1) The error you are getting: param is missing or the value is empty: food_items this suggests that the param that is coming in is not in the format specified above. the parent attribute food_items is missing, so that is the first part of your code that you need to rectify from inside the view where you are sending the avatar, or conform your food_item_params method to meet the structure of what is coming in through the params.
2) There is no food_item_params[:food_image]['avatar'] to loop on, since thee is no food_image in your params. What you have is food_image_attributes, so your loop will be on food_item_params[:food_image_attributes][:avatar]
3) Looping through the food_item_params[:food_image_attributes][:avatar] suggests that it is an array. If so, then the way you are permitting it is wrong too. You will have to specify that it is an array from inside the food_item_params method as follow:
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, avatar: []])
end
If however, as I suspect, what is coming through your param is as follow:
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: []
}
Then your food_item_params method should be as follows:
def food_item_params
params.require(:food_images_attributes).permit(:id, :food_item_id, avatar: [])
end
I'll advise you check your params and paste what it looks like here.
And as a last word, check what you are sending from the view, and set your controller accordingly, or check what you want the controller to expect, and send same from the view.
You need to change food_item_params method :
From :
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
To :
def food_item_params
params.require(:food_item).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
Also, you can improve your code :
Add accepts_nested_attributes_for :food_images in food_item model, then make changes in controller's method
def add_image
#food_item = FoodItem.find(params[:id])
respond_to do |format|
if #food_item.update_attributes(food_item_params)
format.html { redirect_to #food_item, notice: 'Images uploaded successfully' }
else
format.html { redirect_to add_image_food_item_path}
end
end
end

SystemStackError, stack level too deep when trying to route to controller in rails

The error says
SystemStackError in FacilitatorController#index
but its not a problem with the controller because nothing happens if I change lines in there. It does get to the controller. I think it is probably a routing issue, but I'm not sure what is causing it.
Link
<%= link_to "Add Facilitators", facilitator_index_path(:course => #course.id), >:method => :get %>
Relevant routes
resources :facilitator
delete '/facilitator', to: 'facilitator#delete', as: 'facilitator_delete'
Some of Controller
class FacilitatorController < ApplicationController
def index
#course = Course.find(params[:course])
if params[:search_field]
#user = User.select{|user| user.email.downcase.include? params[:search_field].downcase}
else
#user = User.where('id not in (?)', #course.facilitators)
end
end
end
I think it might have something to do with the Courses model having facilitators through an alias, and that conflicting with the facilitator controller?
class Course < ActiveRecord::Base
...
has_many :facilitate_ownedcourses, foreign_key: :ownedcourse_id
has_many :facilitators, through: :facilitate_ownedcourses, source: :facilitator
Can anyone help ?
First, You have a typo in your link_to, should be :method and not >:method
Also in your controller
#user = User.select{|user| user.email.downcase.include? params[:search_field].downcase}
should be something like
#user = User.all.select{|user| user.email.downcase.include? params[:search_field].downcase}
or still better
#user = User.where('email like ?', "%#{params[:search_field].downcase}%")
To handle the case you can change like for ilike if you are using postgres, or you can use lower(email) if you are using mysql
First, you have a syntax error into "Add Facilitators" link, assuming it's typing mistake.
Secondly, back track would helpful to provide suggestion but couple of suggestion below:
Check any finder call back
Iteratively calling a method, may be during before_action from controller.

Resources