I have a piece of code which displays a complicated 'listing' of resources. by complicated i mean that it has name, date, time posted, number of comments, picture, description, and tons more things that you obviously only want to write that block of code one time.
I'm trying to think of how this would go into the rails architecture and I'm at a loss
i need to display the resource listing using a different variable in different controllers. currently its in a partial called _resource_view.html.erb
This is fine for the main listing, but its not usable for the persons profile listing, which takes the same format but shows different resources
_resources_expanded.html.erb
<ul class="res-list">
<% #resources.each do |resource| %>
... list stuff
<% end %>
</ul>
It uses #resources on index, but on the profile page it uses the same listing code except its #user.resources.each do |resource|
I was thinking maybe something like this but this seems redundant .. and im not even sure if it will work
<ul class="res-list">
<% #resources.each do |resource| %>
<%= render 'layouts/resources_expanded' %>
<% end %>
</ul>
Don't use instance variables, use local variables in the partial - that lets you pass in a single each time through the #resources.each loop.
<%= render 'layouts/resources_expanded', :resource => resource %>
and then in _resource_view.html.erb change #resource to resource
Related
I'm pretty new to ruby/rails so bear with me.
I'm attempting to take the results returned by the JIRA rest API and render them in a view. I can do that pretty easily using the jira-ruby gem. The problem I'm having is grouping the results by a specific object inside the object returned by the API (in this case, a "components" field object inside of a "issue" object). I've attempted to use group_by and chunk for this but I'm basically getting the inverse of what I want. Both methods return the same result.
In my controller I have:
#issues = #jira_client.Issue.all
In my view I have:
<% #issues.chunk {|issue_comp| issue_comp.fields["components"]}.each do |comps, issues| %>
<h2>
<% comps.each do |comp| %>
<%= comp["name"] %>
<% end %>
</h2>
<ul>
<% issues.each do |issue| %>
<li><p><%= link_to issue.key, "http://localhost:2990/jira/browse/#{issue.key}" %> - <%= issue.summary %></p></li>
<% end %>
</ul>
<% end %>
What I end up with is:
CompA CompB
IssueA
CompC CompD
IssueB
CompA CompC CompD
IssueC
etc.
What I want is:
CompA
IssueA
IssueC
CompB
IssueA
CompC
IssueB
IssueC
CompD
IssueB
IssueC
The object returned by the API is a pretty convoluted object (i.e. giant array of hashes inside arrays inside of hashes). So, I have to dig pretty deep to get at the component name.
I get the feeling that this shouldn't be as complicated as it seems but I have a terrible habit of making things more complicated than they need to be. What am I doing wrong here?
EDIT: I created a gist of the full dump that is returned with the above call. Notice the "components" array:
jira-ruby gem dump for all issues
I took a look at the data you're getting back from Jira. This is how it looks to me:
There is an outer array of Jira Issues.
Each issue has an "attrs" hash
Each "attrs" hash contains components.
If this understanding is correct, I think you are attempting to invert that structure so that you can get a complete list of components, then iterate over each of them, and show the Issues that belong to that component.
If that understanding is correct, you have two basic choices:
Check if you can ask Jira for that information (so you don't have to generate it yourself), or
Build your own data structure (in memory on in a local DB as you prefer):
Some sample code for building a useful structure in-memory:
# in a controller, model, or service class (as you wish)
#components = {}
#jira_issues_array.each do |jira_issue| # from your API call
jira_issues[:components].each do |jira_component|
#components[jira_component[:key]] ||= { name: jira_component[:name], issue_keys: [] }
#components[jira_component[:key]][:issue_keys] << jira_issue[:key]
end
end
In your view, you could iterate over #components like this:
# some html.erb file:
<h1>Components and Issues</h1>
<ul>
<% #components.keys.each do |component_key, component| %>
<li><%= component[:name] %>
<ul> <!-- nested -->
<% component[:issue_keys].each do |issue_key| %>
<%= #jira_issues_array.find { |issue| issue[:key] == issue_key }[:name] %>
<% end %>
</ul>
</li>
<% end %>
</ul>
Note: Like a typical lazy programmer, I haven't tried this out, but it's really intended to show how you might go about it. For example, each issue's name is embedded in the attrs section, so you'll need to dig that out a bit.
Finally, if anyone would find this useful, I use this to analyse and reformat JSON.
HTH - any questions or problems, post a comment.
I have a user profile page with a sidebar. I need to create more pages within the profile. For example, edit password, edit profile information, statistics, purchase history list, etc. I'm not sure how to proceed while keeping things DRY. I'm trying to get everything to be the exact same except the main content. While going through some tutorials I came across yield but it was mostly used in the application.html.erb to render navigation, footer, etc. I don't understand how to use it for "sub-views".
The way I'm doing it right now seems wrong.
Routes:
as :user do
# Routes to change password of signed in user
get 'user/password' => 'users/registrations#edit_password', as: 'edit_password'
# Routes to change user profile information of signed in users
get 'user/profile' => 'users/registrations#edit_profile', as: 'user_profile'
end
Views:
views\users\show.html.erb:
views\users\registrations\edit_profile.html.erb:
views\users\registrations\edit_password.html.erb:
All contain this 1 line
<%= render 'users/shared/profile' %>
views\users\shared\profile:
<%= render 'users/profile/sidebar' %>
<!-- Display Profile or Password based on route -->
<% if current_page?(user_path current_user) %>
<!-- User Profile -->
<%=render 'users/profile/adminPanels' %>
<% elsif current_page?(edit_password_path) %>
<!-- Password Reset -->
<%=render 'passwordForm' %>
<% else %>
<!-- Profile Edit -->
<%= render 'users/registrations/profileForm' %>
<% end %>
Basically what I wanted to do is keep all the surrounding layout but change the rendered content. Now that I need to add more, extending this if statement really seems like the wrong way to go.
Yeah this is definitely not the way to go, but it's good that you recognize that so no worries. As you guessed, the way to do this involves using layouts and yield. You can read about yield in this Rails guide.
While you can have a layouts like application.rb that your entire Rails app uses by default, you can also define layouts nested within this layout. This is described in the same Rails guide as above, towards the bottom.
This way, the stuff that is the same for your entire application is defined in the application layout, the stuff that is same for everything that is a user profile is defined in the users layout, and the stuff that is specific to each view is defined in there.
Side note: as the users layout is in the layouts folder, I acted as though you moved the _sidebar partial there as well since it is really a partial that belongs to the layout and should be near it.
views/layouts/users.html.erb
<%= render '_sidebar' %>
<%= yield :users_content %>
<%= render template: 'layouts/application' %>
views/users/show.html.erb
<% content_for :users_content do %>
put the view code specific to users/show here
<% end %>
views/users/registrations/edit.html.erb
<% content_for :users_content do %>
put the view code specific to editing a user registration here
<% end %>
etc.
The only thing that you may have issue with is that Rails is using the name of the controller to match the nested users layout and that may break for the registrations stuff if that's a different controller. You can fix that by explicitly calling render template: 'layouts/users' inside of those controller actions.
From what code snippet you've provided, the DRYest way would be to move
<%=render 'users/profile/adminPanels' %>
directly to the show.html.erb page after rendering shared/profile. Same thing for other views.
I have a view that is getting complicated, and I'm wondering I should be doing this different? Picture (or code) is worth a 1000 words, so heres the view...
<% #orientation_by_date[date].each do |orientation| %>
<% if current_user %>
<% if orientation.active? %>
<li><%= link_to orientation.class_time, new_orientation_registration_path(orientation) %>
(<%= orientation.current_number_seats %>/<%= orientation.seats %>)</li>
<% else %>
<li><%= orientation.class_time %>(Class full)</li>
<% end %>
<%= link_to "VIEW", orientation_registrations_path(orientation) %></li>
<% else %>
<% if orientation.active? %>
<li><%= link_to orientation.class_time, new_orientation_registration_path(orientation) %>
(<%= orientation.current_number_seats %>/<%= orientation.seats %>)</li>
<% elsif orientation.class_date.before Date.today %>
<li><%= orientation.class_time %>(Class Closed)</li>
<% end %>
<% else %>
<li><%= orientation.class_time %>(Class full)</li>
<% end %>
<% end %>
<% end %>
What you are looking at is a the front end calendar view of a scheduling application. Based on differnt states, you see different information in each day on the calendar, ie, the number of seats remaining, vs. 'Class Full' vs. something else for Admins. Should I be pulling this logic into my model or controller somehow?
There are lots of ways to skin the cat. Which is 'right' is as much about personal preferences as anything else. That said, here are a few ideas that you might want to consider.
Use partials for each type of user
This may or may not be your driving concern but the outermost layer of decision making is based on user type so it may make sense to build a partial for each type of user. In this case you might have 'active_user_orientation_view' and 'guest_orientation_view'. Doing that reduces (this section) of your view down to a single if-then-else statement with pretty clear indication of your intent -- registered users see one thing and guests see something else.
Wrap-up repeating code into helper methods
Two of the list items are generated using the exact same code. Make it DRY! As an example, I'd probably drop down into the OrientationsHelper (app/helpers/orientations_helper.rb) and add a #orientation_full_item helper like this
def orientation_full_item(orientation)
content_tag(:li) do
"#{orientation.class_time} (Class full)"
end
end
With that helper in place, the two lines rendering the "Class full" message could be reduced to <%= orientation_full_item(orientation) %>. You could do the same for the list item that provides a link to the registration form. For consistency, you might do it for all of the list items. That would give you a view that very clearly declares its intentions.
Consider using a Presenter
Rather than litter your model (business logic) with view-oriented convenience methods, a better choice would be to create a new class that accepts an instance of the class and provides the same convenience methods. This is what the Presenter pattern is all about. The advantage of it is that you very clearly organize your code along the lines of it's intention -- biz logic stays together and stays untangled from view logic. In this case you might provide an ActiveUserOrientationPresenter and a GuestOrientationPresenter class, each of which provides a #list_item convenience method capable of rendering out the list item with its appropriate contents.
The PragProg guys have a title written by Bruce Williams with some great suggestions on how to build robust view code that is probably worth the money and time invested. One of the available code snippets deals specifically with presenters. You can read it http://media.pragprog.com/titles/warv/present.pdf.
Write unit tests that nail down the contents of all those <li> items with XPath.
Grab Nokogiri, and use Nokogiri::HTML::Builder to write all that in Ruby:
builder = Nokogiri::HTML::Builder.new do |doc|
doc.ul {
doc.li('data 1')
doc.li('data 2') if oodles_of_poodles?
doc.li('data 3')
}
end
puts builder.to_html
Now that it's all in one language, you can refactor it freely without constantly tripping over the escape tokens needed to mix two languages together.
Using Rails 3.1.1 and Heroku.
I believe this should be a fairly easy fix but I cannot find (and easily verify) how to do this. I have a very slow controller (6 sec) Product#show, with lots of N+1 and other things I will have to solve.
The website is a two-column website (main-column and right-column) where the main content from Product#show is shown in one column and daily product are shown in the other, including a "Random Product from the Database".
What I want to do is to let the content in main-column that is created by Product#show be cached (and thus bypass the controller and win 6 seconds). I do, however, want the right column to be dynamic (and loaded for each page request).
If I use caches_page :show it will cache the entire website, including the right-column, which makes me have to expire the cache every day in order to be able to load a new Daily Product. Not a good solution.
If I use cache('product-show' + #product.slug) do it only caches the view (right?) and still have to go through the controller.
So, how can I solve this?
You can achieve this with fragment caching like below:
def show
if !fragment_exist?("main_content")
#products = Product.all
#users_count = User.count
end
#random_products = Product.order("RANDOM()").limit(10)
end
show.html.erb
<!--MAIN CONTENT-->
<% cache("main_content") do %>
<%= #users_count %>
<% #products.each do |product| %>
<%= product.name %>
<% end %>
<% end %>
<!--SIDE CONTENT-->
<% #random_products.each do %>
<%= product.name %>
<% end %>
Use fragment caching, and don't load things in the controller.
If you have a very complex query, let it live in the controller as a scope, and only evaluate it in the view.
If you have a complex process to do so the query must be executed, use a helper method.
If you manage to just load lazy queries in the controller, if the cache is hit none of them will be executed.
I'm very new to Ruby on Rails, so there's probably a simple solution I'm missing.
The tldr version - how do I display an Acts As Taggable On tag cloud of distinct (i.e. no repeating) tags assigned to all instances of a particular model on that model's index page?
The longer version - I have a model called Video in which I have successfully managed to implement a tagging feature using Acts as Taggable On and this fantastic tutorial.
What I'd like to do now is, on the Video's index page (index.html.erb), to display a summary of all the individual tags that a user has assigned to individual videos. For example, lets say I have three videos, each tagged as follows:
Video 1: great, banana, book
Video 2: small, great, apple
Video 3: rubbish, small, banana
I'd like the index page to display the following list of tags:
great, banana, book, small, apple, rubbish.
The code for my model (elided) is as follows:
class Video < ActiveRecord::Base
attr_accessible :tag_list # Lots of other fields in here as well, but not relevant
acts_as_taggable_on :tags
end
The code in my Video helper is as follows:
module VideosHelper
include ActsAsTaggableOn::TagsHelper
end
Finally, as per the gem's documentation, I've added the following code to my controller:
class VideosController < ApplicationController
def tag_cloud
#tags = Video.tag_counts_on(:tags)
end
end
So, what code should I be adding to the index page of my view? I tried the following, again as per the documentation:
<% tag_cloud(#tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
<%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
<% end %>
But this returns the following error when I go to the Video index page:
undefined method `empty?' for nil:NilClass
As I say, I'm obviously missing something simple, but I'm completely new to Rails (and Ruby) so I'm still finding my feet.
OK, after hacking about a bit, I think I've found a solution, in case anyone else wondering how to do this happens to stumble across this question.
However, please be aware that I am very much a beginner at RoR, so this is probably not the best solution - if I'm doing anything wrong, or if you have a better solution, feel free to let me know!
Add this code in your view to display the list of tags for a particular model in order:
#tags = ActsAsTaggableOn::Tag.all(:order=>'name')
<% if #tags.count > 0 %>
<ul>
<% #tags.each do |tag| %>
<li><%= link_to tag.name, tagged_url(:tag => tag.name) %></li>
<% end %>
</ul>
<% else %>
<p>There are no tags on the system.</p>
<% end %>
This results in a very basic display and, due to my inexperience I advise using this approach with caution - I'm sure it's not the best, or even the "safest", method, so beware!