rails find_by_object - ruby-on-rails

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

Related

Merge 2 objects in 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

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 %>

It does not work relationship nested resources rails

I have some problem with relationship in rails. My application has the relationship between the publisher and the Website.
When I add a site to the publisher using f.collection_select relationship is working properly. However, if I create a nested routing and am using
localhost:3000/publishers/8/sites/new
<% = Link_to ("Add site", new_publisher_site_path (publisher), class "btn btn-default navbar-btn")%
def new
#publisher = Publisher.find(params[:publisher_id])
#site = Site.new
end
It does not form a relationship.
If you mean relationship between models Site and Publisher you should use has_many, has_one, belongs_to or other associations rails assosiations.
and then you can write:
def new
#publisher = Publisher.find(params[:publisher_id])
#site = #publisher.sites // sites only associated with Publisher.find(params[:publisher_id])
end
Assuming relationship between publisher and site as publisher has_many :sites, and site belongs_to :publisher :
Do this in your site controller:
def new
#publisher = Publisher.find(params[:publisher_id])
#site = #publisher.sites.new
end
def create
#publisher = Publisher.find(params[:publisher_id])
#site = #publisher.sites.new(sites_params)
if #site.save
redirect_to publishers_path
else
render 'new'
end
end

Rails 3: alias_method_chain to set specific attribute first

When user's create a post I'd like to set the user_id attribute first. I'm trying to do this using alias_method_chain on the arrtibutes method. But I'm not sure if this is right as the problem I thought this would fix is still occurring. Is this correct?
Edit:
When my users create a post they assign 'artist(s)' to belong to each post, using a virtual attribute called 'artist_tokens'. I store the relationships in an artist model and a joined table of artist_ids and post_ids called artisanships.
I'd like to to also store the user_id of whomever created the artist that belongs to their post (and I want it inside the artist model itself), so I have a user_id column on the artist model.
The problem is when I create the artist for each post and try to insert the user_id of the post creator, the user_id keeps showing as NULL. Which is highly likely because the post's user_id attribute hasn't been set yet.
I figured to get around this I needed to set the user_id attribute of the post first, then let the rest of the attributes be set as they normally are. This is where I found alias_method_chain.
post.rb
attr_reader :artist_tokens
def artist_tokens=(ids)
ids.gsub!(/CREATE_(.+?)_END/) do
Artist.create!(:name => $1, :user_id => self.user_id).id
end
self.artist_ids = ids.split(",")
end
def attributes_with_user_id_first=(attributes = {})
if attributes.include?(:user_id)
self.user_id = attributes.delete(:user_id)
end
self.attributes_without_user_id_first = attributes
end
alias_method_chain :attributes=, :user_id_first
EDIT:
class ArtistsController < ApplicationController
def index
#artists = Artist.where("name like ?", "%#{params[:q]}%")
results = #artists.map(&:attributes)
results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}
respond_to do |format|
format.html
format.json { render :json => results }
end
end
In your controller, why not just do this:
def create
#post = Post.new :user_id => params[:post][:user_id]
#post.update_attributes params[:post]
...
end
But it seems to me that it would be much better to create the artist records after you've done validation on the post rather than when you first assign the attribute.
EDIT
I would change this to a callback like this:
class Post < ActiveRecord::Base
attr_accessor :author_tokens
def artist_tokens=(tokens)
#artist_tokens = tokens.split(',')
end
after_save :create_artists
def create_artists
#artist_tokens.each do |token|
...
end
end
end

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

Resources