I'm new to Ruby on Rails. I'm building out an app that does custom operations on defined nodes (the object). Here's my table:
<% #nodes.each do |node| %>
<tr>
<td><%= node.name %></td>
<td><%= link_to 'Bootstrap', node_path(node),
method: :bootstrap %>
</td>
</tr>
<% end %>
"Bootstrap" is the custom code that I want to run. I'm defined a custom method within my Controller:
class NodesController < ApplicationController
def bootstrap
......
end
........
end
How do I tie in my custom code to my Rails app so that when a user clicks on the link the code will be run against that object?
In config/routes.rb, you probably have:
resources :nodes
Change this to:
resources :nodes do
get 'bootstrap', on: :member
end
Then, run rake routes to see that you now have a new route method, bootstrap_node_path(node), and will link to /nodes/:id/bootstrap.
I recommend this over the other approach as it keeps your route details together, but that's just my personal opinion. I usually resort to custom routes as a last resort.
Related
I have a Project model with one to many association with Financial, Financial model has one to many relationship with PaymentMilestone model. I want to add new_payment_milestone_path on the financial index.html.erb, but I am not able to pass financial_id on button click.
Here is my code:
financial index.html.erb
<% #financials.each do |financial| %>
<tr>
<td><%= financial.responsibility %></td>
<td><%= link_to " ", new_project_financial_payment_milestone_url(#financial, payment_milestone) %></td>
</tr>
<% end %>
financials_controller.rb
def index
#financials = #project.financials
end
routes.rb
resources :projects do
resources :financials do
resources :payment_milestones
end
end
Don't go nuts when resting:
Rule of thumb: resources should never be nested more than 1 level
deep. A collection may need to be scoped by its parent, but a specific
member can always be accessed directly by an id, and shouldn’t need
scoping (unless the id is not unique, for some reason)
- Jamis Buck
Making a pyramid out of your routes like you have makes things far more complex then they really should be. To unnest payment_milestones from projects you need to change your routes to:
resources :projects do
resources :financials
end
resources :financials, only: []
resources :payment_milestones, shallow: true
end
This will create these routes:
financial_payment_milestones GET /financials/:financial_id/payment_milestones(.:format) payment_milestones#index
POST /financials/:financial_id/payment_milestones(.:format) payment_milestones#create
new_financial_payment_milestone GET /financials/:financial_id/payment_milestones/new(.:format) payment_milestones#new
edit_payment_milestone GET /payment_milestones/:id/edit(.:format) payment_milestones#edit
payment_milestone GET /payment_milestones/:id(.:format) payment_milestones#show
PATCH /payment_milestones/:id(.:format) payment_milestones#update
PUT /payment_milestones/:id(.:format) payment_milestones#update
DELETE /payment_milestones/:id(.:format) payment_milestones#destroy
And you then can change your view to:
<% #financials.each do |financial| %>
<tr>
<td><%= financial.responsibility %></td>
<td><%= link_to "New milestone", new_financial_payment_milestone_path(financial) %></td>
</tr>
<% end %>
You want to use the local variable financial which is passed to the block and not the instance variable #financial. You should also always provide a link text for accessibility.
add this to button path-
new_project_financial_payment_milestone_url(#project, financial)
I have a HABTM relationship between financing_campaign and financing_merchant.
In one specific view, there can be a link_to to 2 possible paths. If there is a #campaigns variable, then the path should be:
(to display the merchant in the context of the campaign)
financing_campaign_merchant_path(#campaign, merchant) -
/financing/campaigns/:id/merchants/:id
else, it should be:
(to display the merchant without the context of the campaign)
financing_merchant_path(merchant) -
/financing/merchants/:id
I'm trying to decide the best way to solve this. Currently we are using a ternary on the view, which seems ugly and confusing:
<% #merchants.each do |merchant| %>
<tr>
<td>
<%= link_to merchant.name, #campaign ? financing_campaign_merchant_path(#campaign, merchant) : financing_merchant_path(merchant) %>
</td>
...
</%end>
routes.rb:
namespace :financing do
resources :merchants
resources :campaigns, only: %i[index show edit update] do
resources :merchants, only: [:index, :show]
...
end
end
Thanks everyone!
Sometimes best to keep it simple. I think what you have is solid, particularly if you are only going to use it in one place.
If you want to beautify the view code, though, you could elect to move the ugliness to a helper class, i.e:
module FinancingHelper
def link_to_merchant(merchant, campaign=#campaign)
link_to merchant.name, campaign ? financing_campaign_merchant_path(campaign, merchant) : financing_merchant_path(merchant)
end
end
Then your view changes to:
<% #merchants.each do |merchant| %>
<tr>
<td>
<%= link_to_merchant merchant %>
</td>
...
</%end>
If #campaign is defined in context, it will be found in the helper and use it; otherwise, it will link to just the merchant. Or, you could explicitly pass a campaign parameter to check, but this just means more code to type, and it seems like you like the "less is more" approach!
I'm working on my first rails project and I have a problem that I just cannot figure out.
I generated a Scaffold for an object named Archive
to this object I added the method processfile
when I try to link_to said method from Archives#Index I'm getting this:
undefined method `processfile' for #<Archive:0x702de78>
This is the model archive.rb
class Archive < ActiveRecord::Base
belongs_to :users
attr_accessible :file, :user_id
mount_uploader :file, FileUploader
end
This is the code on the index.html.erb (belonging to archives)
<% #archives.each do |archive| %>
<tr>
<td><%= archive.file%></td>
<td><%= User.find(archive.user_id).name %></td>
<td>
<%= link_to 'Download', archive.file_url %>
::
<%= link_to 'Show', archive %>
::
<%= link_to 'Edit', edit_archive_path(archive) %>
::
<%= link_to 'Delete', archive, confirm: 'Esta Seguro?', method: :delete %>
::
<%= link_to "Process", archive.processfile %>
</td>
</tr>
<% end %>
this is the routes.rb line:
match "archives/processfile/:id" => "archives#processfile", :as => :processfile
the processfile method defined whitin archives_controller.rb doesn't have anything on it, i just wanted to test the functionality since I'm having a hard time getting the grip of the "rails way"
archives_controler.rb
def processfile
# #archive = Archive.find(params[:id])
#do something with the archive
end
All in all, what I ultimately want to achieve is to call the processfile method on a given archive(taken from the index table) to do something with it. On the example, I watered down the method call (not passing an archive or archive.file to it) to make it run, to no avail.
I've searched a lot (on google and in here) and haven't found a clear guide that would address my problem, probably because i'm new and can't fully grasp the concepts behind rails MVC.
I've read something about methods only being accessed by same controlers but I've seen sample code when people call methods on controllers from index views without declaring them as helpers. o.0
I know it's probably a silly confusion, but I can't figure it out :(
The way you've structured your route (i.e., match "archives/processfile/:id" => "archives#processfile") means that it's expecting an archive id to be passed. You need to adjust your link_to to pass one:
# app/archives/index.html.erb
<%= link_to "Process", processfile_path(archive.id) %>
The error you're receiving is because you're trying to call an instance method called processfile on archive, but there's presumably no method by that name. The second parameter of the link_to helper is a path, not an instance method.
EDIT:
If you're looking to make your routes more RESTful (which you should do if you've created an Archive resource), you can generate all your CRUD routes by declaring resource :archives in your routes. Then, within a block, you can declare a block of member routes, all of which will route to the specified action in your archive_controller.rb and enable you to pass an archive id to the action.
# config/routes.rb
resources :archives do
member do
get 'processfile'
end
end
You added the processfile method to your ArchiveController. That does not make the method available to the Archive model. If you want the method to be available to instances of Archive models then you need to put it inside the model as an instance method.
If you what you want to do is place a route to the action processfile in your ArchiveController then you can do so by adding link_to "Process", processfile_path(id: archive.id)
I think I'm overlooking something very simple here, but would really appreciate some help to work out what it is.
In the project show view, I'm displaying associated (has_many) tasks in a partial. I only want to display those records where a particular field is not empty. My view code looks like this.
<% for task in #tasks %>
<% unless task.user.notes.empty? %>
<tr>
<td><%= task.user.name %></td>
<td><%= task.user.notes %></td>
</tr>
<% end %>
<% end %>
This is returning undefined method 'notes' for nil:NilClass. This is strange as :notes is definitely in the User model.
The Project controller handling this is contains:
def show
#tasks = #project.tasks.paginate(:page => params[:page])
end
My models look as follows
Project
has_many :tasks
end
Task
belongs_to :project
belongs_to :user
end
User
has_many :tasks
end
What have I missed here? Am I using empty? correctly? Or should I be handling this in the controller? I currently have three partials on the Project show, all using the same Task query. Performance and/or best practice -wise, does it make more sense to have all three partials sourcing data from the same controller query, or to have a sperate query just for this case?
Thanks for any pointers.
The problem was that your User model was undefined when you called task.user.notes.
You can solve this problem as well as improve your overall design by making use of the #delegate macro provided by ActiveSupport in Rails. For example, inside of the Task model, try adding
delegate :notes, :to => :user, :prefix => true, :allow_nil => true
This adds a task.user_notes method to the Task model which will allow you to fetch the User's notes straight from the Task itself. Additionally, because we specified the allow_nil option, if there is no User associated with the Task, the result of the method will be nil instead of an exception.
You can also add this for the name attribute of the User allowing you to call task.user_name from within the view.
Resources:
delegate - http://apidock.com/rails/v3.0.9/Module/delegate
"Law of Demeter" - http://devblog.avdi.org/2011/07/05/demeter-its-not-just-a-good-idea-its-the-law/
In the controller
def show
#tasks = #project.tasks.paginate(
:page => params[:page],
:conditions=>["notes is not ? and notes !=?",nil,'']
)
end
OR, not in the controller
Write a helper method to abstract this.
Add a new helper method
def filter_tasks(tasks)
tasks.find(
:all,
:conditions=>["notes is not ? and notes !=?",nil,'']
)
end
And use helper in view
<% for task in filter_tasks(#tasks) %>
<% unless task.user.notes.empty? %>
<tr>
<td><%= task.user.name %></td>
<td><%= task.user.notes %></td>
</tr>
<% end %>
<% end %>
I have just installed the "on_the_spot" gem following the github instructions.
And I'm trying to create a in-place edit for my index action. When I get the mouse over the text that should be editable, nothing happens. Only the background color changes.
This is the relevant code from my index view
<% #part_types.each do |part_type| %>
<tr>
<td><%= on_the_spot_edit part_type, :title %></td>
</tr>
<% end %>
From the controller:
class PartTypesController < ApplicationController
#on_the_spot for in place editing
can_edit_on_the_spot
#.. rest of the controller code
end
Added to the routes:
resources :part_types do
collection do
put :update_attribute_on_the_spot
end
end
nginx already restarted after these changes.
Thank you
Inside your Gemfile add the following:
gem "on_the_spot"
Run the installation task:
rails g on_the_spot:install
if u using rails 3.1 replace created files to app/assets/javascripts (two files jquery.jeditable.mini and on_the_spot)