I have the following classes:
class VideoChannel < ActiveRecord::Base
#Associations
belongs_to :video_playlist, :dependent => :destroy
VideoChannel.video_playlist_name
delegate :name, :id, :list_type, :list_id, :manual, :to => :video_playlist, :prefix => true
#validations
validates_presence_of :name
#After Functions
def after_create
video_playlist = VideoPlaylist.new(:name => self.name,
:list_type => "VideoChannel",
:list_id => self.id)
video_playlist.save
end
And :
class VideoPlaylist < ActiveRecord::Base
belongs_to :list, :polymorphic => true
has_many :video_channels, :dependent => :destroy
delegate :name, :id, :description, :to => :video_channel, :prefix => true
end
I'm trying to use the Rails Delegate function to create a link in the VideoChannel page that allows me to to link to the Video Playlist and edit the contents there. So the association is there and You can currently edit the playlists by going through the playlists section but we want to combine them. I can't seem to figure this out. Im also very new to Rails, still working through the guides etc.
Edit: Here's the view code
<%= link_to '<span class="pen icon"></span>Edit',
content_url(:controller =>"video_playlists", :id => channel.video_playlist_id, :action => "edit"),
:class => "button right" %>
Here are teh relevant pieces of the controllers:
class VideoChannelsController < ApplicationController
# GET /videochannels
# GET /videochannels.xml
def index
#video_channels = VideoChannel.roots(:order => 'order_num')
#video_channels_parents = #video_channels.group_by {:parent_id}
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #video_channels }
end
end
# GET /videochannels/1
# GET /videochannels/1.xml
def show
#video_channel = VideoChannel.find(params[:id], :order => 'order_num')
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #video_channel }
end
end
end
class VideoPlaylistsController < ApplicationController
# GET /video_playlists
# GET /video_playlists.xml
def index
if !params[:with].nil?
#video_playlists = VideoPlaylist.find(:all, :conditions => {:list_type => 'VideoShow'})
else
#video_playlists = VideoPlaylist.find(:all, :conditions => {:list_type => 'Section'})
end
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #video_playlists }
end
end
# GET /video_playlists/1
# GET /video_playlists/1.xml
def show
#video_playlist = VideoPlaylist.find(params[:id], :include => [{:video_video_playlists => :video}, {:videos => :asset}, {:videos => :content_image}])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #video_playlist }
end
end
end
Where did the line
VideoChannel.video_playlist_name
Come from? What's it doing? You're also calling a method on the class not an instance (sort of - Ruby isn't quite like this, but it's enough to explain).
Anyway:
Delegate is really for avoiding lots of train wreck code like this:
fred.jim.bill.xxx
You've said that they belong to each other - the relationships look like they're the wrong way round. Why are you creating the parent from inside the child? How are you going to have many video channels belonging to a given playlist?
I think you need to look at build and the relationship names. To gat past me maybe misunderstanding your model, lets have switch to a product that has many stock items:
class Product < ActiveRecord::Base
has_many :stock_items
end
class StockItem < ActiveRecord::Base
belongs_to :product
end
This means that stock_item will have a column product_id.
So, say you're creating a product you'd do something like:
product.stock_items.build # :whatever the params are required
This automatically sets the id's for you and means you don't have to set id's. Then when you do product.save it will save all the related stock items too.
And in the view for this toy model, then if you were displaying one of the stock items, you'd use delegate to show the name of the product in the view without having to do lost of stock_item.product.name (for example).
I hope this helps.
Related
I am running into a strange situation, considering the following models:
class Collection < ActiveRecord::Base
attr_accessible :name, :season, :year
has_many :collection_items_assocs
has_many :items, :through => :collection_items_assocs
end
class Item < ActiveRecord::Base
attr_accessible :name, :reference, :item_type_id
has_many :pictures
has_one :item_type
end
class CollectionItemsAssoc < ActiveRecord::Base
attr_accessible :collection_id, :item_id
belongs_to :item
belongs_to :collection
end
I can successfully retrieve Items associated to a Collection with the following code:
# GET /collections/1
# GET /collections/1.json
def show
#collection = Collection.find(params[:id])
#collection.items = Collection.find(params[:id]).items
respond_to do |format|
format.json { render json: #collection.to_json(:include => {:items => #collection}) }
end
end
But when I try to include pagination (for items) like that
# GET /collections/1
# GET /collections/1.json
def show
#collection = Collection.find(params[:id])
**#collection.items = Collection.find(params[:id]).items.paginate(:page => params[:page],:per_page =>1)**
respond_to do |format|
format.json { render json: #collection.to_json(:include => {:items => #collection}) }
end
end
It works for the following call
/retailapp/collections/1?format=json&**page=1**
Then if I call
/retailapp/collections/1?format=json&**page=2**
the records in the association table CollectionItemsAssoc are deleted
I really don't get it
Thanks for your help
The problem is the code to fetch the items
#collection.items = Collection.find(params[:id]).items
it assigned the fetched items to current collection object.
you need to change the response to support the pagination on associate objects
def show
#collection = Collection.find(params[:id])
respond_to do |format|
format.json {
json_hash = #collection.as_json
json_hash[:items] = #collection.items.paginate(:page => params[:page],:per_page =>1).as_json
render json: json_hash.to_json
}
end
Additionally you can overwrite to_json method inside Collection model.
I have the following models in my Rails application:
class Transaction
belongs_to :category
has_one :group, :through => :category
class Category
belongs_to :group
has_many :transactions
class Group
has_many :categories
has_many :transactions, :through => :category
In my controller is the following:
#transactions = Transaction.includes(:category, :group).group("groups.id").sum("amount")
respond_to do |format|
format.json{
render :json => JSON.generate(#transactions.as_json(:include => [:category, :group]))
}
end
This produces the following json (A):
{"1":2000,"3":5000,"2":1000}
However, my goal is to produce something like this (B):
[{"group_id":1,"amount":2000},{"group_id":3,"amount":5000},{"group_id":2,"amount":1000}]
Any help on how I can go from A to B would be greatly appreciated.
Try:
def user_transactions
#transactions = Transaction.includes(:category, :group).group("groups.id").sum("amount")
respond_to do |format|
format.html
format.json do
render :json => custom_json_for(#transactions)
end
end
end
private
def custom_json_for(value)
list = value.map do |k,v|
{ :group_id => k,
:amount=> v
}
end
list.to_json
end
If you're doing a lot a JSON-serialization, I would recommend you to take a look at ActiveModelSerializers:
https://github.com/rails-api/active_model_serializers
I have the following models:
require 'books_projects.rb'
class Project < ActiveRecord::Base
has_many :book_to_projects
has_many :books, :through => :book_to_projects
end
require 'books_projects.rb'
class Book < ActiveRecord::Base
has_many :book_to_projects
has_many :projects, :through => :book_to_projects
end
books_projects.rb:
class BookToProject < ActiveRecord::Base
set_table_name "books_projects"
belongs_to :book
belongs_to :project
end
In my projects controller, I am trying to do this:
#projects = Project.find(:all, :include => [:books])
My hope is to get a nested data structure back that looks something like:
projects: [
{
..,
books: [
{
..
},
{
..
}
]
}
]
This doesn't work. How can I achieve this?
Also, does the above model relationship call for the use of has_and_belongs_to_many in project and book instead of specifying the join table?
Thanks in advance!
Edit 1:
I don't have a view. This acts like a REST service only. I am doing the following:
def index
#projects = Project.find(:all, :include => [:books])
respond_to do |format|
format.xml { render :xml => #projects }
format.json { render :json => #projects}
end
end
This does not produce the output that I'm looking for (mentioned above).
You code does exactly what you expect it to do. The problem is in verifying that.
Try this:
project = Project.find(:first)
project.books.loaded?
would return false
project = Project.find(:first, :include => [:books])
project.books.loaded?
would return true :)
The eager-loaded associations do not normally show up when you inspect the object.
So turns out that the following works:
def index
#projects = Project.find(:all)
respond_to do |format|
format.json { render :json => #projects.to_json(:include => [:books]) }
end
end
Can someone tell me why this works and the one before didn't?
We know that the distance field gets dropped when using the Geokit gem in Rails with acts_as_mappable :through model class. I wonder if there's a way to work around this to get the distance field back. I tried to follow the monkey-patching example over here:
http://www.sobyteme.com/news/2010/05/13/computers/2010/06/25/geokit-acts_as_mappable-through-with-distance-attribute/
but it didn't work for me.
Well, Steve's suggestion over on his site was accurate, I was missing calling sort_by_distance_from after doing the find. So credit goes to him for this answer.
I'm on Rails v3.0.7. Here's my code:
class Office < ActiveRecord::Base
has_many :users
acts_as_mappable :default_units => :miles,
:default_formula => :sphere,
:lat_column_name => :latitude,
:lng_column_name => :longitude
end
class User < ActiveRecord::Base
belongs_to :office
acts_as_mappable :through => :office
end
users_controller.rb:
# Monkey patching to include the 'distance' attribute
module Geokit
module Mappable
def to_lat_lng
return self if instance_of?(Geokit::LatLng) || instance_of?(Geokit::GeoLoc)
return LatLng.new(self.office.send(self.office.class.lat_column_name),
self.office.send(self.office.class.lng_column_name)) if self.class.respond_to?(:acts_as_mappable)
nil
end
end
end
class UsersController < ApplicationController
def location
#lat = params[:lat].to_f
#long = params[:long].to_f
#origin = [#lat, #long]
#users = User.find(:all,
:origin => #origin,
:conditions => "distance < 3")
# We have to add this to get the 'distance' field
#users.sort_by_distance_from(#origin)
respond_to do |format|
format.html
format.xml { render :xml => #users.to_xml(:methods => :distance)}
format.json { render :json => #users.to_json(:methods => :distance)}
end
end
...
end
I'm trying to get a nested form view to update properly. This is however causing problems when the second form has existing data.
I'm using accepts_nested_attributes_for and nested_form_for. The second which only purpose is to dynamically add the form element using js. See github for more
The error I'm getting is:
Couldn't find Muscle with ID=3685340 for Exercise with ID=212831413
I've tried to manually do the updating but my code didnt really work and I'm under the impression that it shouldnt be needed since rails is suppose to take care of it under the hood.
The idea is that: Exercises has many Muscles through Targets
And from within the Exercise form I want to be able to add target muscles.
My models:
class Exercise < ActiveRecord::Base
has_many :targets, :dependent => :destroy
has_many :muscles, :through => :targets
accepts_nested_attributes_for :muscles, :reject_if => :all_blank
...
end
class Target < ActiveRecord::Base
belongs_to :exercise
accepts_nested_attributes_for :exercise, :update_only => true
belongs_to :muscle
end
class Muscle < ActiveRecord::Base
has_many :targets, :dependent => :destroy
has_many :exercises, :through => :targets
end
My (haml) view:
%p
%b Target(s):
= f.fields_for :muscles do |e|
= e.collection_select :id, Muscle.all, :id, :name
= e.link_to_remove "-remove"
= f.link_to_add "Add target muscle", :muscles
And finally my failing controller:
def update
#exercise = Exercise.find(params[:id])
#exercise.user = current_user
params[:exercise][:muscles_attributes].each { |id, muscle|
target = Target.where(:exercise_id => #exercise.id , :muscle_id => muscle[:id]).first
if target && !(muscle[:_destroy] == "false")
puts "!!>>>>>>>>>>>>>>>>>>destroy target #{target.exercise_id} #{target.muscle_id}"
target.destroy
else
if !target
t = #exercise.targets.build(:muscle_id => muscle[:id])
t.save
end
end
}
respond_to do |format|
if #exercise.update_attributes(params[:exercise])
format.html { redirect_to(#exercise, :notice => 'Exercise was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #exercise.errors, :status => :unprocessable_entity }
end
end
end
Please let me know if I should expose more of my code (the final result will be opensource anyway) ill happily push to github or whatever is requested, thanks in advance.
You could achieve this by manipulating the intermediate table, here targets.
Insert in your model:
accepts_nested_attributes_for :targets
And in your html, append this line with javascript:
<input name="exercise[targets_attributes][0][muscle_id]" value="the_muscle_id_goes_here" type="hidden">