i'm working on a Rails app.
And i'm trying to write a controller that will have access on the files of the Public folder of rails.
And i'm kind a stuck right now.
What i'm trying to do is a get method on my FileController.
That will obtain a variable "path" who correspond to the path of the file to read who is located on the public folder of rails.
And i want to modify routes.rb for when i make a GET request of "/file/path/to/htmlpage" the request sendback to me the content of the file, like a string for an HTML file.
EDIT: The problem is that is need REST url like this.
show => GET /file/:path
create => POST /file/:path
update => PUT /file/:path
destroy => DELETE /file/:path
So i deleted my old routes and put
resources :files
And my old routes wad like that
#get "file/:path" => 'file#get', as: :get
#get "file/create/*path" => 'file#create', as: :create
#post "file/create/*path/:content" => 'file#create', as: :create_content
#get "file/update/*path/:content" => 'file#update', as: :update
#get "file/destroy/:path" => 'file#destroy', as: :destroy
My controller is maybe a little bit weird, i need some advice.
class FileController < ApplicationController
# get "file/:path"
def show
path = params[:path]
if File.exists?( Rails.public_path.join( "#{path}.html" ) )
#content = File.read( Rails.public_path.join( "#{path}.html" ) )
puts #content
else
puts "The file you want to access doesn't exist"
end
end
# get "file/create/:path"
# post "file/create/*path/:content"
def create
path = params[:path]
content = params[:content]
if File.exists?( Rails.public_path.join( "#{path}.html" ) )
puts "The file you want to create already exist"
else
File.write(Rails.public_path.join( "#{path}.html" ), "#{content}")
end
end
# get "file/update/*path/:content"
def update
path = params[:path]
content = params[:content]
if File.exists?( Rails.public_path.join( "#{path}" ) )
File.write(Rails.public_path.join( "#{path}" ), "#{content}")
else
puts "The file you want to update doesn't exist"
end
end
# get "file/destroy/:path"
def destroy
path = params[:path]
if File.exists?( Rails.public_path.join( "#{path}.html" ) )
File.delete( Rails.public_path.join( "#{path}.html" ) )
else
puts "The file you want to delete doesn't exist"
end
end
end
But now it's not working, i think i have a problem with my routes and my controller that don't communicate in the right way. But i don't see how i have do to it.
I don't have a file model because i don't see the need for what i want to do.
Thanks for your help
Try this:
# FileController
def action_name
#check and filter here your path param, something like:
path = params[:path].gsub /[^a-z0-9\.\/]+/, ''
if File.exists?( Rails.root.join( "public/#{path}" ) )
#content = File.read( Rails.root.join( "public/#{path}" ) )
end
end
There are several answers to your question that yield different results.
If you want to load the file inside ruby, you can reference the public path like: Rails.public_path. In addition, you can get the root path to your application via Rails.root. That should help you get access to the files you need inside your controller.
However I have to ask: why do you want to do this? If you simply want to serve up the file, you can let rails do that by default by simply providing the path in the link itself - it will serve up the public files. In addition, it's quite likely you don't want to push that through your ruby/rails at all, and want to setup your HTTP server to serve the static files for you which will yield a dramatic performance benefit.
I would use a model like this:
class PublicFile
attr_accessor :path, :content
def save(params)
File.write(params[:path], params[:content])
end
def destroy(params)
File.delete(params[:path])
end
def initialize(params)
#content = File.read(params[:path])
#path = params[:path]
end
def find(path_or_paths)
# to implement
end
def all
# to implement
end
def update_attributes(params)
# to implement
end
end
In controller and views you could generate a scaffold rails g scaffold PublicFile, you need to move the model before running the scaffold, and then use the above model that manipulates files.
In conclusion, what I mean is to change the DB that an usual Rails model has, with the access to files, this would be done in model logic.
Related
I have a model called studies.
After action redirect redirect_to edit_study_path(#new_study),
URL: http://localhost:3000/studies/2/edit.
Is there anyway to customize an url after passing id ?
For example, http://localhost:3000/study
(still going to the edit path, and still with the :id in the params)
I guess what you want is to edit the current study?
In this case, it's possible, using ressource instead of ressources in the routes.
Let's have an example:
#in routes.rb
resources :studies
resource :study
Both of them will by default link to the StudiesController and call the same actions (eg. edit in your case) but in two different routes
get "/studies/:id/edit" => "studies#edit"
get "/study/edit" => "studies#edit"
in your edit action, you should then setup to handle correctly the parameters:
def edit
#study = params[:id].nil? ? current_study : Study.find(params[:id])
end
Note you need a current_study method somewhere, and store the current_study in cookies/sessions to make it works.
Example:
# In application_controller.rb
def current_study
#current_study ||= Study.find_by(id: session[:current_study_id]) #using find_by doesn't raise exception if doesn't exists
end
def current_study= x
#current_study = x
session[:current_study_id] = x.id
end
#... And back to study controller
def create
#...
#Eg. setup current_study and go to edit after creation
if study.save
self.current_study = study
redirect_to study_edit_path #easy peesy
end
end
Happy coding,
Yacine.
I am having a hard time solving this problem because this is my first time to learn ruby on rail, i have a index.html.erb, new.html.erb, show.html.erb, edit.html.erb files. So when i go to localhost:3000/blogs/edit page the page that is showing is show.html.erb and when i delete the show.html.erb then access the edit.html.erb im having a template missing error. but when i access localhost:3000/blogs/new or just localhost:3000/blogs its working fine.So here's my code inside blogs_controller.rb
class BlogsController < ApplicationController
def index
#content_first = 'This 1' ;
#content_two = 'This 2' ;
end
def new
end
def create
end
def edit
end
def update
end
def show
end
def destroy
end
end
I think your problem is that you're trying to access /blogs/edit , and the route is probably /blogs/edit/:id. As in you need to provide the id of a blog object, so that you can edit it.
If you run
rake routes
You will be able to see your available routes.
Hope this helps =)
I'm making a breadcrumb module for my Ruby on Rails application, but I wanted a specific syntax - which I thought was good looking and more intuitive for Rails developers.
Here's the deal:
class WelcomeController < ApplicationController
breadcrumb_for :index, :text => 'Home', :href => -> { root_path }
def index
end
end
See, it's neat.
You can safely ignore the everything else but that proc - what I assign to the :href key.
I use instance_eval so that when the proc is evaluated it has access to the root_path helper.
And it worked. The example above is okay. BUT then I wanted to use an instance variable and that didn't work.
Like this:
class WelcomeController < ApplicationController
breadcrumb_for :index, :text => 'Home', :href => -> { #path }
def index
#path = root_path
end
end
Now, in that proc context #path is nil.
What should I do so I can access the instance variables from the block ?
Below is all the code of my module. Note that when I "process" the blocks and use instance_eval (aka call my module's #breadcrumb) the action should already be evaluated so the instance variable #path should already exist.
module Breadcrumb
extend ActiveSupport::Concern
included do
cattr_accessor(:_breadcrumb) { [] }
helper_method :breadcrumb
def self.breadcrumb_for(*args)
options = args.pop
_breadcrumb.push([args, options])
end
end
def breadcrumb
#breadcrumb ||= self._breadcrumb.map do |item|
puts item
if item[0].include?(params[:action]) || item[0][0] == '*'
text, href = item[1].values_at(:text, :href)
if text.respond_to?(:call)
text = instance_eval(&text)
end
if href.respond_to?(:call)
href = instance_eval(&href)
end
[text, href]
end
end
end
end
Oh no. I'm ashamed to say but it was my mistake. The code above works just fine, I was using different variable names in my application, not shown in the excerpt I used in the question.
Thanks anyway, I'll left it here for reference.
I am building a small application in RoR that has a form asking for a URL. Once the URL has been filled in and submit button is pressed I have downloaded a web-scraping plugin scrAPI(which is working fine) which gets the of URL and creates a record in db with title.
My issue right now is that I am able to make the whole thing work if the URL is valid and scrAPI is able to process it. If a URL entered does not work it gives this "Scraper::Reader::HTTPInvalidURLError" which is expected, but my knowledge of working in Model is preventing me from handing that error in a correct manner.
Controller:
#controller
class ArticleController < ApplicationController
def savearticle
#newarticle = params[:newarticle]
#link = #newarticle["link"]
#id = #newarticle["id"]
Article.getlink(#link)
success = Article.find(:last).update_attributes( params[:newarticle] )
if success
render :partial => 'home/articlesuccess'
else
render :partial => 'home/articlebad'
end
end
end
# model
require 'scrapi'
class Article < ActiveRecord::Base
attr_accessor :getlink
def self.getlink(link)
scraper = Scraper.define do
process "title", :title => :text
result :title
end
uri = URI.parse(link)
Article.create(:title => scraper.scrape(uri))
end
end
How to:
1) Handle the Scraper::Reader::HTTPInvalidURLError properly, so text could be returned to view with proper error.
2) I would also like to know how I can return 'uri' from model and use it in the controller or view.
3) Also, I would like to return the ID of the Article created in Model so I can use that in the controller instead of doing find(:last) which seems like bad practice.
Something like...
class ApplicationController < ActionController::Base
rescue_from 'Scraper::Reader::HTTPInvalidURLError', :with => :invalid_scrape_url
private
def invalid_scrape_url
flash[:error] = 'The URL for scraping is invalid.'
render :template => 'pages/invalid_scrape_url'
end
end
rescue_from is what you need.
That's 1)
for 2) You could just use #uri but personally I'd create a new model called Scrape and then you can retrieve each Scrape that is attempted.
for 3) I'm not quite sure of the question but
#article = Article.create(:title => scraper.scrape(uri))
then
#article.id
Hope that helps!
(1) In Ruby, you can handle any exception as follows:
begin
# Code that may throw an exception
rescue Scraper::Reader::HTTPInvalidURLError
# Code to execute if Scraper::Reader::HTTPInvalidURLError is raised
rescue
# Code to execute if any other exception is raised
end
So you could check for this in your controller as follows:
begin
Article.getlink(#link)
# all your other code
rescue Scraper::Reader::HTTPInvalidURLError
render :text => "Invalid URI, says scrAPI"
rescue
render :text => "Something else horrible happened!"
end
You'll need to require 'scrapi' in your controller to have access Scraper::Reader::HTTPInvalidURLError constant.
I would probably make the creation of the new Article and the call to scrAPI's method separate:
title = scraper.scrape(uri)
Article.create(:title => title)
(2) and (3) In Ruby, the last statement of a method is always the return value of that method. So, in your self.getlink method, the return value is the newly created Article object. You could get the ID like this in your controller:
article = Article.getlink(#link)
article_id = article.id
You may need to refactor the code a bit to get the results you want (and make the code sample on the whole cleaner).
I have to separate models: nested sections and articles, section has_many articles.
Both have path attribute like aaa/bbb/ccc, for example:
movies # section
movies/popular # section
movies/popular/matrix # article
movies/popular/matrix-reloaded # article
...
movies/ratings # article
about # article
...
In routes I have:
map.path '*path', :controller => 'path', :action => 'show'
How to create show action like
def show
if section = Section.find_by_path!(params[:path])
# run SectionsController, :show
elsif article = Article.find_by_path!(params[:path])
# run ArticlesController, :show
else
raise ActiveRecord::RecordNotFound.new(:)
end
end
You should use Rack middleware to intercept the request and then rewrite the url for your proper Rails application. This way, your routes files remains very simple.
map.resources :section
map.resources :articles
In the middleware you look up the entity associated with the path and remap the url to the simple internal url, allowing Rails routing to dispatch to the correct controller and invoking the filter chain normally.
Update
Here's a simple walkthrough of adding this kind of functionality using a Rails Metal component and the code you provided. I suggest you look at simplifying how path segments are looked up since you're duplicating a lot of database-work with the current code.
$ script/generate metal path_rewriter
create app/metal
create app/metal/path_rewriter.rb
path_rewriter.rb
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
class PathRewriter
def self.call(env)
path = env["PATH_INFO"]
new_path = path
if article = Article.find_by_path(path)
new_path = "/articles/#{article.id}"
elsif section = Section.find_by_path(path)
new_path = "/sections/#{section.id}"
end
env["REQUEST_PATH"] =
env["REQUEST_URI"] =
env["PATH_INFO"] = new_path
[404, {"Content-Type" => "text/html"}, [ ]]
end
end
For a good intro to using Metal and Rack in general, check out Ryan Bates' Railscast episode on Metal, and episode on Rack.
Rather than instantiating the other controllers I would just render a different template from PathController's show action depending on if the path matches a section or an article. i.e.
def show
if #section = Section.find_by_path!(params[:path])
render :template => 'section/show'
elsif #article = Article.find_by_path!(params[:path])
render :template => 'article/show'
else
# raise exception
end
end
The reason being that, whilst you could create instances of one controller within another, it wouldn't work the way you'd want. i.e. the second controller wouldn't have access to your params, session etc and then the calling controller wouldn't have access to instance variables and render requests made in the second controller.