Im trying to make a category system with parents. Here is how my database is structured.
- id
- name
- parent_id
Basically what I want it to do is check if there is a parent id, and if so then check if the parent id, and if that parent has a parent id, and so on. Then output the parents in order until it reached the child.
Currently what I am doing (in Ruby/Ruby on Rails) is this:
<% #cat = Category.find_by_id(#project.category) %>
<% if #cat.parent_id.nil? %>
<%= #cat %>
<% else %>
<%= Category.find_by_id(#cat.parent_id).name%> => <%= #cat.name %>
<% end %>
The problem with this is that it only gets one parent. I need it to recurse and constantly gather the parent until it hits and end, and also I dont want it to take up too much memory. Any help would be appreciated. Thanks! :)
Add a method on your model to search for the parent
class Category
# inline, less memory usage
def last_parent
last = self
while last.parent
last = last.parent
end
last
end
# recursive, better readability
def last_parent_recursive
if parent
parent.last_parent_recursive
else
self
end
end
end
then, on your view:
<%= #cat.last_parent %>
Related
My portfolio_controller.rb has an index method like this:
def index
#portfolio = PortfolioItem.all
end
How can I specify in the condition that the code in this block should be executed 6 times? In other words, how can I access exactly 6 values from the #portfolio object in my view, using a loop? This is what I have so far:
<% #portfolio.shuffle.each do |portfo| %>
Using all, followed by shuffle, is a bad solution for two reasons.
A slight improvement would be to use sample(6) instead of shuffle.first(6), as this removes a step from the process.
However, the bigger issue here is that Portfolio.all.<something> (where the <something> method requires converting the data into a ruby Array) will fetch all of the data into memory - which is a bad idea. As the table grows, this will become a bigger performance issue.
A better idea is to perform the "random selection" in SQL (with the order and limit methods), rather than in ruby. This avoids the need to fetch other data into memory.
The exact solution is database-specific, unfortunately. For PostgreSQL and SQLite, use:
Portfolio.order('RANDOM()').limit(6).each do |portfolio|
Or for MySQL, use:
Portfolio.order('RAND()').limit(6).each do |portfolio|
You could define this as a helper in the Portfolio model - for example:
class Portfolio < ApplicationRecord
# ...
scope :random_sample, ->(n) { order('RANDOM()').limit(n) }
# ...
end
And then in your view:
#portfolio.random_sample(6).each do |portfolio|
You can something like this :
<%(1..6).each do |i| %>
<% #your statements %>
<%end%>
<% #portfolio.shuffle.each_with_index do |portfo, index| %>
<%if index <= 6%>
<p><%= portfo.title %></p>
<%end%>
<% end %>
Or You can do it as
<% #portfolio.shuffle.take(6).each do |portfo| %>
<p><%= portfo.title %></p>
<% end %>
So I've got two records from Model, a and b. I want to do this:
def do_codependant_stuff(a,b)
a.attribute += b.attribute2
b.attribute = b.stuff+a.stuff
end
I keep hearing fat model, skinny controller, and no business logic in views, so I've put this in my Model model. Based on what a user clicks in one of my views, I want to call do_codependant_stuff(a, b) or do_codependant_stuff(b,a).
I've been just using the basic crud controller actions up to this point, and I'm not quite sure how to make this happen. Do I add this logic to my ModelController update action? Because it's technically updating them, just in a more specific way. Or make another action in the controller? How do I call it/set it up? Most of the default new, update etc are somehow called behind the scenes based on their respective views.
And is updating two things at a time bad practice? Should I split the do_codependant_stuff method into two instance methods and call them on each record with the other as a parameter?
Thanks for reading.
Edit:
Alright, real world code. I'm displaying on the home page of my app two pictures. The user selects the one they like most. The ratings of these pictures change based on a chess ranking algorithm. This is the relevant section of my picture class.
class Picture < ActiveRecord::Base
...
...
def expected_first_beats_second(picture first, picture second)
1/(1+10**((second.rating-first.rating)/400))
end
def first_beat_second(picA,picB)
Ea = expected_first_beats_second(picA,picB)
picA.rating += 50*(1-Ea)
picB.rating += 50*(-(1-Ea))
end
end
The partial I'm using for the view just displays two pictures at random so far with
<% picA = Picture.offset(rand(Picture.count)).first %>
<% picB = Picture.offset(rand(Picture.count)).first %>
<div class = "row">
<div class = "col-md-5">
<%= image_tag picA.url, :class => "thumbnail" %>
</div>
<div class = "col-md-5">
<%= image_tag picB.url, :class => "thumbnail" %>
</div>
</div>
I need to somehow link the onclick of those images to the method in the model.
Here's some code to get you started. Note the comments, they're ruby and rails best practices
Controller:
class PC < AC
...
def compare
# count query once, save the number
count = Picture.count
#pic_a = Picture.offset(rand(count)).first
#pic_b = Picture.offset(rand(count)).first
end
def compare_submit
# Note variables are snake cased, not camel cased
pic_a = Picture.find(params[:winner_id])
pic_b = Picture.find(params[:loser_id])
pic_a.beats(pic_b)
redirect to compare_pictures_path # Or wherever
end
...
end
Any sort of querying should be done in the controller or model. You essentially should never directly access a model in your view. At this point your view has access to the instance variables set in the controller: #pic_a and #pic_b
View:
<%= link_to compare_submit_pictures_path(winner_id: #pic_a.id, loser_id: #pic_b.id) do %>
<%= image_tag #pic_a.url, :class => "thumbnail" %>
<% end %>
<%= link_to compare_submit_pictures_path(winner_id: #pic_b.id, loser_id: #pic_a.id) do %>
<%= image_tag #pic_b.url, :class => "thumbnail" %>
<% end %>
So we just linked to a new path (see routes below) that will pass two parameters: winner_id and loser_id so whichever picture the user clicks on you'll know which one they chose and which one they didn't choose.
Model:
class Picture < AR::Base
# Method arguments don't need a type declaration
def beats(loser)
# Always lowercase variables
ea = 1/(1+10**((loser.rating-self.rating)/400))
self.rating += 50*(1-ea)
loser.rating += 50*(-(1-ea))
self.save
loser.save
end
end
This explicitly uses self for clarity, which isn't necessary. Calling save or rating = ... implicitly calls it on self since we're in the context of an instance method on the picture Model.
Routes:
resource :pictures do
collection do
get :compare
get :compare_submit
end
end
I know that a view shouldn't have any knowledge, of the model or the controller but i am not to sure how to avoid it.
The issue is i am trying to fetch the information from the controller or the model before i need to manipulate it in my view.
Here the code
Model
USER LOCATION ARTICLE
id user_id title
first article_id ...
last
...
Relationship
user has many locations
article has many locations
location belongs to user
location belongs to article
controller
#userlocation = #user.locations
View
<% #userlocation.each do |event| %>
...
<% evTitle = Article.find_by_id(event.article_id) %>
<%= evTitle.title %>
<% end %>
Obviously its not the best not sure how to do the query in the controller, and evTitle doesnt report properly why?
In controller you can do like this:
#userlocations = #user.locations
#locations_articles = Article.find_all_by_id(#userlocations.map(&:article_id).uniq)
Then you can use #locations_articles in view. Avoid using db queries in view.
<% #locations_articles.each do |article| %>
<%= article.title %>
<% end %>
You can first watch the rails cast multiple form episode at here :-
http://railscasts.com/episodes/196-nested-model-form-part-1
and there is an attribute that first you should define in model to access the information at single view form.
def new
#survey = Survey.new
3.times do
question = #survey.questions.build
4.times { question.answers.build }
end
end
In a Rails 3.2 app I have a model Project, which has many Tasks. Each Task has a :status field, which is an integer as follows
1=Normal
2=Urgent
In the Project show view, I want to display a text alert if any of the associated tasks are flagged as urgent.
If the status field was within the Project model, I would do something like this:
<% if Project.status == 2 %>
<div class="alert">URGENT TASKS!</div>
<% end %>
How can I set up a similar if statement, that will cycle through all associated Tasks, and return true if at least one task is marked as urgent?
I'm not sure what terms I should be searching on for this sort of functionality. Or maybe I'm not looking at the problem the right way. I'd be grateful for any pointers in the right direction.
Thanks
This method in Project will do it:
def urgent?
tasks.detect{|t| t.status==2}
end
Then you can do, if you have #project set to the project you're looking at:
<% if #project.urgent? %>
...whatever ...
<% end %>
This next bit was added in answer to your comment. This method in Project will return the highest priority set (lowest number in your example) for any task in a particular project:
def highest_priority
tasks.map{|t| t.status}.min
end
You can then switch between them in your view:
<% case #project.highest_priority
when 1 %>
...priority 1 stuff...
<% when 2 %>
...priority 2 stuff...
<% when 3 %>
...and so on...
<% end %>
I guess that you want to check if a project has some urgent task to be completed. If thats the case I think the best way to achieve that would be to create new method in the Project model, something like this:
def has_urgent_task?
tasks.map(&:status).include?(Task::URGENT)
end
Assuming you have defined your statuses as constants in your Task model, if not just replace Task::URGENT for 2.
So in your view you only need to do this:
<% if #project.has_urgent_task? %>
<div class="alert">URGENT TASKS!</div>
<% end %>
I'm porting a php app to rails so I have a set of sql statements that I'm converting to find_by_sql's. I see that it returns a collection of objects of the type that I called on it. What I'd like to do is then iterate through this collection (presumably an array) and add an instance of a specific object like this:
#class is GlobalList
#sql is simplified - really joining across 3 tables
def self.common_items user_ids
items=find_by_sql(["select gl.global_id, count(gl.global_id) as global_count from main_table gl group by global_id"])
#each of these items is a GlobalList and want to add a location to the Array
items.each_with_index do |value,index|
tmp_item=Location.find_by_global_id(value['global_id'])
#not sure if this is possible
items[index]['location']=tmp_item
end
return items
end
controller
#controller
#common_items=GlobalList.common_items user_ids
view
#view code - the third line doesn't work
<% #common_items.each_with_index do |value,key| %>
<%=debug(value.location) %> <!-- works -->
global_id:<%=value.location.global_id %> <!-- ### doesn't work but this is an attribute of this object-->
<% end %>
So I have 3 questions:
1. is items an Array? It says it is via a call to .class but not sure
2. I am able to add location these GlobalList items. However in the view code, I cannot access the attributes of location. How would I access this?
3. I know that this is pretty ugly - is there a better pattern to implement this?
I would get any data you need from locations in the sql query, that way you avoid the n+1 problem
#items=find_by_sql(["select locations.foo as location_foo,
gl.global_id as global_id,
count(gl.global_id) as global_count
from main_table gl
inner join locations on locations.global_id = gl.global_id
group by global_id"])
Then in the view:
<% #items.each do |gl| %>
<%= gl.location_foo %> <-- select any fields you want in controller -->
<%= gl.global_count %>
<%= gl.global_id %>
<% end %>
If you want to keep it close to as is, I would:
def self.common_items user_ids
items=find_by_sql(["select gl.global_id, count(gl.global_id) as global_count
from main_table gl group by global_id"])
items.map do |value|
[value, Location.find_by_global_id(value.global_id)]
end
end
Then in the view:
<% #common_items.each do |value,location| %>
<%=debug(location) %>
global_id:<%=value.global_id %>
<% end %>