specify columns from has many through association Rails - ruby-on-rails

I have three models.
User
has_many :boards through => :celebrations
has_many :celebrations, :dependent => :destroy
Board
has_many :celebrations, :dependent => :destroy
has_many :users, :through => :celebrations do
def by_role(role)
find(:all, :conditions => ["celebrations.role = ?", role])
end
end
Celebration
:belongs_to :user
:belongs_to :board
Celebration Table
create_table :celebrations do |t|
t.column :board_id, :int, :null => false
t.column :user_id, :int, :null => false
t.column :role, :string, :null => false
t.column :token, :string
t.column :accepted, :boolean, :default => false
t.timestamps
In the controller:
#board = Board.find(session[:board_id])
#friends = #board.users.by_role("FRIEND")
In the view:
<% for friend in #friends do %>
<%= friend.name %>
<%= friend.email %>
However when I do the following:
<% friend.celebration.accepted %>
I get the following error:
undefined method `celebration' for #<User:0x104788c00>
How can I access the column 'accepted' in the celebrations table returned along with the record using the model extension "by_role(role)".
Thank you for your help in advance.

You can eager load the celebrations like this:
Board model:
Board
has_many :celebrations, :dependent => :destroy
has_many :users, :through => :celebrations do
def by_role(role)
find(:all, :conditions => ["celebrations.role = ?", role], :include => :celebrations)
end
end
With :include you will have one more hit:
Celebration Load (0.2ms) SELECT celebrations.* FROM celebrations WHERE (celebrations.user_id IN (5,6))
that loads the celebrations data for users.
This will avoid in this particular case two hits that would be needed to retrieve celebrations for each user. If you have 10 users you will end with one hit instead of 10.
Eager loading loads all the data you will need to cycle through in association with one query instead of n queries.
The problem remains for the accepted data because you will have an array of celebrations due to the has_many relationship with user.
You can use something like this:
<% friend.celebrations.map{|celeb| celeb.accepted }.join(",") %>

Related

Sudden no method error in Active Admin?

I have activeadmin installed and working fine for a 'reviews' section of may app, allowing me to add individual reviews to various locations in which my business is based. I tried to add the identical set up but using a BusinessReviews model rather than Reviews (thus allowing me to add business reviews on the same basis)
Everything works fine until I go into active admin (log in and accessing the 'Business Reviews' panel is fine, until I try and actually add a business review. I then get the error:
NoMethodError in Admin::BusinessReviews#new
undefined method `business_profile_image' for #<BusinessReview:0x007f893fe853d0>
My model is as follows:
class BusinessReview < ActiveRecord::Base
belongs_to :location
has_many :images, :as => :referrer
accepts_nested_attributes_for :images, :allow_destroy => true
def thumbnail
if self.images.count > 0
self.images.last.avatar(:thumb)
else
nil
end
end
end
my_app/admin/business_review.rb is as follows:
ActiveAdmin.register BusinessReview do
index do
column :business_reviewer_name
column :business_review_content
column :business_reviewer_address
column :rating
column :location do |business_review|
business_review.location.name
end
column :business_profile_image do |business_review|
image_tag(business_review.business_profile_image) if business_review.business_profile_image.present?
end
actions
end
show do |business_review|
attributes_table do
row :business_reviewer_name
row :business_profile_image
row :business_review_content
row :business_reviewer_address
row :rating
row :location do
business_review.location.name
end
end
panel "Images" do
table_for business_review.images do
column {|img| img.currently_used }
column {|img| image_tag(img.avatar.url(:large)) }
end
end
active_admin_comments
end
permit_params [:id, :business_reviewer_name, :business_profile_image, :business_review_content, :business_reviewer_address, :rating, :location_id], images_attributes: [:id,:_destroy,:avatar,:usage_type, :currently_used]
form do |f|
f.inputs 'Details' do
f.input :business_reviewer_name
f.input :business_profile_image
f.input :business_review_content
f.input :business_reviewer_address
f.input :rating
f.input :location
end
f.inputs "images" do
f.has_many :images, :allow_destroy => true, :heading => 'Images', :new_record => true do |imgf|
imgf.input :currently_used
imgf.inputs "Attachment", :multipart => true do
imgf.input :avatar, :as => :file, :hint => imgf.object.avatar? \
? imgf.template.image_tag(imgf.object.avatar.url(:large))
: imgf.template.content_tag(:span, "no image yet")
end
end
end
f.actions
end
end
Relevant part of my schema:
create_table "business_reviews", force: true do |t|
t.text "business_reviewer_content"
t.string "business_reviewer_name"
t.string "business_reviewer_address"
t.float "rating"
t.string "profile_image"
t.datetime "created_at"
t.datetime "updated_at"
end
The routes appear to be there ok too?
batch_action_admin_business_reviews POST /admin/business_reviews/batch_action(.:format) admin/business_reviews#batch
_action
admin_business_reviews GET /admin/business_reviews(.:format) admin/business_reviews#index
POST /admin/business_reviews(.:format) admin/business_reviews#creat
e
new_admin_business_review GET /admin/business_reviews/new(.:format) admin/business_reviews#new
edit_admin_business_review GET /admin/business_reviews/:id/edit(.:format) admin/business_reviews#edit
admin_business_review GET /admin/business_reviews/:id(.:format) admin/business_reviews#show
PATCH /admin/business_reviews/:id(.:format) admin/business_reviews#updat
e
PUT /admin/business_reviews/:id(.:format) admin/business_reviews#updat
e
DELETE /admin/business_reviews/:id(.:format) admin/business_reviews#destr
oy
I just don't get it as the reviews one I set up works perfectly and is identical (apart from the not haveing business_ appended to it).
According to your schema there is no business_profile_image but just profile_image:
t.string "profile_image"
So either rename the column or use profile_image instead of business_profile_image.

Has_and_belongs_to same model

I have and User model that will need to 'own' itself. Thats because the only way to a user connect with another is if they have this 'bound'.
So... I created a 'join model':
class CreateUserConnections < ActiveRecord::Migration
def change
create_table :user_connections, id: false do |t|
t.string :token
t.integer :user_a_id, :null => false
t.integer :user_b_id, :null => false
t.timestamps
end
add_index :user_connections, :token, unique: true
add_index :user_connections, [:user_a_id, :user_b_id], unique: true
end
end
and
class UserConnection < ActiveRecord::Base
belongs_to :user
belongs_to :user_a, :class_name => 'User'
belongs_to :user_b, :class_name => 'User'
before_create :generate_token
protected
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless UserConnection.exists?(token: random_token)
end
end
end
and then created the relation on my user model:
#unlocked users
has_and_belongs_to_many :users,
:join_table => "user_connections",
:foreign_key => "user_a_id",
:association_foreign_key => "user_b_id"
The problem is that when I create a relation with a user like:
User.find(1).users << User.find(2)
It creates the own relation from User 1 to User 2, but I tought that with the many_to_many relation the ownership relation from user 2 to 1 would be automatic.
What I'm missing here?
Thanx in advance
I should create the reverse realationship by myself.

Rake task imports one line only of txt file

I wrote many simple rake files to import txt files to my mysql.
Everything work perfectly except one model.
I have no errors so I don't know what happening.
The rake imports the first line only. Everything else don't!
The txt is on UTF8 encoding, by the way.
Could be the counter_cache of recipe's associations?
RAILS 3.1
Model:
class Recipe < ActiveRecord::Base
belongs_to :chef, :counter_cache => true
belongs_to :category, :counter_cache => true
belongs_to :cuisine, :counter_cache => true
belongs_to :festivity, :counter_cache => true
belongs_to :daily, :counter_cache => true
after_update :update_counter
# Setup accessible (or protected) attributes for your model
attr_accessible :name,
:slug,
:description,
:ingredients,
:steps,
:...
:status_id,
:created_at,
:updated_at
STATUS = { 'Não publicada' => 0, 'Publicada' => 1, 'Invisível' => 4 }
def status
STATUS.invert[status_id]
end
private
def update_counter
if category_id_changed?
Category.increment_counter(:recipes_count, category_id)
Category.decrement_counter(:recipes_count, category_id_was)
end
if cuisine_id_changed?
Cuisine.increment_counter(:recipes_count, cuisine_id)
Cuisine.decrement_counter(:recipes_count, cuisine_id_was)
end
if festivity_id_changed?
Festivity.increment_counter(:recipes_count, festivity_id)
Festivity.decrement_counter(:recipes_count, festivity_id_was)
end
if daily_id_changed?
Daily.increment_counter(:recipes_count, daily_id)
Daily.decrement_counter(:recipes_count, daily_id_was)
end
end
end
RAKE file:
namespace :db do
desc "import data from files to database"
task :import_recipe => :environment do
puts "Importing Recipe..."
# recipe.txt
File.open("lib/tasks/data/recipe.txt", "r").each do |line|
id, name, slug, description, ingredients, steps, other_tips, cook_time, recipe_yield, diet, light, lactose_intolerance, vegetarian, microwave, video_url, chef_id, category_id, cuisine_id, festivity_id, daily_id, likes_count, rating, ratings_count, views_count, comments_count, status_id, created_at, updated_at = line.strip.split("\t")
d = Recipe.new(
:id => id,
:name => name,
:slug => slug,
:description => description,
:ingredients => ingredients,
:steps => steps,
:...
:status_id => status_id,
:created_at => created_at,
:updated_at => updated_at
)
d.save!
end
puts "=========== > FINISHED!"
end
end

ActiveAdmin how to sort column with associations

I'm developing an ActiveAdmin app, and I want to sort a column of businesses by their "type". Unfortunately my code is not working. What code should I use to accomplish this? Here is my code...
app/models/business.rb
class Business < ActiveRecord::Base
belongs_to :type
attr_accessible :description, :email, :facebook, :foursquare, :google, :manager,
:mobile, :name, :phone, :type_id, :url, :yelp
end
app/models/type.rb
class Type < ActiveRecord::Base
attr_accessible :category
has_many :businesses
def to_s
category
end
end
app/admin/businesses.rb
ActiveAdmin.register Business, { :sort_order => :name_asc } do
scope :joined, :default => true do |businesses|
businesses.includes [:type]
end
index do
column :name
column :type, :sortable => 'businesses.type'
column :manager
column :email
default_actions
end
end
Thanks!
according to this discussion: https://github.com/gregbell/active_admin/pull/623, if you don't want to use scopes, you can use the scoped collection method instead:
ActiveAdmin.register Business, { :sort_order => :name_asc } do
scope :all, :default => true
index do
column :name
column :type, :sortable => 'types.category'
column :manager
column :email
default_actions
end
controller do
def scoped_collection
end_of_association_chain.includes(:type)
end
end
end
FIXED
column :type, :sortable => 'types.category'
Yes, the scoped_collection Evgenia provided works great. Also for more than one columns:
ActiveAdmin.register Foo do
index do
column :field_name_a, :sortable => 'association_a.field_name'
column :field_name_b, :sortable => 'association_b.field_name'
end
end
controller do
def scoped_collection
end_of_association_chain.includes([:association_a, :association_b])
end
end
This can be done.
Here I have a model called Star. A star belongs to a Person. I'm going to put the Person.name in the Star admin index, make it sortable, make it work with scopes, and add filters.
First you have to add the join model to each of your scopes. In this case I had 3 scopes: all, category_subscriptions and person_subscriptions. I'm declaring the scopes and adding the join model to them:
ActiveAdmin.register Star do
[ :all, :category_subscriptions, :person_subscriptions ].each do |sym|
scope(sym, :default => (sym == :all) ) do |stars|
stars.includes [:person]
end
end
end
Now to add the person name from the join model into my star index I do this:
index do
id_column
column("Name", nil, :sortable => :"people.name") {|star| star.person.name}
column("Email", nil, :sortable => :"people.email") {|star| star.person.email}
default_actions
end
Let's dissect that:
column("Name", nil, :sortable => :"people.name") {|star| star.person.name}
The first parameter is the column title.
The second is not needed since we're overriding sort and the value.
:sortable tells Active Admin how to sort the thing. This is the table name, since it's going into SQL.
The block tells Active Admin what to use as a row value.
Now to add some filters. This is much easier:
filter :person_name, :as => :string
filter :person_email, :as => :string
And you're done.
Based on your needs this is my implementation working for sorting, default index and filtering:
ActiveAdmin.register Business do
index do
column :name
column :type, :sortable => 'businesses.type'
column :manager
column :email
default_actions
end
controller do
def scoped_collection
super.includes(:businesses)
end
end
end
Just in case you are wondering how to sort when you have one more level of the association, this is the way I'm dealing with that for eg:
class TechResult < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
belongs_to :organization
end
class Organization < ActiveRecord::Base
has_many :users
end
and then in your tech_results section, you want to render an organization field, in my case the value tech_result.user.organization.name this is the way to sort them out:
ActiveAdmin.register TechResult do
config.batch_actions = false
permit_params :user_id
preserve_default_filters!
index do
column :id
column 'user', sortable: 'users.name' do |tr|
tr.user.name
end
column 'organization', nil, sortable: 'organizations.name' do |tr|
tr.user.organization.name.titleize if tr.user.organization
end
end
controller do
def scoped_collection
super.includes(user: [:organization])
end
end
end

Polymorphism and forms in Ruby on Rails

I've been full of questions lately, but thanks to this awesome community, I'm learning a ton.
I got all the help I needed with polymorphic associations earlier and now I have a question about tackling forms with polymorphic models. For instance I have Phoneable and User, so when I create my form to register a user, I want to be able to assign a few phone numbers to the user (ie: cell, work, home).
class User < ActiveRecord::Base
has_many :phones, :as => :phoneable
end
class Phone < ActiveRecord::Base
belongs_to :phoneable, :polymorphic => true
end
class CreateUsers < ActiveRecord::Migration
t.column :name, :string
t.references :phoneable, :polymorphic => true
end
class CreatePhones < ActiveRecord::Migration
t.column :area_code, :integer
t.column :number, :integer
t.column :type, :string
t.references :phoneable, :polymorphic => true
end
Now when I create my form, I'm getting confused. Normally I would do the following:
- form_for :user, :html => {:multipart => true} do |form|
%fieldset
%label{:for => "name"} Name:
= form.text_field :name
##now how do I go about adding a phone number?
##typically I'd do this:
##%label{:for => "phone"} Phone:
##= form.text_field :phone
Using polymorphism, would I just approach the same way, but use fields_for?
- user_form.fields_for :phone do |phone| %>
%label{for => "area_code"} Area Code:
= phone.text_field :area_code
%label{for => "number"} Number:
= phone.text_field :number
Is this the correct approach in this instance?
Before we go further, I did notice one issue - you do not need the t.references with the has_many side of the association. So you do not need it in the create_user model. What that does is it creates the phonable_id and the phoneable_type columns, you only need that in the polymorphic model.
You are heading down the correct path with the fields_for approach. But in order to get that working, you need to tell the model how to handle those fields. You do that with the accepts_nested_attributes_for class method.
class User < ActiveRecord::Base
has_many :phones, :as => :phoneable
accepts_nested_attributes_for :phones
end
and one minor thing, you will need to have the fields_for point to the exact name of the association
- form_for #user do |user_form|
- user_form.fields_for :phones do |phone|
Instead of
- form_for #user do |user_form|
- user_form.fields_for :phone do |phone|
and make sure you remove your stray %> erb tag :)
More on accepts_nested_attributes_for: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I hope this helps!

Resources