I'm trying to make an app in Rails 4 and using Statesman for states.
In my project model, I want to display the :current_state of an object. However, I don't want the name of the attribute to appear. Instead, I want to write a human friendly state that can be rendered if the object is in the corresponding state.
For example, I have defined a state called :request_approval.
In my projects show page, I can write:
<%= #project.current_state %>
and the output is request_approval.
How can I write something that says if project is in current_state :request_approval, render: Awaiting a response to your request for approval? Can I make some kind of method in my model to do that?
In your projects_helper you can write a method:
def text_for_state(state)
case state
when 'request_approval'
'Awaiting Response'
when 'something_else'
'Then Something Else'
end
end
And in your view just write:
<%= text_for_state(#project.current_state) %>
This is a option there may be better solutions to do this.
Related
I've got a partial view:
<%= form_with url: admin_command_path() do |f| %>
[form stuff]
<%= f.submit :onclick(?) button_to? link_to? %>
<% end %>
This view/form doesn't need to create/update the model referenced, but should instead ONLY execute a sql() that I currently have stashed in ApplicationHelper AND in the referenced ^Command module, just to see where I can call it from. Little bit of pasta, meet wall situation :/
def command_string(id)
execute = ActiveRecord::Base.connection.exec_query("
exec [dbo].[FunctionName] #{id}")
end
I've tried just about all manner of form action, url routing, etc no no avail.
The ideal outcome is just calling the dang sql function from the form's submit button, or in a in the view itself, but either the function executes on load (rather than on click) or doesn't execute at all.
Yall don't get too hung up on the missing params, code for rough context. Just looking for an onclick -> sql exec path forward. Thx
You're thinking about this completely wrong.
Helpers are mixins/junk drawers where you can place code that you intend to resuse in your controllers and views. They are not called directly from your routes and "I want method X in my helper to be called when a button is clicked" isn't a very good way of going about it.
If you want to something to happen server side when the user clicks a link/form/button etc its done by sending a HTTP request from the client to the server. This can be a syncronous (normal) or asyncronous (AJAX) request.
You make Rails respond to HTTP requests by creating a route which matches the request and a controller action which sends a response.
# routes.rb
post 'admin_command', as: :admin_command,
to: "admin#do_the_thing"
class AdminController < ApplicationController
def do_the_thing
# This code is vulnerable to a SQL injection attack if the
# id originates from user! Use a parameterized query!
result = ActiveRecord::Base.connection.exec_query("
exec [dbo].[FunctionName] #{id}")
render text: "Okelidokeli duderino"
end
end
<%= form_with url: admin_command_path, local: true do |f| %>
<%= f.submit %>
<% end %>
You follow the same basic structure in Rails applications even when there is no model or view involved. Don't think in terms of functions - think in terms of the API your Rails application provides to the client and how you're going to respond.
Its only later if you need to resuse the code which performs the SQL query across controllers or in your view that you would place it in a helper when refactoring - it has no merit in itself.
If you then want to make this asyncronous (so that the page doesn't reload) you can do so by using Rails UJS or Turbo depending on your rails version. Or you can do from scratch by attaching an event handler to the form and sending an ajax request with the Fetch API.
But you should probally figure out the basics of the syncronous request / response cycle first.
So I'm trying to re-create GitHub version control for let's say posts. I've found a way to re-create an original post using duplicate AND another method to create a new post based on the original. Cool.
My issue is being able to display both the original and the new on the same page.
What I've attempted thus far is to just rely on the show method with having:
def show
#post = Post.find(params[:id])
end
Then in the view have in the form a checkbox to allow a user to select multiple posts, click a submit, and a new page renders displaying both side by side. Preferably showing the differences between the two but that's a wish list as I deal with this first.
Actually could I just simply do?:
def other_show
#post = Post.where(params[:id])
end
I also added in status as a boolean to help on the view for marking the checkbox. Would I then need to put something in the other_show method about the status?
If you want to "recreate" some sort of version control I suggest you use something like the audited. Instead of building your own. From your example and comments it seems you don't have a clear relation between all related (versions of) posts.
Using this gem, each change to the Post content (for example, if configured properly) would be stored as an audit.
Showing the differences is a different problem. That's usually called a diff and you can find gems that do it for you, for example: diffy
To show 2 different entities on one page you need to give posts_controller both ids.
Declare your show method like this:
def show
#original = Post.find(params[:id])
#compared = Post.find(params[:compared_id])
end
Correct route to this method will look like this:
/posts/:id?compared_id=:another_id
# Example: /posts/1?compared_id=2
To construct such a link in your view, you need to declare link_to method like this:
<%= link_to '1 <> 2', post_path(#post, compared_id: '2') %>
If you want to have a page where user can check 2 checkboxes for certain posts, you'll need to construct such href via Javascript.
But in fact I wouldn't suggest you to modify show method for such a task. It is better to use show method only for showing one entity from database. You can create another method, e.g. compare and pass both parameters there.
def compare
#original = Post.find(params[:original_id])
#compared = Post.find(params[:compared_id])
end
In routes.rb
resources :posts do
get 'compare', on: :collection
end
It will give you helper compare_posts_path, which will lead to /posts/compare and you'll need to pass original_id and compared_id to it, like this:
<%= link_to 'Compare', compare_posts_path(original_id: 'some_id', compared_id: 'some_another_id') %>
It will result to
/posts/compare?original_id=some_id&compared_id=some_another_id
I currently have a form (using form_tag). One of the fields is a dropdown list of options. Each option value matches the name of a method in my controller. What I want to do is when the form submit button is clicked, it runs the controller method corresponding directly to the value selected in the dropdown field.
I've built a work-around right now, but it feels too verbose:
def run_reports
case params[:report_name]
when 'method_1' then method_1
when 'method_2' then method_2
when 'method_3' then method_3
when 'method_4' then method_4
else method_1
end
# each method matches a method already defined in the controller
# (i.e. method_1 is an existing method)
I had thought that it may work to use the dropdown option value to run the corresponding method in my controller through the form_tag action (i.e. :action => params[:report_name]), but this doesn't work because the action in the form needs to be set before the params value is set. I don't want to use javascript for this functionality.
Here is my form:
<%= form_tag("../reports/run_reports", :method => "get") do %>
<%= select_tag :report_name, options_for_select([['-- Please Select --',nil],['Option 1','method_1'], ['Option 2','method_2'], ['Option 3','method_3'], ['Option 4','method_4']]) %>
<%= submit_tag "Run Report" %>
<% end %>
Any suggestions?
Can I change my controller method to look something like this - but to actually call the controller method to run? I'm guessing this won't run because the params value is returned as a string...
def run_reports
params[:report_name]
end
WARNING: this is a terrible idea
You could call the method via a snippet of code like this in the controller:
send(params[:report_name].to_sym)
The reason this is a terrible idea is that anyone accessing the page could manually construct a request to call any method at all by injecting a request to call something hazardous. You really, really do not want to do this. You're better off setting up something to dynamically call known, trusted methods in your form.
I think you should rethink the design of your application (based on the little I know about it). You have a controller responsible for running reports, which it really shouldn't be. The controllers are to manage the connection between the web server and the rest of your app.
One solution would be to write a new class called ReportGenerator that would run the report and hand the result back to the controller, which would run any of the possible reports through a single action (for instance, show). If you need variable views you can use partials corresponding to the different kinds of reports.
As for the ReportGenerator, you'll need to be a little creative. It's entirely possible the best solution will be to have an individual class to generate each report type.
For people that only want the question, here it is :
Is there a way to specify the folder to look in when you call render on an object? I don't want to specify the view, only the folder to look in.
And for people that want context :
I am working on an activity stream system (something that looks like google+/facebook).
I have "Activities", which are exactly like google+ feeds (or facebook, or whatever!). So, I have a simple loop that display each activities, which are bound to one of the following object (polymorphic) : User, Group, Comment, Note.
In my view that render an activity (views/activities/_activity.html.erb), I have
<%= render activity.object %>
where activity.object is a reference to the bound object (User, Group, Note, Comment). If it's a user, it goes to views/users/_user.html.erb and renders it. For a group, views/groups/_group.html.erb.
That works just fine. However, I come to the point where the rendering of a group in my activities should not be the same rendering as in the group list page. Is there a way to specify the folder to look in when you call render on an object? So that my :
<%= render activity.object %>
would become :
<%= render activity.object, :folder => 'views/activities/' %>
Note that I don't want to specify which view directly, as I don't want to do a case for each of the possible types of objects (User, Group, Note, Comment) in the activity. I want to to have the same behaviour as of right now, which means if it finds a views/activities/_user.html.erb, it would load any user in the activities with that view instead of the one in the views/users/_user.html.erb.
Thanks
I'm not aware of any folder type option, but when I do this I usually do:
<%= render "activities/#{activity.object.class.name.underscore}" %>
That would give you similar behaviour.
EDIT A good point below by Dominic, if your classes are nested in namespaces, you will have to include the appropriate structure.
i.e.
module Foo
class Bar < ActiveRecord::Base
end
end
# class.name is Foo::Bar, underscored is 'foo/bar'
<%= render "activities/#{activity.object_type.underscore}" %>
# will be in
activities/foo/_bar.html
In current Rails (3.2.9), you can define a to_partial_path method on your model to tell it where to look.
For example, you can do:
class User
def to_partial_path; "user"; end
end
Rails will then look for _user.html.erb relative to the view from which you're calling render.
My own approach, which I extracted in polymorphic_render gem - to add suffixes for special partials, but store that partial in resource folders.
In your case views/users/_user.html.erb will have very common user representation (which probably used in users list rendering), but views/users/_user_activity.html.erb will render special partial for user activity.
And inserting that partials is very simple, just <%= polymorphic_render activity.object, :activity %>
Here's my code for a form that contains a drop down list -
<div class="field">
<%= f.label :type, "Select profile type"%>
<%=
f.select :type, Profile::TYPES,
:prompt => "Select a profile type"
%>
</div>
The drop down menu looks fine. But, how would I check which option is selected? I want to route to a different view based on this selection.
Thanks in advance!
The logic of routing to a different view should occur in your controller. When the user submits this form, check the value of the params, and perform your logic to route to a view:
class ExampleController
def routing
case params[:example][:type]
when 'foo'
redirect_to foo_path
when 'bar'
redirect_to bar_path
end
end
You can create a custom action name, since this routing isn't one of the CRUD operations. You will need to place this route into the config/routes.rb file if it is a custom name.
Optionally, you can bind to the select's onChange event, as mentioned by others to auto-submit the form when the user changes the value. This would still send the data to the controller and perform a redirect. The advantage to this approach is that you can keep your route information out of Javascript, and in the Rail's controller.
More on Rails routing can be found here: http://guides.rubyonrails.org/routing.html
More on Javascript binding to onChange can be found here: http://www.w3schools.com/jsref/event_onchange.asp
You should be able to use jQuery or any other popular Javascript framework to achieve this -- either attach an onChange listener or set the value somewhere and check it. Events, yay, etc.