link to nested ressource in each - ruby-on-rails

I have an Array of various ActiveRecord Objects which are Objects of different Models. One of them is called Team which is a nested ressource of Department:
resources :departments do
resources :teams
end
So when I use this in the array.each like this:
array.each do |element|
link_to element.name, element
end
It throws an error that team_path doesnt exist whats logical because of nested ressources the route is called department_team_path but I cant call this method absolutely because I also treat Objets of other Models in this each.
One posibility I see is to add a Route called team_path whih refers to Team#Show but thats not pretty and also bad for the SEO. Is there another better possibility to link to this and other models in one course?

array.each do |element|
if element.is_a?(Team)
link_to element.name, url_for([element.department, element])
else
link_to element.name, element
end
end
as per Rails Guides. Alternatively, you can use resources :departments, :shallow => true but like you mentioned that will give undesirable results for SEO.

try this:
link_to element.name, url_for(element)

I wrote my own methods to deal with this problem. They are located in ApplicationHeler
def nested_name(object)
routes = Rails.application.routes.named_routes.to_a
name = nil
routes.each do |r|
name = r[0].to_s if r[1].requirements[:controller] == object.class.to_s.downcase.pluralize && r[1].requirements[:action] == "show"
end
name
end
def nested_object(object)
name = nested_name(object)
parent = name.gsub("_", "").gsub(object.class.to_s.downcase, "")
if object.class.reflect_on_association(parent.to_sym)
return [object.send(parent), object]
else
return object
end
end
So I can do the following:
array.each do |element|
link_to element.name, nested_object(element)
end
It works pretty good for me now,...

Related

Dynamic url_for with varying amount of nesting

I'm building a helper method that allows me to pass an array with options I would like to be available for the management of an object (like edit, delete, etc.). A simplified version of the method looks like this:
def management_links(instance, actions, *parent)
actions.each do |action|
if (can? action, instance)
has_options = true
case action
when :destroy
options = {content: glyphicon('trash') + " Delete #{instance.class.to_s}", class: "delete #{instance.class.to_s.downcase}", method: :delete}
url = url_for [parent, instance]
end
end
end
end
As you can see this works perfectly for objects that are nested once (passing 1 parent model) to get the structure:
parent_model/parent_id/model/id/action
But now I have a model that is nested twice, so this won't cut it anymore. I tried passing an array [#grandparent, #parent], but that doesn't work since the url_for already has an array.
Is there some way to allow me passing 'unlimited' parent objects to work with the url_for?
*parent will always be part of an array (if present), so why not declare it as such and then push instance into it:
def management_links(instance, actions, *parent)
parents = Array(parent) if parent
new_url = parents ? parents << instance : instance
actions.each do |action|
if (can? action, instance)
has_options = true
case action
when :destroy
options = {content: glyphicon('trash') + " Delete #{instance.class.to_s}", class: "delete #{instance.class.to_s.downcase}", method: :delete}
url = url_for new_url
end
end
end
end
I use Array() to ensure that parent is the correct data type (you may pass a single instance of the var).
Off topic, but in the pursuit of convention, you should read up about nesting by more than one layer:
Resources should never be nested more than 1 level deep.

Ruby on Rails 4 fields_for number of repetitions

I would like to display a form with four nested fieldsets for associated objects. The only way I've found is to override the initialize method and define four associations:
RUBY
def initialize(attributes = {})
super
4.times { items << Item.new }
end
and then display nested fields normally:
HAML
= f.fields_for :items do |item|
= render 'item_fields', f: item
This is not working when I try to edit objects that already exist and have fewer number of associated items.
Any help will be appreciated.
MORE INFO:
Order has_many items
OrderSet has_many orders
Orders are added through the cocoon gem (there is at least one order in each set)
There should always be four items for each order. But when there are less items I don't want to save empty records, instead I would like to just display remaining items as empty.
The initialize is not the place as it is executed every time a new Order instance is created, this means: also when retrieving an existing order from the database.
Imho the view is also not the optimal place.
I would solve this in the controller:
def new
#order = Order.new
4.times { #order.items.build }
end
and then you can just leave your model/view as they were originally.
If you always want to show 4 nested items, you can do something similar in the edit action (to fill up to 4)
def edit
#order = Order.find(params[:id])
(#order.items.length...4).each { #order.items.build }
end
In my personal opinion this is cleaner then doing it in the view.
[EDIT: apparently it is a double nested form]
So, in your comment you clarified that it is a double-nested form, in that case, I would use the :wrap_object option as follows (it gets a bit hard to write a decent example here, without you giving more details, so I keep it short and hope it is clear). I am guessing you have a form for "something", with a link_to_add_association for :orders, and that order needs to have several (4) items, so you could do something like:
= link_to_add_association('add order', f, :orders,
:wrap_object => Proc.new { |order| 4.times { order.items.build}; order })
Before your f.fields_for in your view, or even in your controller, you can check the length of .items() and create new objects as required:
(o.items.length...4).each { f.object.items << Item.new}
= f.fields_for :items do |item|
= render 'item_fields', f: item

Rails: if record exists then use this record

How do I use the result of an if condition in Rails? Something like:
if #edits.where(:article_id => a.id).first
THIS.body.html_safe
else
a.body.html_safe
end
How on earth do I access the result of that condition? That being the THIS record?
Thanks!
You can do the assignment within the if statement.
if edit = #edits.where(:article_id => a.id).first
edit.body.html_safe
else
a.body.html_safe
end
You could write in one line:
(#edits.where(:article_id => a.id).first || a).body.html_safe
Putting such logic in view or helper is very ugly. It's not View's job to judge these.
Better alternative:
# Article model
def default_edit
edits.first
end
# Articles Controller
def show
article = Article.find(params[:article])
#article = article.default_edit || article
end
# view: no need to do anything, just plain obj
<%= #article.body %>
You can use find_by or find_by_* to avoid the nasty .where().first
if edit = Edit.find_by(article_id: article.id)
edit.body.html_safe
else
article.body.html_safe
end

Rails: list all links for new object creation

Is there any elegant way of displaying a list of all links for new object creation?
Right now I solved it getting all routes with
routes= Rails.application.routes.routes.map do |route|
{alias: route.name,
path: route.path.spec.to_s,
action: route.defaults[:action]}
end
taken from How to programmatically list all controllers in Rails.
Then filtered and prepared links using
routes.each do |r|
if r.reject { |k, v| v != 'new' } != {}
puts %{ #{r[:alias]} }
end
end
I looked around and I guess there is no built-in method for this. Still, I'm looking for a shorter and more concise solution.

Rails 3 displaying tasks from partials

My Tasks belongs to different models but are always assigned to a company and/or a user. I am trying to narrow what gets displayed by grouping them by there due_at date without doing to many queries.
Have a application helper
def current_tasks
if user_signed_in? && !current_company.blank?
#tasks = Task.where("assigned_company = ? OR assigned_to = ?", current_company, current_user)
#current_tasks = #tasks
else
#current_tasks = nil
end
end
Then in my Main view I have
<%= render :partial => "common/tasks_show", :locals => { :tasks => current_tasks }%>
My problem is that in my task class I have what you see below. I have the same as a scope just named due_today. when I try current_tasks.due_today it works if I try current_tasks.select_due_today I get a undefined method "select_due_tomorrow" for #<ActiveRecord::Relation:0x66a7ee8>
def select_due_today
self.to_a.select{|task|task.due_at < Time.now.midnight || !task.due_at.blank?}
end
If you want to call current_tasks.select_due_today then it'll have to be a class method, something like this (translating your Ruby into SQL):
def self.select_due_today
select( 'due_at < ? OR due_at IS NOT NULL', Time.now.midnight )
end
Or, you could have pretty much the same thing as a scope - but put it in a lambda so that Time.now.midnight is called when you call the scope, not when you define it.
[edited to switch IS NULL to IS NOT NULL - this mirrors the Ruby in the question, but makes no sense because it will negate the left of the ORs meaning]

Resources