When you generate a rails scaffold using a command like rails g scaffold Thing is there any way to avoid getting that annoying
respond_to do |format|
format.html # index.html.erb
format.json { render json: #things }
end
stuff in your controller?
I'm trying to teach a class on Rails and I'd like to start by having them generate a scaffold, but with all the json formatting it's much more complicated than it needs to be. I'd be much happier if they could generate a scaffold that created a controller like this:
class ThingsController < ApplicationController
def index
#things = Thing.all
end
def show
#thing = Thing.find(params[:id])
end
def new
#thing = Thing.new
end
def edit
#thing = Thing.find(params[:id])
end
def create
#thing = Thing.new(params[:thing])
if #thing.save
redirect_to #thing, notice: 'Thing was successfully created.'
else
render: "new"
end
end
end
def update
#thing = Thing.find(params[:id])
if #thing.update_attributes(params[:thing])
redirect_to #thing, notice: 'Thing was successfully updated.'
else
render: "edit"
end
end
end
def destroy
#thing = Thing.find(params[:id])
#thing.destroy
redirect_to things_url
end
end
Comment out gem jbuilder in your Gemfile and respond_to blocks won't be generated.
Just clone the file
https://github.com/rails/rails/blob/v5.2.2/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
to your
lib/rails/generators/rails/scaffold_controller/templates/controller.rb
path in your application and customize what you want. Also, you can write your own generators for scaffolding ( http://guides.rubyonrails.org/generators.html ).
I think you'd be missing an opportunity. For one thing, you'd be teaching non-standard Rails, so your students might be confused when they see the normal version in their own installations.
More importantly, the controllers are formatted that way for a reason. Rails puts an emphasis on REST, which encourages access to resources via multiple data formats. Many modern apps are de-emphasizing slower server-rendered html/erb responses in favor of json APIs. I realize this is a little over a year after your OP, and you have limited time in class, just adding some thoughts for anyone who might happen by. I think you could wave your hand over the respond_to and tell them it's setting you up for some future possibilities.
You'll notice that the JSON response is coded directly into the template for the rails generator here:
https://github.com/rails/rails/blob/master/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
I think something to note is that the scaffold generator is really intended to illustrate and moreover educate on how the Rails stack works, it shows how you can edit the controller to provide many different formats to suit your needs!
Related
I am creating a Rails app to which its users have two ways of interaction.
Through a web interface and through an API (mobile app and other software).
The functions for the web and the api access are the same, for example a user can write a comment via the web interface (views) or through the API.
What I would do now is create all the controllers with views, and then create a namespace /API/ with its own controllers. The problem now is of course that I have to write the function to write write a comment twice. Once in my PostController and once in my API/PostController.
I learned that Rails = DRY, so I guess I am doing something wrong.
How would I make the same functions available for my views and at the same time for my API (JSON response).
And how would the routes and namespaces look like? I think even if I find a way to not repeat myself it would be nice to have API routes like api/v1/...
I've done something similar, both using an API namespaced controller and using a single controller.
Since you seem convinced that you can do everything equally within your Web interface as your API interface it would make sense to merge the two, though note that this is not always true.
If it makes more sense for you to use a single controller, then what you need to do is play with the repond_to format of requests coming to a single controller action.
Ex:
class ArticlesController < ApplicationController
def index
#articles = Article.all
respond_to do |format|
format.html # will render a view by default
format.json { render json: #articles }
end
end
def create
article = current_user.articles.build(article_params)
respond_to do |format|
if article.save
flash[:notice] = "success!"
else
flash[:error] = "uhoh!"
end
format.html # renders a view by default
format.json { render json: { errors: #articles.errors }
end
end
private
def article_params
params.require(:article).permit(:title, :content)
end
end
I am a beginner of Rails. I am learning rails with the book 'Beginning Rails 4' now. I want to ask you about 'parameter' passed to params method. The following is one of typical rails controllers.
class CommentsController < ApplicationController
before_action :load_article
def create
#comment = #article.comments.new(comment_params)
if #comment.save
redirect_to #article, notice: 'Thanks for your comment'
else
redirect_to #article, alert: 'Unable to add comment'
end
end
def destroy
#comment = #article.comments.find(params[:id])
#comment.destroy
redirect_to #article, notice: 'Comment Deleted'
end
private
def load_article
#article = Article.find(params[:article_id])
end
def comment_params
params.require(:comment).permit(:name, :email, :body)
end
end
Yes, this is just a typical comment controller used to create a comment attached to an article. The Comment model 'belongs to' the Article model, and the Article model 'has many' comments.
Take look at the destroy method.
def destroy
#comment = #article.comments.find(params[:id])
-- snip --
end
It finds the comment associated with the article by find(params[:id]). My question is, where on earth does params[:id] come from?
Does it come from URL? Or does rails save params hash automatically whenever any comment record is created? So we can find any comment by find(params[:id])?
The load_article method is similar.
def load_article
#article = Article.find(params[:article_id])
end
It finds an article by params[:article_id]. Where does this params[:article_id] come from? How does rails find an article by this?
params[:id] is meant to be the string that uniquely identifies a (RESTful) resource within your Rails application. It is found in the URL after the resource's name.
For example, for a resource called my_model, a GET request should correspond to a URL like myserver.com/my_model/12345, where 12345 is the params[:id] that identifies that specific instance of my_model. Analogies follow for the other HTTP requests (PUT, DELETE etc) and their RESTful counterparts.
You should read about Rails routing and its interpretation of RESTful architecture if you're still confused about these concepts and terminologies.
params[:id] does come from the URL. When you use resources in your routes file, Rails will automatically generate the standard REST routes for you. In your destroy example, that would usually be a be a request to /comments/:id using the DELETE HTTP method, in which that :id is added to the params hash, i.e. params[:id].
I'm trying to have the "show" action for my pages controller render a Liquid template instead of the normal view. The template itself is stored in the database.
This is my show action:
def show
#organization = Organization.find_by_subdomain(request.subdomain)
#template = Liquid::Template.parse(Template.find(#organization.current_template))
#page = #organization.pages.find(params[:id])
respond_to do |format|
format.html { render #template.render('page' => #page)}
format.json { render json: #page }
end
end
However, it raises this exception:
uninitialized constant PagesController::Liquid
I'm a RoR newbie, so I'm assuming what's happening is that it's trying to find the Liquid class in the PagesController class, instead of realizing it's a class unto itself. I'm following the (somewhat sparse) instructions here as best I can.
What am I doing wrong?
You need to include liquid in your Gemfile:
gem "liquid"
Then run bundle install and restart your rails server.
I want to output a list of affiliate links, each tagged to identify the current user. It would be simple in HTML, but we're writing an API, so the output is JSON.
I have it working, but it seems overly complicated. Is this the best approach?
My model, AffiliateLink contains a field (the raw HTML of the link) that I'll transform and output on the fly by adding a token. I have a model method that produces the replacement -- it is non-trivial because we use multiple affiliates and each has a special transformation rule that this method knows about:
def link_with_token(user_token)
# some gnarly code that depends on a lot of stuff the model knows
# that returns a proper link
end
To get my correct link html in JSON I have done these things:
add attr_accessor :link_html to model
add an instance method to set the new accessor
...
def set_link_html(token)
self.link_html = link_with_tracking_token(token)
end
override as_json in the model, replacing the original html_code with link_html
...
def as_json(options = {})
super(:methods => :link_html, :except => :html_code)
end
iterate over the collection returned in the controller method to do the transformation
...
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
#links.each do |link|
link.set_link_html(account_tracking_token)
end
render json: #links
end
end
end
This seems like a lot of stuff to do just to get my teensy-weensy transformation done. Helpful suggestions (relating to this problem and not to other aspects of the code, which is in flux now) are welcome.
1) A quick solution to your problem (as demonstrated here):
affiliate_links_controller.rb
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
render json: #links.to_json(:account_tracking_token => account_tracking_token)
end
end
end
AffiliateLink.rb
# I advocate reverse_merge so passed-in options overwrite defaults when option
# keys match.
def as_json(options = {})
json = super(options.reverse_merge(:except => :html_code))
json[:link_with_token] = link_with_token(options[:account_tracking_token])
json
end
2) A more hardcore solution, if you're really writing an API:
See this article describing your problem.
See the gem that the authors made as a solution.
See this railscast on using the gem.
3) And lastly, the convenient solution. If you have a convenient model relation, this is clean:
Pretending AffiliateLink belongs_to :user. And assuming user_token is an accessible attribute of User.
AffiliateLink.rb
# have access to user.user_token via relation
def link_with_token
# some gnarly code that depends on a lot of stuff the model knows
# that returns a proper link
end
def as_json(options = {})
super(options.reverse_merge(:methods => :link_with_token, :except => :html_code))
end
affiliate_links_controller.rb
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
render json: #links
end
end
end
Suppose you have an edit form with :remote => true. Your controller method looks like
def update
#article = Article.find(params[:id])
respond_to do |format|
if #article.update_attributes(params[:article])
format.html { redirect_to #article}
format.js { render :js => "window.location.replace('#{article_path(#article)}');"}
else
format.html { render :action => "edit" }
# Render update.js.erb which replaces the body of the form
format.js {}
end
end
end
What's the best way to do the redirect on successful article update in Rails 3.2.1? The raw JS solution seems a little sleazy, but I do like the fact that it's obvious that it's performing the same function as the format.html version.
I would prefer a solution that does not use RJS (if that even works in Rails 3.2?)
How about adding the below line of code on the view file itself
#some_file.js.erb
`window.location = redirect_path
As you mentioned in the question you do not prefer RJS, but I think it's better to follow this pattern better than writing the js code in the controller.
Does your ajax interact with a model (.php,.asp?). My preferred method in this instance is to create a success/fail criteria within the model after submission and redirect directly from there. Not sure if that makes sense in this application though?