How to KISS and smart this method - ruby-on-rails

I have an action in my RoR application, and it calls a different script depending on the user running it.
def index
#user = User.find(session[:user_id], :include => [ :balances, :links, :comments ])
render :file => "#{RAILS_ROOT}/app/views/user/index_#{#user.class.to_s.downcase}.html.erb"
end
How to make the call to render a more elegant and simple?

Try:
render :template => "user/index_%s" % #user.class.to_s.downcase

You can make it a partial (index_whatever.html.erb --> _index_whatever.html.erb) and it would look like this:
def index
#user = User.find(session[:user_id], :include => [ :balances, :links, :comments ])
render :partial => "index_#{#user.class.to_s.downcase}"
end
Also, what I would do is add a method in the user model, like this:
def view
"index_#{class.to_s.downcase}"
end
So your index action would be:
def index
#user = User.find(session[:user_id], :include => [ :balances, :links, :comments ])
render :partial => #user.view
end

Related

Pass one variable to next in JSON object in Rails 5

I need to be able to access user data in comments.
def index
#user = User.all
#libraries = Library.all.order('created_at ASC')
#comments = Comment.all
#user_likes = UserLike.all
render :json => #libraries, :include => [:user, :comments, :user_likes]
end
I tried this:
render :json => #libraries, :include => [:user, {:comments => :user}, :user_likes]
And it did not work. Any ideas?
Assuming you have the associations set up, you need to add include for the nested associations:
render :json => #libraries,
:include => {:user, {:comments => { include: :user}}, :user_likes}
When rendering JSON like this, Rails calls as_json first to convert the payload to hash. That's when the option include is used. You can check the documentation to see how it works.

Serialize JSON nested model in Ruby on Rails

I am new to rails and I have two models, one has a foreign key on the other model.
I created a controller and defined and index method which is working fine:
def index
#collections = Collection.all
render json: #collections
end
Which is rendering something like this:
[{"id":1,"title":"Collection
A","book_id":1,"created_at":"2016-04-10T18:41:32.709Z","updated_at":"2016-04-10T18:41:32.709Z"}]
I would like to transform that book_id field into a list of book objects, something like this:
[{"id":1,"title":"Collection A","books": [{"book_id": 1, "title:
"book_title"},],"created_at":"2016-04-10T18:41:32.709Z","updated_at":"2016-04-10T18:41:32.709Z"}]
Then I tried with:
def index
#collections = Collection.all
render :json => collections.as_json(
:include => { :book_title }
)
end
But is giving me syntax error and I cannot see how to do it properly in this doc http://guides.rubyonrails.org/layouts_and_rendering.html
I am using Rails 4.1.0
Try:
def index
#collections = Collection.all
render :json => #collections.as_json(
:include => :book
)
end
or if you would like just the :id and :title:
def index
#collections = Collection.all
render :json => #collections.as_json(
:include => {:book => {:only => [:id, :title]}}
)
end

How to get acts on taggable working

I am new to ruby on rails (and programming) and this is probably a really stupid question. I am using Rails 3.2 and trying to use acts_as_taggable_on to generate tags on articles and to have those tags show on article index and show pages as a clickable links.
I have tags clickable on both the article show and index pages, but the links just go back to the index page and don't sort according to the tag name. I have scoured the Internet and pieced together the code below from various sources, but I am clearly missing something.
Any help is greatly appreciated, as I have exhausted my seemingly limited knowledge! Thanks.
Here is what I have:
class ArticlesController < ApplicationController
def tagged
#articles = Article.all(:order => 'created_at DESC')
#tags = Article.tag_counts_on(:tags)
#tagged_articles = Article.tagged_with(params[:tags])
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #articles }
end
end
def index
#articles = Article.paginate :page => params[:page], :per_page => 3
#tags = Article.tag_counts_on(:tags)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #articles }
end
end
module ArticlesHelper
include ActsAsTaggableOn::TagsHelper
end
class Article < ActiveRecord::Base
acts_as_ordered_taggable
acts_as_ordered_taggable_on :tags, :location, :about
attr_accessible :tag_list
scope :by_join_date, order("created_at DESC")
end
article/index.html.erb
<% tag_cloud(#tags, %w(tag1 tag2 tag3 tag4)) do |tag| %>
<%= link_to tag.name, articles_path(:id => tag.name) %>
<% end %>
article/show.html.erb
<%= raw #article.tags.map { |tag| link_to tag.name, articles_path(:tag_id => tag) }.join(" | ") %>
routes.rb file snippet
authenticated :user do
root :to => 'home#index'
end
devise_for :users
resources :users, :only => [:show, :index]
resources :images
resources :articles
You can run 'rake routes' from the terminal to see all your paths. Here your tags are pointing at articles_path, which you will see routes to the index action in the articles controller ("articles#index")
You could create another route in your routes.rb file, something like:
match 'articles/tags' => 'articles#tagged', :as => :tagged
Place it above others in the routes file if you want it to take precedence, and remember you can always run 'rake routes' in the terminal to see how the routes are interpreted.
see http://guides.rubyonrails.org/routing.html#naming-routes for more info (maybe read the whole thing)
Another (probably better) option would be to combine your desired functionality into the index action using params, e.g. .../articles?tagged=true. Then you could use logic to define the #articles variable in the index controller based on params[:tagged]. A simple example might be
def index
if params[:tagged]
#articles = Article.all(:order => 'created_at DESC')
else
Article.paginate :page => params[:page], :per_page => 3
end
#tags = Article.tag_counts_on(:tags)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #articles }
end
end
This is called DRYing up your code (for Don't Repeat Yourself); it would save you the need for code duplication in the articles#tagged action, which would make it easier to understand and maintain your code base.
Hope that helps.

Ordering by Sum from a separate controller part 2

Ok, so I had this working just fine before making a few controller additions and the relocation of some code. I feel like I am missing something really simple here but have spent hours trying to figure out what is going on. Here is the situation.
class Question < ActiveRecord::Base
has_many :sites
end
and
class Sites < ActiveRecord::Base
belongs_to :questions
end
I am trying to display my Sites in order of the sum of the 'like' column in the Sites Table. From my previous StackOverflow question I had this working when the partial was being called in the /views/sites/index.html.erb file. I then moved the partial to being called in the /views/questions/show.html.erb file and it successfully displays the Sites but fails to order them as it did when being called from the Sites view.
I am calling the partial from the /views/questions/show.html.erb file as follows:
<%= render :partial => #question.sites %>
and here is the SitesController#index code
class SitesController < ApplicationController
def index
#sites = #question.sites.all(:select => "sites.*, SUM(likes.like) as like_total",
:joins => "LEFT JOIN likes AS likes ON likes.site_id = sites.id",
:group => "sites.id",
:order => "like_total DESC")
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #sites }
end
end
I think it should be
#sites = #question.sites.all(:select => "sites.*, SUM(likes.like) as like_total",
:joins => "LEFT JOIN likes AS likes ON likes.site_id = sites.id",
:group => "sites.id",
:order => "SUM(likes.like) DESC")
Ah...turns out that I had move the #sites controller code from the SitesController to the QuestionsController Show action. I then had to change my partial in the /views/questions/show.html.erb page from
<%= render :partial => #question.sites %>
to
<%= render :partial => #sites %>

How to build a JSON response made up of multiple models in Rails

First, the desired result
I have User and Item models. I'd like to build a JSON response that looks like this:
{
"user":
{"username":"Bob!","foo":"whatever","bar":"hello!"},
"items": [
{"id":1, "name":"one", "zim":"planet", "gir":"earth"},
{"id":2, "name":"two", "zim":"planet", "gir":"mars"}
]
}
However, my User and Item model have more attributes than just those. I found a way to get this to work, but beware, it's not pretty... Please help...
Update
The next section contains the original question. The last section shows the new solution.
My hacks
home_controller.rb
class HomeController < ApplicationController
def observe
respond_to do |format|
format.js { render :json => Observation.new(current_user, #items).to_json }
end
end
end
observation.rb
# NOTE: this is not a subclass of ActiveRecord::Base
# this class just serves as a container to aggregate all "observable" objects
class Observation
attr_accessor :user, :items
def initialize(user, items)
self.user = user
self.items = items
end
# The JSON needs to be decoded before it's sent to the `to_json` method in the home_controller otherwise the JSON will be escaped...
# What a mess!
def to_json
{
:user => ActiveSupport::JSON.decode(user.to_json(:only => :username, :methods => [:foo, :bar])),
:items => ActiveSupport::JSON.decode(auctions.to_json(:only => [:id, :name], :methods => [:zim, :gir]))
}
end
end
Look Ma! No more hacks!
Override as_json instead
The ActiveRecord::Serialization#as_json docs are pretty sparse. Here's the brief:
as_json(options = nil)
[show source]
For more information on to_json vs as_json, see the accepted answer for Overriding to_json in Rails 2.3.5
The code sans hacks
user.rb
class User < ActiveRecord::Base
def as_json(options)
options = { :only => [:username], :methods => [:foo, :bar] }.merge(options)
super(options)
end
end
item.rb
class Item < ActiveRecord::Base
def as_json(options)
options = { :only => [:id, name], :methods => [:zim, :gir] }.merge(options)
super(options)
end
end
home_controller.rb
class HomeController < ApplicationController
def observe
#items = Items.find(...)
respond_to do |format|
format.js do
render :json => {
:user => current_user || {},
:items => #items
}
end
end
end
end
EDITED to use as_json instead of to_json. See How to override to_json in Rails? for a detailed explanation. I think this is the best answer.
You can render the JSON you want in the controller without the need for the helper model.
def observe
respond_to do |format|
format.js do
render :json => {
:user => current_user.as_json(:only => [:username], :methods => [:foo, :bar]),
:items => #items.collect{ |i| i.as_json(:only => [:id, :name], :methods => [:zim, :gir]) }
}
end
end
end
Make sure ActiveRecord::Base.include_root_in_json is set to false or else you'll get a 'user' attribute inside of 'user'. Unfortunately, it looks like Arrays do not pass options down to each element, so the collect is necessary.
Incase anyone is looking for an alternative solution for this, this is how I solved this in Rails 4.2:
def observe
#item = some_item
#user = some_user
respond_to do |format|
format.js do
serialized_item = ItemSerializer.new(#item).attributes
serialized_user = UserSerializer.new(#user).attributes
render :json => {
:item => serialized_item,
:user => serialized_user
}
end
end
end
This returns the serialized version of both objects as JSON, accessible via response.user and response.item.
There are a lot of new Gems for building JSON now, for this case the most suitable I have found is Jsonify:
https://github.com/bsiggelkow/jsonify
https://github.com/bsiggelkow/jsonify-rails
This allows you to build up the mix of attributes and arrays from your models.
Working answer #2 To avoid the issue of your json being "escaped", build up the data structure by hand, then call to_json on it once. It can get a little wordy, but you can do it all in the controller, or abstract it out to the individual models as to_hash or something.
def observe
respond_to do |format|
format.js do
render :json => {
:user => {:username => current_user.username, :foo => current_user.foo, :bar => current_user.bar},
:items => #items.collect{ |i| {:id => i.id, :name => i.name, :zim => i.zim, :gir => i.gir} }
}
end
end
end

Resources