I have element witch I want hide on specific pages, for example on pages located at app/views/users/ (there I have new.html.erb; edit.html.erb; show.html.erb. And I have div in my layouts/application.html.erb it will be shown on all pages, so I want to hide it.
I thought i can do it like this:
<% unless current_page?(new_user_path) || current_page?(user_path) %>
<div>Some content</div>
<% end %>
But it will give me an error, pretty obvious: for user_show method he need an id of the user, but we are not visiting pages where variable #user is present. Can you land me a help:
Any possibility to get around this error? (And I don't want to assign #user variable every where and I don't want make list of all page what are allowed)
Is there any other way to hide element on specific pages?
Not entirely sure what you are trying to achieve, but this is how you can guard from user not being present:
unless current_page?(new_user_path) || #user && current_page?(user_path(#user))
I think what you probably want is:
<% unless current_page?(controller: 'users') %>
<div>Some content</div>
<% end %>
By passing controller: 'users' you catch all routes (actions) for that controller, including the new/edit/show routes.
For more detail see the docs.
Related
I've only ever used a static default scope for my projects and my latest one I wanted to create a dynamic scope feature, where the user can click a button or use a dropdown menu to change the order they view a list.
so far I've come across a few methods like using unscoping and setting a new scope, and seen reorder. I'm looking at the docs but I also am unsure of how to make a user be able to choose. Would I use something like Link_to, or button_to in the corresponding HTML.erb file?
in my post.rb file it'd look like this
default_scope { order(created_at: :desc) }
scope :ordered_by_title, -> { reorder(title: :asc) }
scope :ordered_by_reverse_created_at, -> { reorder(created_at: :ASC)}
I added those other scopes under the default because I assumed one would set the scopes they wanted and the view would call/activate them once a user clicks or chooses it from the rendered page.
in my post_controller.rb
def index
#posts = Post.all
end
in my index.html.erb the view
I have the list of posts rendered this way, if it won't work with what I want to do can someone show me a better way to do it?
# <some way for user to choose those defined scopes and render the new page would go here>
<% #posts.each do |post| %>
<div class="media">
<div class="media-body">
<h4 class="media-heading">
<%= link_to post.title, post %>
<small> <%= post.body %> </small>
</h4>
</div>
</div>
<% end %>
There are basically two ways to do this:
purely front end, use js. You can use some existing js plugin with sorting abilities if you don't feel like writing your own (e.g. DataTables). You'll probably need to make a table with the different attributes available for this to work.
back end, use ajax. Write your own or use something like filterrific, which uses scopes for filtering and sorting.
Messages are displayed green if sent by the current user, and blue otherwise. Following Rails convention, where does that logic belong?
Introdution
The user will visit /group/:id to see the list of messages, so the corresponding view is views/groups/show.html.erb and the corresponding controller is controllers/groups_controller.rb.
The message we want to display are in an array in #group, as #group.messages. The array is sorted by timestamp.
The code to style the color of the message is not important, but for simplicity purposes we will say there are two class selectors (one for from and one for to) and we can simply add a class attribute to the div that a message is within to change its color.
Both the user's sent and received messages are held in the array #group.messages.
If we have an individual message stored in message, we can test if it was sent by the current user with:
if session[:user_id] == message.user_id
Problem
Messages are ordered by timestamp and will need to be displayed in that order. For this reason, I can't see any clean way of handling the logic in the controller.
I would like to keep as much logic as possible out of the views and especially out of the partials, but after considering the options for rendering sent and received messages in different ways, the cleanest option I've found is to put the logic in the message partial.
Handling the logic in the message partial:
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> <%= message.body %> </p>
</div>
<% else %>
<div class="from">
<p> <%= message.body %> </p>
</div>
<% end %>
Pros:
This method handles the logic with one if statement that is clean and simple
It allows us to make the code DRY because we won't have to use the logic anywhere else if we want it on other pages
Since every message only has a body, we don't have to make another partial to display messages without this formatting
Cons:
The logic is in the partial! I think people I'm working with or other programmers or even myself would first look in the controller then in the view then in the partial to make any changes or see the code
This doesn't feel like normal Rails convention
Handling the logic in the view:
Possibly two clean solutions -
1) Style the messages inside the logic or
2) Render a different partial for sent/received messages
Styling inside the logic:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> message.body </p>
</div>
<% else %>
<div class="from">
<p> message.body </p>
</div>
<% end %>
<% end %>
Rendering different partials:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<%= render :partial => '/messages/sent_message', :message => message %>
<% else %>
<%= render :partial => '/messages/received_message', :message => message %>
<% end %>
<% end %>
Pros:
Either view solution keeps the logic out of the partial
It makes sense that showing something as one color or another is decided in the view
The view solution using two partials is clean and allows us to avoid styling within logic which also means that we can change the style within the partials and affect the look of messages everywhere.
Cons:
Both view options mean that our code is no longer DRY. Using these methods will mean that if we want the same functionality on 3 other pages, we will have to write the same code 3 more times
It makes sense that a view shouldn't be deciding anything
The view solution using two partials means that we will crowd the views/messages folder with partials, and still not have a default partial for rendering messages
Both of the view solutions just feel dirty in my opinion
My main points about my solutions -
No option allows for the logic to be held within the controller
Placing the logic inside the view means that to provide the same functionality on multiple pages, the same code will be written in more than one place
The option that looks the cleanest and makes the most sense to me means putting logic inside a partial, and there must be a better way.. right?
None of the solutions seem like they follow Rails convention
Which of the three options I coded best follow Rails convention?
Is it possible to place the logic in the controller?
Is there a better way to design this so that there is a clear solution following Rails convention?
What you probably have realized is that each of the three versions you described is either not DRY or not scalable. You've done a great job analyzing pros and cons of each option, so there is very little for me to add there. :)
To add presentation functionality to your models, Rails community uses Presenters. There is a great article on Presenters here that explains more about them.
Basically, you'll want to have one partial for message:
<div class=<%=#presenter.css_class%>>
<p> <%= message.body %> </p>
</div>
Then Presenter:
class MessagesPresenter
def initialize(message, current_user)
#message = message
#current_user = current_user
end
def css_class
message.user == current_user ? 'to' : 'from'
end
private
attr_reader :message, :current_user
end
And controller:
#presenter = MessagesPresenter.new(#message, current_user)
Voila! The presenter is available in both views and partials and is a great place to stash all presentation logic.
Since the only difference in these examples in the CSS class, you're repeating yourself quite a bit. Can't you add or remove a class on the tag depending on whether the tag belongs to the current_user or not?
This is really a presentation issue, and you can handle this simple logic for displaying the correct CSS tag using decorators (http://johnotander.com/rails/2014/03/07/decorators-on-rails/). I recommend using Draper (https://github.com/drapergem/draper).
First, for simplicity, add a current_user helper method to application_controller.rb to return the authenticated user.
Add a Decorator:
MessageDecorator.rb
def recipient_class
user_id == current_user.id ? "to" : "from" # (user_id delegates to message object)
end
Now your views can have much cleaner logic
Views
Message Partial:
<div class="<%= message.recipient_class %>">
<p><%= message.body %></p>
</div>
collection partial in the main view:
<%= render partial: "message", collection: #messages, as: :message %>
Finally, call decorate on messages in your controller action:
#messages = #group.messages.decorate
EDIT
You can also use a simple helper method rather than a decorator:
def css_class_for_message(message)
message.user_id == current_user.id ? "to" : "from"
end
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.
When calling all posts for a user Posts.find(creator: current_user:_id), and the user hasn't made any...rails spits a "NoMethodError" for it...
What I want to do is have a pretty output for the user of "Why, no. You haven't posted anything, you lazy slob." instead of this scary error.
What's the best way to handle things like this?
You need to use where instead of find. By design, find method expect to actually find an existing thing you're looking for. Consult docs about querying here.
Also, you can try and use try method. Basically it's equal to the following:
object.try(:something_scary)
# is equal to
object && object.something_scary
This is how I handle nil entities. If you want to show some kind of message to user (about being slobby) you make a check inside of your template and render different partials. Example:
<% if #posts.present? %>
<%= render 'posts' %>
<% else %>
<%= render 'no_posts' %>
<% end %>
Then you can put your message inside of that no_posts partial.
In my app, I've got a little box that appears on every page, checking on the status of requests made by the user. If a request is accepted at any time, then the user should automatically be taken to a certain page. This is my code so far:
<% offersMade.each do |w| %>
<% if w.accepted == true %>
<% redirect_to offer_path(:email => "email#gmail.com") %>
<% end %>
<% end %>
But I'm getting this error:
undefined method `redirect_to' for #<ActionView::Base:0x1042a9770>
Is it not possible to user redirect_to in a view? If not, is there something else I can use? Thanks for reading.
redirect_to is a method of ActionController::Base Class so you can not use it in ActionView.
You can try following
<% if w.accepted == true %>
<script type="text/javascript">
window.location.href="/logins/sign_up" // put your correct path in a string here
</script>
<% end %>
Edited for the email parameter
window.location.href="/logins/sign_up?email=<%= w.email %>"
Sorry i don't know if there is anything in ruby for that.
If you want to use redirect_to from view , do this method:
syntax : <%controller.redirect_to path %>
Example:<% controller.redirect_to users_profile_path %>
The code in the view could be moved into the controller which would make redirect_to available.
class ApplicationController < ActionController::Base
before_action :check_for_accepted_offer
def check_for_accepted_offer
if Offer.any? { |o| o.accepted }
redirect_to offer_path(:email => "email#gmail.com")
end
end
end
If the OP wanted to immediately change the URL when an offer is accepted then none of the Ruby code shown in the answers would help because it is only evaluated when the page is loaded. The OP would need to setup some kind of polling or push strategy to alert the browser when an offer has been accepted and then use the JavaScript redirect scheme posted in another answer:
window.location.href="/logins/sign_up?email=<%= w.email %>"
Although, this does not answer the question: "How can I use redirect_to in a view", I think this answer would ultimately have been more useful to the OP. I stumbled across someone using this answer to redirect to another page, when the redirect should have been performed in the controller.
redirect_to is not a method of ActionView. Its a method of ActionController. You can probably use Javascript window.location.href on page load or some other event to take your user to another page.
Yes, you can call controller.redirect_to from your view to get what you want without having to render the whole response and then use javascript on the client to make a new request.
In your example, this would look like:
<% offersMade.each do |w| %>
<% if w.accepted == true %>
<% controller.redirect_to offer_path(:email => "email#gmail.com") %>
<% end %>
<% end %>
Note that this controller.redirect_to will not break out of your loop, so you will probably want to break, and you'll probably want to make sure that the rest of your view is only conditionally rendered if you didn't redirect.
(Disclaimer: I don't necessarily condone this technique. You would be better off doing this in your controller or helper, as others have mentioned.)