Rabl Multi-model collection - ruby-on-rails

I am using RABL to output a Sunspot/SOLR result set and the search results object is composed of multiple model types. Currently within the rabl view I have:
object false
child #search.results => :results do
attribute :id, :resource, :upccode
attribute :display_description => :description
code :start_date do |r|
r.utc_start_date.to_i
end
code :end_date do |r|
r.utc_end_date.to_i
end
end
child #search => :stats do
attribute :total
end
The above works for a single model; however, when multiple model types are within the #search.results collection, it fails because both classes do not have the same instance methods. Does anyone know how to have different attributes based on the type? Ultimately, it would be nice to conditionally extend a different template within the results collection based on the type of object. Something like the below pseudo code:
child #search.results => :results do |r|
if r.class == Product
extends "product/base"
else
extends "some other class base"
end
end

You can take full control with 'node' and avoid this issue entirely in the 'worst' case:
node :results do
#search.results.map do |r|
if r.is_a?(Product)
partial("product/base", :object => r)
else # render other base class
partial("other/base", :object => r)
end
end
end
Does that help?

Related

Getting ActiveAdmin paths for any model

ActiveAdmin generates methods to get the paths of each class. For example, if I wanted to make a link to a FooBar object I would call link_to obj.name, admin_foo_bar_path(obj).
How would I do that without needing to hard code the class of the object in admin_foo_bar_path?
For example, if I wanted to make a function usable by any class...
def show_link(obj)
display = obj.try(:name) || obj.id
link_to display, ???
end
Hello such function already exists in active admin
https://github.com/activeadmin/activeadmin/blob/ef4e80ea2f0cb528ea146becd104f7b5b029910d/lib/active_admin/view_helpers/auto_link_helper.rb#L14
example:
index do
column :name, :sortable => :name do |company|
auto_link(company)
end
column :active do |company|
company.active? ? icon(:check) : icon(:x)
end
end

Rails query of parent model doesn't retrieve child models

I have parent and child models on rails 5 with mongoid. When I query the parent, with .includes command - I can see rails trying to query mongo db - but the result json does not return the child objects.
Parent Model:
class Activity
include Mongoid::Document
field :title, type: String
has_many :activity_pictures
end
Child Model:
class ActivityPicture
include Mongoid::Document
field :name, type: String
belongs_to :activity, :class_name => 'Activity'
end
The controller methods:
def index
#activities = Activity.includes(:activity_pictures).all
end
def show
Activity.includes(:activity_pictures)
end
off course, I have updated activity_params:
def activity_params
params.require(:activity).permit(:title, :activity_pictures)
end
How do i get the full json data from http://localhost:3000/activities.json and the single object links?
Whilst the associations are being loaded with the use of includes, you need to specifically call the loaded association in order for it to render. Try
def index
#activities = Activity.includes(:activity_pictures).all
render json: #activities, include :activity_pictures
end
The answer above by margo was the right lead. I am using jbuilder though, so the solution was to change the file
index.json.jbuilder
as follows:
json.array! #activities do |activity|
json.title activity.title
json.activity_pictures activity.activity_pictures do |activity_picture|
json.name activity_picture.name
end
end

Rails 4: Assigning a records "has many" attribute without database saving

I have a couple models shown below and I'm using the search class method in Thing to filter records
class Category << ActiveRecord::Base
has_many :thing
end
class Thing << ActiveRecord::Base
belongs_to :category
:scope approved -> { where("approved = true") }
def self.search(query)
search_condition = "%" + query + "%"
approved.where('name LIKE ?', search_condition)
end
end
It works fine in my Things controller. The index route looks like so:
def index
if params[:search].present?
#things = Thing.search(params[:seach])
else
#thing = Thing.all
end
end
On the categories show route I display the Things for this category. I also have the search form to search within the category.
def show
#category = Categories.find(params[:id])
if params[:search].present?
#category.things = #category.things.search()
end
end
So the problem is that the category_id attribute of all the filtered things are getting set to nil when I use the search class method in the categories#show route. Why does it save it to database? I thought I would have to call #category.save or update_attribute for that. I'm still new to rails so I'm sure its something easy I'm overlooking or misread.
My current solution is to move the if statement to the view. But now I'm trying to add pages with kaminiri to it and its getting uglier.
<% if params[:search].present? %>
<% #category.things.search(params[:search]) do |thing| %>
... Show the filtered things!
<% end %>
<% else %>
<% #category.things do |thing| %>
... Show all the things!
<% end %>
<% end %>
The other solution I thought of was using an #things = #categories.things.search(params[:search]) but that means I'm duplicated things passed to the view.
Take a look at Rails guide. A has_many association creates a number of methods on the model to which collection=(objects) also belongs. According to the guide:
The collection= method makes the collection contain only the supplied
objects, by adding and deleting as appropriate.
In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id]).
Like Yan said, "In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id])". Validations will solve this problem.
Records are being saved as nil because you have no validations on your model. Read about active record validations.
Here's the example they provide. You want to validate presence as well because records are being created without values.
class Person < ActiveRecord::Base
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false

Rails custom order of records?

My Rails site has different categories that the user can browse through. Right now they're rendered in the view through a simple loop, but I'd like to be able to choose the order from left to right in which they're displayed.
Here's the current code:
<h4>Explore Listings</h4>
<% #categories.each do |f| %>
<p class="category-page-category"><%= link_to f.name, view_category_path(f.id) %></p>
<% end %>
What would be an easy solution to accomplish what I want to do?
The easiest way I know of how to do this is to set up an AJAX form which will pass an order parameter which will be handled by the controller to re-render the form. Your controller would then listen for the order parameter and append that to #categories.
For example, your controller code might look something like:
def index
#categories = Category.<some_other_scope>
#categories = #categories.order(order_param) if order_param.present?
respond_to do |format
format.js { render :index }
end
end
private
def order_param
params.permit(:order)
end
First of all native sorting by the DB is to be preferred in every case (it's much faster).
If your sorting does not depend on already existing Category attributes, migrate a new attribute like position:
add_column :categories, :position, :integer
and add a scope to the model:
class Category < ActiveRecord::Base
def self.ordered default_order=nil
return scoped if default_order.nil? or
not( %w(asc desc).include?(default_order.to_s) )
order("position #{default_order}")
end
end
and then in the controller:
def index
#categories = Category.ordered params[:order]
end
following the sorting attribute approach you also should ensure your position attribute is valid defined. So like:
class Category < ActiveRecord::Base
before_create :default_position
validates :position,
presence: true,
numericality: true,
uniqueness: true,
on: :update
def self.ordered default_order=nil
return scoped if default_order.nil? or
not( %w(asc desc).include?(default_order.to_s) )
order("position #{default_order}")
end
private
def default_position
self.position = self.class.maximum("position") + 1
end
end
Changing positions has to be implemented.
Otherwise if positions never change, this means Categories have to be sorted by their creation datetime. Then you also could sort by created_at instead of position.
It sounds like you want to sort the array of categories before they are shown, no?
If so you can use http://www.ruby-doc.org/core-2.1.2/Enumerable.html#method-i-sort_by or http://www.ruby-doc.org/core-2.1.2/Enumerable.html#method-i-sort to sort your categories however you want before rendering them.
So this could end up looking something like:
<% #categories.sort_by(&:name).each do |f| %>
assuming categories have a name field. This should probably be done in the controller or in the model with a scope (if you are sorting based on a database field) to keep this logic out of the view.
looks like acts_as_list is exactly what you need

How to reuse RABL partial templates by keeping conventions?

I am using Ruby on Rails 4 and the RABL gem. I would like to reuse templates by rendering the show.json.rabl view from the index.json.rabl and keep convention stated at jsonapi.org (source: RABL documentation).
That is, I have the following files:
# index.json.rabl
collection #articles => :articles
attributes :id, :title, :...
# show.json.rabl
collection [#article] => :articles
attributes :id, :title, :...
Since for each article instance the index.json.rabl renders the same attributes as for the show.json.rabl I would like to reuse the latter as partial template, or (maybe) to extend the first.
What I would like to output is:
# index JSON output
{"articles":[{"id":1,"title":"Sample 1","...":...},{"id":2,"title":"Sample 2","...":...},{"id":3,"title":"Sample 3","...":...}]}
# show JSON output
{"articles":[{"id":1,"title":"Sample 1","...":...}]}
Here is a neat way:
things/base.rabl:
attributes :id, :title
child(:another_child, :object_root => false) { attributes :id, :title }
things/show.rabl:
object #thing
extends 'things/base'
things/index.rabl:
collection #things
extends 'things/base'
You can use extends - https://github.com/nesquena/rabl/wiki/Reusing-templates
object #post
child :categories do
extends "categories/show"
end
I was having trouble interpreting what I needed to do from https://github.com/nesquena/rabl/wiki/Reusing-templates but finally figured it out. For me, I had files structured like this: (names changed to protect the innocent ;) )
/app/controllers/api/my/boring_things_controller.rb
/app/views/api/my/boring_things/index.json.rabl
/app/controllers/api/my/interesting_things_controller.rb
/app/views/api/my/interesting_things/index.json.rabl
boring_things/index.json.rabl looked like this (except for real, it had a LOT more attributes):
collection #boring_things, :root => :things, :object_root => false
attributes :id,
:name,
:created_at,
:updated_at
And interesting_things/index.json.rabl looked ALMOST identical:
collection #interesting_things, :root => :things, :object_root => false
attributes :id,
:name,
:created_at,
:updated_at
I wanted to reuse just the attribute parts. The confusing part to me about https://github.com/nesquena/rabl/wiki/Reusing-templates was that I didn't really think I needed a 'node', and I didn't think I needed 'child' either because I wanted the attributes at the top level, not as a child object. But it turns out I did need 'child'. Here's what I ended up with:
/app/controllers/api/my/boring_things_controller.rb
/app/views/api/my/boring_things/index.json.rabl
/app/controllers/api/my/interesting_things_controller.rb
/app/views/api/my/interesting_things/index.json.rabl
/app/views/api/my/shared/_things.json.rabl
_things.json.rabl is this:
attributes :id,
:name,
:created_at,
:updated_at
boring_things/index.json.rabl is now this:
child #boring_things, :root => :things, :object_root => false do
extends "api/my/shared/_things"
end
and interesting_things/index.json.rabl is now this:
child #interesting_things, :root => :things, :object_root => false do
extends "api/my/shared/_things"
end
So for you, instead of rendering show from index, I'd try doing something where you extract article attributes out into a rabl file shared by index and show. In the same directory where your show.json.rabl and index.json.rabl are (I'm assuming it's views/articles/index.json.rabl and views/articles/show.json.rabl), create a "_article_attributes.json.rabl" file. Sorry I don't have your exact setup so I can't try it out myself syntactically, but it should be something like this in your index.json.rabl:
child #articles do
extends "articles/_article_attributes"
end
[A side note: Another thing that tripped me up when I was doing this was that since my shared file was in a sibling-directory to the different rabl files that used them and I was trying to use a relative path and that did NOT work. Instead I had to use the path starting with whatever's under "app/views" (i.e. "extends 'api/my/shared/_things'" not "extends '../shared/_things'"). That was a weird situation and I won't go into why we did it that way, but if you can it's better to have the shared file in the same directory as your index and show, like you're doing.]

Resources