I'm new to Rails and web development in general. I'm trying to better understand what are the options to render different buttons on my views depending on which user is visiting a page. I'm working with Rails 3.2.
2 specific cases for me:
On my movie page, I want to display a Bookmark button. The button should say "add bookmark" and not be checked if the user is not logged in or if the movie is not in user's bookmark list. The button should say "remove bookmark" and be checked (with an "active" css class) if the user added the movie in her bookmarks.
If user search for "action movie", the view renders a list of movies. Each item of the list should display an "add bookmark" / "remove bookmark" button depending on whether the movie is in the user's bookmark list.
What are best practices to manage my buttons text, actions, and display depending on my user status? Where does the code go (view, javascript...), and what key methods I would need to implement?
Well, best practice is to using something like Draper: https://github.com/jcasimir/draper which handles the responsibility of presenting the correct content based on a set of conditions.
This would work however, but should probalby be refactored into a Draper Decorator:
<% if user.has_bookmarked(#movie) %>
<%= link_to 'Remove bookmark', remove_bookmark_path%>
<% else %>
<%= link_to 'Add bookmark', add_boookmark_path %>
but again, in the decorator you could just do it once
def display_correct_bookmark_link_for_movie(movie)
if current_user.has_bookmarked(movie)
h.link_to 'Remove bookmark', remove_bookmark_path
else
h.link_to 'Add bookmark', add_boookmark_path
end
end
and then in your view you could just call:
<%= display_correct_bookmark_link_for_movie(#movie) %>
Otherwise, you'll be repeating that logic all over your views which is not good.
Obviously some of that is psuedo-code but I think you get my drift.
Related
I have a page where a deactivated user account can be activated - but before they can be activated, they need to be assigned a new password. Currently I have two buttons to perform these actions:
At the end of the 'add password' form there is this button:
<%= f.submit %>
A little further on the page there is this button, which sets the status of the user to 'active':
<%= standard_button 'Activate user', update_status_user_path(#user),
:method => :patch %>
If possible, I'd like add the functionality of the second button to the submit button.
I don't fully grasp the magic behind forms and their buttons, so I am wondering whether this is possible and if so, how I could accomplish this?
You can think of a button as a mini form. View the docs to get more information about the button_to rails method.
In your form_for, there will be a url which specifies where the form is posting to. In that controller method, you can include the logic to update the user's status to 'active'. Something like
def some_method
user = User.find(params[:user_id])
user.update_attribute(:status, "active")
end
I've been stuck on this problem for days. First off, I now know this code is horribly wrong. I've been trying to fix it, but it's way more important in the short term that this link is created. In my view (I'm so sorry), I call the create method like this, if a certain condition is met:
index.html.erb (controller: subjects_controller)
<%= Baseline.create(subject_id: sub.subject_id) %>
I do this several times on the page, from several controllers (i.e., FollowUp3Week.create(subject_id: sub.subject_id) works). All of the other controllers work. I've checked, and double checked, every controller action and compared them to each other, and they appear the same.
So instead of creating the record, it leaves something like this instead:
#<Baseline:0x007f944c4f7f80>
I'm at a bit of a trouble shooting loss. Once again, I know how wrong it is to have these database actions in the view. But I didn't know that when I made the page, and I really need this to function before I can take the time to learn how to rearrange everything through the MVC.
Any advice would be greatly appreciated. Let me know what other code you might want to look at.
EDIT 1.
link Creation:
<% if Baseline.where(subject_id: sub.subject_id).first != nil %>
<%= link_to "edit", baseline_path(Baseline.where(subject_id: sub.subject_id).first) %>
<% else %>
<%= Baseline.create(subject_id: sub.subject_id) %>
<% end %>
First of all, making DB calls in views is a big NO! NO!
Secondly, to answer why you see the output as
#<Baseline:0x007f944c4f7f80>
for
<%= Baseline.create(subject_id: sub.subject_id) %>
You are trying to render an instance of Baseline model. Its just how the instance would be displayed. If you want to display a particular attribute's value in view then just do
<%= Baseline.create(subject_id: sub.subject_id).subject_id %>
Also, this code will not create a link. To create a link you would have to call link_to helper in your view.
What you need to do is, move the Baseline.create call in the controller. Set an instance variable in the action which renders this particular view as below:
def action_name
#baseline = Baseline.create(subject_id: sub.subject_id)
end
After this in you view you can easily access all the attributes of #baseline instance.
For example:
To access subject_id
<%= #baseline.subject_id %>
To create a link for show page of #baseline, provided you have a RESTful route to show action for baselines
<%= link_to "Some Link", #baseline %>
Model:
Users have expenses. Expense has a status.
View:
As users add their expenses, they are shown in a list. Each expense row has a form button on the end, which is used to submit the expense (changing the status of the expense). This allows users to add expenses they have not completely filled out, and submit them when they are ready. There is no parent form on this page, just the form buttons which submit the expense to a method which changes the status, and then reloads the page.
Currently it works great, but users have asked to be able to "submit all" the expenses that are showing on the view with a single button.
Question:
What is the proper way to handle this in rails? Should I find a way to gather the array of expense id's and then submit a separate form? Is there a way to ask for a set of records present in a view with a certain status?
Thanks!
Another option, if I'm thinking about this right (big if), would be to wrap your page in a User form. Then you could have something like...
<%= form_for(#user) do |f| %>
<% #user.expenses.each do |expense| %>
<% f.fields_for expense do |e| %>
<!-- expense form -->
<% end %>
<% end >
<% end %>
This is something you could submit as a whole. I'm having trouble picturing what a single expense addition might look like, but hopefully this gets you a little further down the road.
Edit: in addition to having this User form on the page, you could have an "extra" Expense form to create an expense. When you submit a new expense, that expense appears in the list under the user form, where it can be edited or submitted, either as part of a group or individually (as part of a "group" of 1).
custom controller action:
def update_all_expense_statuses
expenses = current_user.expenses
ExpenseUpdater.new(expenses).update_expense
redirect_to :back
end
expense updater class:
class ExpenseUpdater
def initialize(expenses)
#expenses = expenses
end
def update_expense
#expenses.each do |expense|
expense.update_attributes(status: 'paid')
expense.save
end
end
end
This is just an example of one way to update all the user's expenses with a custom controller action. Just call the controller method from a link_to:
<%= link_to "Update all expenses", update_all_expense_statuses_path %>
Remember to add it to your routes. Hope this helps.
The first thing you should do is change the forms to submit remotely, ie make an ajax request. Then you're not reloading the whole page. Check out Rails' various "remote" form helpers, eg "remote_form_for".
Then, write a javascript function to submit all the forms for inputs that have changed since the page loaded. You'd probably want to add a "changed" (or similar) class to the parent form in an onchange event in each input, to facilitate this. I think this is the best way to handle the "status" thing you're asking about. Make a "Submit all" button which calls this function.
Use a form/service object http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ to encapsulate expense report
I'm working on a small picture application. That I'm trying to do is build a counter to track how many times each image is clicked.
Right now I have in my view:
<% #galleries.each do |g| %>
<% for image in g.images %>
<div id="picture">
<%= render 'top_nav'%>
<%= link_to g.source, :target => true do %>
<%= image_tag image.file_url(:preview) %>
<% g.vote %>
<% end %>
<%= will_paginate(#galleries, :next_label => "Forward", :previous_label => "Previous") %>
</div>
Obviously this doesn't work, as the g.vote executes every time it's rendered, not clicked. Here's the vote method in my model:
def vote
self.increment!(:score)
end
I'm looking for a solution to run the vote method only when the image above is clicked. The links are to external resources only, not to a show action. Should I be building a controller action that's accepts a post, executes the vote, then redirects to the source?
Anyway, looking for some ideas, thanks.
I've done something similar, but keeping a count of how many times a Download link was clicked. This was awhile ago and I didn't know about Ajax at the time, but now I would recommend using jQuery (a great library in my opinion, but you could use something else) and do an Ajax call when the image is clicked that would execute some controller action which would increment that vote.
The other way, which is what I did in my scenario, and is what you talked about there, is creating a custom action in the controller that accepts a post. But I have to ask as well, does clicking on the image do something else in the behaviour of your website? For example, if when you click the picture, another random image is supposed to come up, that means you'll already have an action to load a new image and it be easy to stick the vote up in there before showing a new image. Otherwise you'd have to create the new controller action. If that's the case, the Ajax would be more efficient as the user wouldn't see a momentary flash as the page was refreshed (especially bad if the refresh time is long).
Id like to be able to create checkboxes for a list of objects. Then offer the user a number of actions to perform on the objects selected. I.e. delete, archive etc.
I know of ryan's screencasts but it doesnt explain how to create links to multiple actions for the selected objects. It just showed him create a form_tag with a url to one action and a submit button.
I think you can do it in two ways.
First: you can add as many buttons to one form as you want:
<%= f.submit "Action 1" %>
<%= f.submit "Action 2" %>
<%= f.submit "Action 3" %>
And all of them are submitted to one action in which you can check:
if params[:commit] == "Action 1"
do some stuff for action 1
elsif params[:commit] == "Action 2"
do other stuff
... and so on
end
Another way is to use some js. On example when user clicks on button "Action 2" it changes "action" parameter in form and submits data to this action.
EDIT:
In case of translated websites, you can do it like this:
<%= f.submit (I18n.t :action_1) %>
and in controller:
if params[:commit] == I18n.t :action_1
...
end
And in en.yml add:
action_1: Action 1
In pl.yml add:
action_1: Akcja 1
and so on.
You can always change the name of the submit button. Just look for the params[:button_name] instead of params[:commit].
You'll need some sort of method in the controller to handle the ability to update_many objects. Maybe a before filter to dispatch...