It's my first post on stackoverflow! I warn you, I just started learning Ruby on Rails.
Currently, I'm working on a music scanning project in rails. I'm trying to process al the music on the hard disk. To do that, the user can add a folder to scan when he is inside the config part of the app. So I store inside a YAML file all the config of my app.
I create a 'config' controller, that contains 2 view for the moment: "adddir" and "index".
The index view show all parameters, that have been gathered by loading the YAML config file (done by the controller 'config').
Next, I want to give the possibility to the user to add a new folder to scan. So I did a static route to the 'adddir' view:
routes.rb file:
match "/config/adddir" => "config#adddir"
Now, I want to create a form that get the path provided by the user, and add it to the YAML file.
So my question is: How to create a form that be able to use the method config#addfolder I created when the user click on submit button?
I hope this is enough clear.
Best regards
Welcome to SO. Its a very basic question. Just have a look at the screencast. Make a simple project first (with a model), then you will see the ABC of Rails.
http://rubyonrails.org/screencasts/rails3/
Basically:
You generate the controller like:
rails g controller config adddir
This setups the controller, the view and your route.
Then go into your view folder and look inside config. There is adddir.html.erb. This file you need to change in something like a form.
You can open your form:
http://localhost:3000/config/adddir
rake routes
Will print you a list of available routes you can use (inside your form).
Now the magic is that Rails generate your a config_adddir_path (watch the _path). That you can use to hookup the form.
<%= form_tag(config_adddir_path, :method => "get") do %>
<%= label_tag(:q, "Folder:") %>
<%= text_field_tag(:q) %>
<%= submit_tag("index") %>
<% end %>
Inside your controller (def adddir):
You can access the parameters:
def adddir
logger.debug params.inspect
end
Have a look here:
http://guides.rubyonrails.org/form_helpers.html
Related
The main table in my backend gets populated by a CSV file. When seeding and running rails db:seed , the CSV files in lib/assets/csv is read with file = File.read(Rails.root.join('lib', 'assets', 'csv', 'data.csv'), and the logic in seeds.rb runs through each row to create a table entry, populating the fields with the CSV column data.
I've also implemented Thoughtbot's Administrate for a UI admin dashboard to view this data.
So my question is, what is the best way to configure some kind of custom file upload system on Administrate dashboard if I ever need to replace the files sitting in lib/assets/csv and reseed?
I've looked at ActiveStorage but I've only ever used it to store files like an image specifically related to a table entry, not for seeding the entire table.
Yes, it is possible, and you don't need ActiveStorage. Rails's basic file upload facilities will be enough. Meanwhile, Administrate tries to follow Rails conventions, and provides hooks for you to alter the templates and refer to your own controllers and actions where you can implement this. Here's an example of how you could solve your problem.
First, you'll want to add a form with a file field. Admins will be able to use it to upload CSVs.
There are many places where you could put this form. For this example, let's say that it's "products" that you want to import (model Product), and you want to put the link in the index page.
You can override Administrate's own templates with your own ones. The following command will generate a copy of the index template that you can customize and Administrate will use instead of its own:
$ ./bin/rails g administrate:views:index
This will get you a new file app/views/admin/application/index.html.erb.
Alter it to add a link to a separate page that will host the upload form:
<% if page.resource_name == "product" %>
<div class="link-to-upload"><%= link_to "CSV Import", upload_admin_products_path %></div>
<% end %>
You can put this in between the page title and the search bar. Or wherever you want, really. It does the following:
The if checks that we are in the index page for "products" and not for something else.
The link_to links to a page that we haven't created yet, which will host the upload form.
If you load the products index page now, it will show an error NameError, with message undefined local variable or method 'upload_admin_products_path'. This is because we still don't have a route for this new page of ours. Lets add it now.
In the routes file config/routes.rb, you'll already have a route resources :products. Change it to look like this:
resources :products do
collection do
get :upload, action: 'upload_form'
post :upload, action: 'upload_process'
end
end
This actually adds two routes, not one. One will be for the form page, while the other one will be for the action that will process the uploaded CSV.
For now, you can click on the link and go to the new page. It will break again. This time the error will be Unknown action - The action 'upload_form' could not be found for Admin::ProductsController. That's because we haven't provided a view for this page yet. Let's do it now.
Add a view with the following contents at app/views/admin/products/upload_form.html.erb:
<header class="main-content__header" role="banner">
<h1 class="main-content__page-title" id="page-title">
Import products from CSV
</h1>
</header>
<section class="main-content__body main-content__body">
<%= form_for :products, html: { class: "form" } do |f| %>
<p><%= f.file_field(:file) %></p>
<p><%= f.submit "Upload" %></p>
<% end %>
</section>
Now you should be able to load the page and see the form. I have added additional markup (like those header and section elements) to make it look similar to other pages that Administrate provides.
That form has a file_field and a submit button. On submit, it will send the file to the current URL (/admin/products/upload) as a POST request. It will be handled by the post route we added earlier, on the action upload_process. Let's write that now.
On the controller Admin::ProductsController, add the following method to implement the action:
def upload_process
file = params[:products][:file]
data = CSV.parse(file.to_io, headers: true, encoding: 'utf8')
# Start code to handle CSV data
ActiveRecord::Base.transaction do
data.each do |row|
Product.create(row.to_h)
end
end
# End code to handle CSV data
redirect_to admin_products_url
end
This will create new products based on what's contained in the CSV, and then will redirect the user back to the products index page.
This example action is very simple and may not suit your needs exactly. You probably want to copy your code from seeds.rb and paste it in between my Start and End comments, so that it does what you want exactly. There are many possibilities.
i'v been trying to resolve this error for the past 5 hours and I'm gonna burn my computer if I can't solve this.
undefined method `pushes_path' for #<#:0x007f859d605250> this is the error code I'm getting but i don't understand why.
this is my index.html.erb file inside of the interaction
<%= simple_form_for #push do |f| %>
<%= f.input :payload, as: :text %>
<%= f.input :segment, as: :radio_buttons %>
<%= submit_tag "start the campaign" %>
<% end %>
and this is my interaction controller
class InteractionController < ApplicationController
def index
#push =Push.new
end
end
Push is my table in the database and i'll get the inputs and write them in the database to use them for later one.
and this is my routes file
devise_for :partners
get 'home/index'
get 'segmentation/index'
get 'interaction/index'
root to: "home#index"
i really don't know why its looking for pushes_path, what am i doing wrong?
form_for
The problem you have is that your form_for method is going to try and generate a route based off your #path object. And as such, if you don't have a path created for it, you'll receive the error you're getting:
:url- The URL the form is to be submitted to. This may be represented
in the same way as values passed to url_for or link_to. So for example
you may use a named route directly. When the model is represented by a
string or symbol, as in the example above, if the :url option is not
specified, by default the form will be sent back to the current url
(We will describe below an alternative resource-oriented usage of
form_for in which the URL does not need to be specified explicitly).
The bottom line is that as Rails is object orientated, its built around the assumption that you'll have routes set up to handle the creation of individual objects.
Every time you use form_for, Rails will attempt to construct your routes from your object -- so if you're trying to do the following, it will treat the routes as photo_path etc:
#app/views/pushes/new.html.erb
<%= form_for #push do |f| %>
...
<% end %>
--
Fixes
As #mandeep suggested, there are several fixes you can employ to get this to work:
Firstly, you can just create a route for your push objects:
#config/routes.rb
resources :pushes
Secondly, as you're using a different controller, you'll want to do the following:
#config/routes.rb
resources :interactions
#app/views/pushes/new.html.erb
<%= form_for #push, url: interaction_path do |f| %>
...
<% end %>
This will route your form submission to the interactions controller, rather than the pushes controller that you'll get by default!
Objects
Something to consider when creating Rails-based backends is the object-orientated nature of the framework.
By virtue of being built on Ruby, Rails is centered on objects - a term for a variable, which basically encompasses much more than just a piece of data. Objects, in the case of Rails, are designed to give the application:
Once you understand this, the entire spectrum of Rails functionality becomes apparent. The trick is to realize that everything you do in Rails should be tied to an object. This goes for the controllers too:
--
Ever wondered why you call resources directive in your routes, for a controller? It's because you're creating a set of resourceful routes based for it:
Do you see how it's all object orientated?
This gives you the ability to define the routes for specific controllers etc. The most important thing to note is how this will give you the ability to determine which routes / controller actions your requests should go
--
There's nothing wrong in using the controller setup as you have - the most important thing is to ensure you're able to define the custom URL argument, as to accommodate the non-object based structure
In your index action you have
def index
#push =Push.new
end
and your form has
<%= simple_form_for #push do |f| %>
so your form is looking for /pushes with post verb or pushes_path and you don't have that route in your routes.rb file so to fix this you need to add this in routes.rb:
resources :pushes
Update:
when you add resources :push rails basically creates seven different routes for you. One of which is
POST /pushes pushes#create create a new push
and if you look at the html generated by your form it would be something like:
<form action="/pushes" class="new_push" id="new_push" method="post">
// your fields
</form>
notice the action and verb so when you submit your form your routes are checked for them and since you didn't define them in your routes you were getting this error
And how will i be able to use the params i m getting from this form with this new resource addition?
Your form will take you to pushes_controller create action so first of all you'll have to define them. You can access them simply by params[:pushes] in your controller action but since you want to create a new record so you'll have to permit those attributes, checkout strong parameters
If you are using rails >= 4 then you can do
class PushesController < ApplicationController
def create
#push =Push.new(push_params)
if #push.save
redirect_to #push
else
render 'interaction/index'
end
end
private
def push_params
params.require(:push).permit(:attributes)
end
end
If you are using rails < 4 then instead of permitting these attributes(because strong parameters feature came from rails 4) you'll have to tell rails that these attributes are accessible by writing this in your pushes.rb
attr_accessible :attribute_name
Why it is assuming that its pushes controller?Because of the Push.new creation?
That's because if you look at your index action #push = Push.new so #push contains a push object with nil values(as you have just initialized it) so this is where rails magic comes, rails automatically tries to figure out url of your form and since your #push is only an initialized variable so rails takes you to create action for it. For details you should checkout rails polymorphic urls If you want your form to go to interaction_controller or some other url then you'll have to specify the url option for it
<%= form_for #push, url: "your_url_for_custom_method" %>
// other fields
<% end %>
And in the end you should really read docs
I'm trying to create a link so that if the current user has a project already created, then they just click the 'Project' link, and it will take them to their project. Each user can only have one project. If they don't have a project, then it will instead take them to the form to create one (i.e. the new view/action).
How would I go about this? I apologise, I'm new to rails. At the moment, I am using the following:
<%= link_to 'Project', project_path %>
which works fine if the user already has a project, but says "No route matches {:action=>"show", :controller=>"projects"}" if one doesn't exist. I'm not sure where to start - do I add in conditions to the link_to, or is it something I need to put in the controller? Thanks!
I guess you should check it inside the new action. Find project if exist and redirect to the edit action.
If you will decide which link to render user can cheat you and type /projects/new in address bar.
<%= link_to 'Project',
current_user.project.present? ?
project_path(current_user.project) :
new_project_path %>
I am new to Rails and don't quite understand what I'm supposed to do. Let's say, for example, I want a textbox containing a string to be passed into another controller (another page?) when the user clicks a button. How would I go about doing that?
Functions of controllers are pages, correct? Can a function take parameters just like a normal method? (E.g. sum(x,y))
For complete information, check out Rails Form helpers. Basically, you give the form_tag method a path which points to the controller and the action that you want to handle the form submission. For example,
<%= form_tag(search_path, :method => "get") do %>
<%= label_tag(:q, "Search for:") %>
<%= text_field_tag(:q) %>
<%= submit_tag("Search") %>
<% end %>
Here, the action and controller that search_path points to (defined in your routes) will receive the form submission and the value from the text field.
Your action in the controller IS a function, but it will not receive the value from the form submission as a parameter to the function. Instead, you will access it through the params hash. In the example above, you can access the value from the text field as
params[:q]
What are you doing with the string? Storing it? Using it as a parameter on another page?
I suggest you take a look at the Getting Started Guide, go through it, and pay particular attention to the What is Rails? section, where it explains MVC architecture and REST (Representational State Transfer.)
There are dozens of other Rails tutuorials out there, I'm sure if you searched this site you'd find many questions like this one:
https://stackoverflow.com/questions/2794297/how-to-learn-ruby-on-rails-as-a-complete-programming-beginner
Functions of controllers are pages, correct? Can a function take parameters just like a normal method?
Functions of controllers are pages if that's the route you've set up in your routes.rb configuration file. I suggest you run through some tutorials to understand what Rails is for and how it works.
I'm trying to build a CMS in Rails from scratch, and for showing the user generated pages I'm having trouble deciding exactly how to do it.
The way I have it right now, I have a controller named 'content' with a single action called 'show'. In routes.rb I have a rule that passes any name after the name of the website to the content controller, show action with parameter name.
For example, www.mysite.com/about_us would route to
:controller => 'content', :action => 'show', :page => 'about_us'
Inside the content controller, I do a find on the Pages model to locate the named page:
#markup = Page.find_by_name(params[:page])
And then in the show.html.erb view I use the raw helper to display the content:
<%= raw #markup.text %>
Does this method violate anything about the way I should do be doing things in Rails? Or is this an OK solution?
I ended up using the sanitize helper, by default it removes <script> tags which is essentially what you need to prevent XSS, as far as I understand. For those who have found this via a search, the only code that changes from what I described above is that in the view you change to:
<%= sanitize #markup.text %>