Merge 2 objects in Rails - ruby-on-rails

I have an items_controller.rb and an item_galleries_controller.rb.
The item_galleries takes an item_id. Everything works great.
Since I'm using react I created a presenter object that sends the appropriate data to my redux store. My problem is I can't output the associated item_galleries.
This is my index method in the items_controller.rb:
def index
#items = Item.all
#presenter = {
:items => {
:records => #items,
:filtered => #items,
:suggested => []
}
}
end
I was able to get this to work and have the appropriate data output to json, but unfortunately the jbuilder is different from this #presenter object.
json.images items.item_galleries do |p|
json.img url_for(p.image_url)
end
any idea on how to replicate above inside the index method and have it output along side the #items

My problem is I can't output the associated item_galleries
If Item has_many :item_galleries and item_gallery, item_id are columns on :item_galleries table then ...
#app/controllers/items_controller.rb
def index
#items = Item.all
...
#galleries = ItemGallery.where(item_id: #items.map(&:id))
end
#index.json.jbuilder
json.images #galleries do |gallery|
json.img url_for(gallery.image_url)
end

Related

Rails, order parents w/ pagination by child created_at, unless child.nil, order by parent created_at

In my video#index I'm trying to order video objects by the most recent created_at time of their optionally present child element, comment, otherwise order by parent video element created_at.
I'm refactoring from this...
def index
if params[:tag]
#videos = Video.tagged_with(params[:tag]).page(params[:page]).per_page(10).order(created_at: "desc")
else
#videos = Video.all.page(params[:page]).per_page(10).order(created_at: "desc")
end
end
but unsure of how to structure the desired function.
The first step is in refactoring here is to create scopes in your model to DRY it out:
class Video
scope :newest, ->{ order(created_at: :desc) }
def self.paginate(page = 1, per_page: 10)
self.page(page).per_page(10)
end
end
You can compose scopes in rails by mutating variables:
#videos = Video.all
if params[:tag]
#videos = #videos.tagged_with(params[:tag])
end
Or by using .merge and .merge!:
#videos = Video.paginate(params[:page]).tap.do |s|
if params[:tag]
s.merge!(Video.tagged_with(params[:tag]))
else
s.merge!(Video.newest))
end
end

Rails subcategories

How do I find and manage subcategories? (the find_subcategory method i'd defined does not seem to work.)
class CategoriesController < ApplicationController
before_action :find_category, only: [:show]
def index
#categories = Category.order(:name)
end
def show
end
private
def find_category
#category = Category.find(params[:id])
end
def find_subcategory
#subcategory = Category.children.find(params[:parent_id])
end
end
I'm using the acts_as_tree gem, which has:
root = Category.create("name" => "root")
child1 = root.children.create("name" => "child1")
subchild1 = child1.children.create("name" => "subchild1")
root.parent # => nil
child1.parent # => root
root.children # => [child1]
root.children.first.children.first # => subchild1
It's not clear what you want your find_subcategory method to do, but if you want it to find all subcategories of the category with id in params[:id], then change it to
def find_subcategories
#subcategories = Category.where(:parent_id => params[:parent_id]).all
end
In your original you're just looking for a single subcategory, and if you just want a single category you might as well just load it from it's id.
I know you accepted the answer, but I've done this before and so it might be beneficial to explain how we did it:
Firstly, we used the ancestry gem. I think acts_as_tree is deprecated -- acts_as_tree is better than ancestry, I forgot why we used it now - ancestry works in a very similar way (parent column, child methods, etc).
I'll explain our implementation with ancestry - hopefully it will give you some ideas for acts_as_tree:
#app/models/category.rb
class Category < ActiveRecord::Base
has_ancestry #-> enables the ancestry gem (in your case it should be "acts_as_tree"
end
This will allow you to populate the ancestry (in your case parent_id) column in your categories model, and (most importantly) gives you the ability to call the child methods attached to objects in the model:
#category.parent
#category.children
... etc.
--
The important thing to note here is how we're able to call the child objects (which would be subcategories in your case).
Your method is to create separate objects and have them inherit from each other. The beauty of ancestry / acts_as_tree is their added methods.
Any object with the correct parent ids can call their "children" as associative data:
In our case, we were able to associate all the objects using the ancetry column. This is slightly trickier than acts_as_tree because you have to provide the entire hierarchy in the column (which is lame), nonetheless the result is still the same:
#app/controllers/categories_controller.rb
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
end
#app/views/categories/index.html.erb
<%= render #categories %>
#app/views/categories/_category.html.erb
<%= category.name %>
<%= render category.children if category.has_children? %>
This will output the sub categories for you:
How do I find and manage subcategories
You can do it like this:
#subcategories = Category.where parent_id: #category.id
or if you have your ancestry set up properly, you should be able to use the following:
#config/routes.rb
resources :categories
#app/controllers/categories_controller.rb
class CategoriesController < ApplicationController
def show
#category = Category.find params[:id]
end
end
This will allow you to use:
#app/views/categories/show.html.erb
<% #category.children.each do |subcategory| %>
<%= subcategory.name %>
<% end %>

rails find_by_object

I know the rails method find_by_name but what if instead of a name I have an object? Assuming I have #car and #brand objects. How can I search for all cars that have a specific brand object.
Something like #cars = Car.find_by_brand(#brand)
I have tried #cars = #brand.cars but that only seems to pull one parent model.
Edit - More info on code
Car controller:
has_and_belongs_to_many :brands
Brands model:
attr_accessible :name
has_and_belongs_to_many :cars
Brands Controller
def create
#car = Car.find(params[:car_id])
#brand = Brand.create
#brand.assign_attributes({ :name => params[:brand][:brands][:name] })
#brand.cars << #car
if #brand.save
redirect_to #car
else
flash[:notice] = "Error!"
redirect_to #car
end
end
def findcars
#brand = Brand.find_by_name(params[:brand_name])
#cars = #brand.cars
end
View
<%= link_to brand.name, findcars_car_brand_path(#car, brand_name: brand.name), method: "get" %>
Routes
resources :cars do
resources :brands do
member { get :findcars }
end
end
Tables - There is no model for the join table
brands (name:string)
cars (name:string)
brands_cars (brand_id:integer, car_id:integer)
Let's look at findcars first. .joins(:brands) is key as it allows you to narrow the query via brand conditions ('brands.attribute' => brand_value), while building Car objects.
The SQL will look something like this:
SELECT "cars".* FROM "cars" INNER JOIN "cars_brands" ON "cars_brands"."car_id" = "cars"."id" INNER JOIN "brands" ON "brands"."id" = "cars_brands"."brand_id" WHERE "brands"."id" = 1
and the implementation like this:
# If all you care about is the #cars - as the method name indicates
def findcars
#cars = Car.joins(:brands).where('brands.name' => params[:brand_name])
end
# If you need both variables
def findcars
#brand = Brand.find_by_name(params[:brand_name])
#cars = Car.joins(:brands).where('brands.id' => #brand)
end
Without the joins, your sql looks like this:
SELECT "cars".* FROM "cars" WHERE "brands"."id" = 1
This will break as 'brands' means nothing in this context. You need to give rails a little more detail so it can build the query properly.
Now the create method:
# #brand = Brand.create
# #brand.assign_attributes({ :name => params[:brand][:brands][:name] })
# params[:brand][:brands][:name] indicates something fishy with your form
# since this form is in the context of Brand, params[:name] should suffice
#brand = Brand.create(:name => params[:brand][:brands][:name])
Does redirect_to #car mean that the form is a Car-centric form? Using *form_for* in the correct context will simplify the object creation.
For 2 great overviews, check out:
http://guides.rubyonrails.org/active_record_querying.html
http://guides.rubyonrails.org/association_basics.html

Including nested objects in JSON response, from MongoMapper objects

class Api::StoresController < ApplicationController
respond_to :json
def index
#stores = Store.all(:include => :products)
respond_with #stores
end
end
Returns only stores without their products, as does
Store.find(:all).to_json(:include => :products)
The association is tested, I can see the nested products in console ouput from, say,
Store.first.products
What's the correct way to get them products included with MongoMapper?
Here are my models:
class Store
include MongoMapper::Document
many :products, :foreign_key => :store_ids
end
class Product
include MongoMapper::Document
key :store_ids, Array, :typecast => 'ObjectId'
many :stores, :in => :store_ids
end
UPDATE
In trying Scott's suggestion, I've added the following to the Store model:
def self.all_including_nested
stores = []
Store.all.each do |store|
stores << store.to_hash
end
end
def to_hash
keys = self.key_names
hash = {}
keys.each{|k| hash[k] = self[k]}
hash[:products] = self.products
hash[:services] = self.services
hash
end
And in the controller:
def index
#stores = Store.all_including_nested
respond_with #stores
end
Which looks like it should work? Assuming the array of hashes would have #to_json called on it, and then the same would happen to each hash and each Product + Service. I'm reading through ActiveSupport::JSON's source, and so far that's what I've grokked from it.
But, not working yet... :(
Have a look at the as_json() method. You put this in your models, define your json, and then simply call the render :json method and get what you want.
class Something
def as_json(options={})
{:account_name => self.account_name,
:expires_on => self.expires_on.to_s,
:collections => self.collections,
:type => "Institution"}
end
end
You'll notice self.collections which is a many relationship. That model also has as_json() defined:
class Collection
def as_json(options={})
{:name => self.name,
:title => self.title,
:isbn => self.isbn,
:publisher => self.publisher,
:monthly_views => self.monthly_views}
end
end
This one contains self.monthly_views which represents another many relationship.
Then in your controller:
#somethings = Something.all
render :json => #somethings
You might have to create your own method to generate a hash then turn the hash into JSON. I'm thinking something like this:
store = Store.first
keys = store.key_names
hash = {}
keys.each{|k| hash[k] = store[k]}
hash[:products] = store.products
hash.to_json

Passing a variable into a models after_initialize method

I have the following (heavily simplified) model, that uses will_paginate
class Search < ActiveRecord::Base
attr_reader :articles
def after_initialize
#articles = Article.paginate_by_name name, :page => 1
end
end
and the controller code in my show action is
#search = Search.new(params[:search])
This all works fine, but notice i hard coded the page number to 1, problem is passing params[:page] value into the after_initialize method, can anyone suggest an elegant way to do this please?
Thanks
Add a page parameter (or even better an options hash parameter) to the initialize method:
class Search
def initialize(search, options = {})
#options = options
end
def after_initialize
#articles = Article.paginate_by_name name, :page => #options[:page]
end
end
and then in your controller:
#search = Search.new(params[:search], :page => params[:page])
You can even supply default values to the option hash, if you like.

Resources