How to access associated model through another model in Rails? - ruby-on-rails

UPDATE (specific and more detailed previous version is below):
I'm developing a TV station web site. Here are requirements for my Program section:
Each Program has ONE Category.
Each Program has ONE Subcategory.
Each Category has MANY Subcategories
Each Category has MANY Programs.
Each Subcategory has ONE Category
Each Subcategory has MANY Programs.
I want to retrieve all these three models to be associated. For example, I should be able to retrieve below data from my views:
While:
p = Program.find(1)
p_cat = ProgramCategory.find(1)
p_subcat = ProgramSubcategory.find(1)
I should be able to retrieve and also EDIT these:
p.program_category
p.program_subcategory
or
program_category.programs
program_subcategory.programs
You can see what I tried below to achieve these requirements. You may recommend me a totally different way or fix my mistakes.
Thank you
============================================================
I have 3 models. They are supposed to be nested in eachother.
ProgramCategory > ProgramSubcategory > Program
Here are my codes:
ProgramCategory model:
has_many :programs
has_many :program_subcategories
ProgramSubcategory model:
belongs_to :program_category
has_many :programs
Program Model:
belongs_to :program_category
belongs_to :program_subcategory
As I create a new Program, I can set its Category and everything is fine. I can access them from both sides. For example,
program.program_category
gives me what I expected. and also
program_category.programs
gives me what I want to have, too.
BUT, -here comes the question-
When I try to access program.program_subcategory, I receive just a nil.
Eventhough my Subcategory's Category is set and my Program's Category is set too, why I can't access program.program_subcategory directly?
When I type program_category.program_subcategories, I receive all Subcategories owned by that Category. But I CAN NOT get Subcategories from directly a Program object.
My scheme is below. Any help is appriciated.
ActiveRecord::Schema.define(:version => 20120926181819) do
create_table "program_categories", :force => true do |t|
t.string "title"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "program_subcategories", :force => true do |t|
t.integer "program_category_id"
t.string "title"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "program_subcategories", ["program_category_id"], :name => "index_program_subcategories_on_program_category_id"
create_table "programs", :force => true do |t|
t.integer "program_category_id"
t.integer "program_subcategory_id"
t.string "title"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "programs", ["program_category_id", "program_subcategory_id"], :name => "my_join1", :unique => true
end

The design is strange a bit. If you need nesting like
ProgramCategory > ProgramSubcategory > Program
then you need
class Program < ActiveRecord::Base
belongs_to :program_subcategory
end
class ProgramSubcategory < ActiveRecord::Base
belongs_to :program_category
has_many :programs
end
class ProgramCategory < ActiveRecord::Base
has_many :programs, :through => :program_subcategories
has_many :program_subcategories
end
This way when you create a program you can assign a subcategory to it. And this subcategory is already assigned to category, so you can access it via program.program_subcategory.program_category
And you do not need program_category_id foreign key in programs because program is not connected to category directly, but via subcategory.
UPDATE
Each Program has ONE Category.
Each Program has ONE Subcategory.
Each Category has MANY Subcategories
Each Category has MANY Programs.
Each Subcategory has ONE Category
Each Subcategory has MANY Programs.
Then I believe that my answer is still valid. You see, my structure is the same as your description except Each Program has ONE Category (because rails has no belongs_to through). you has one is actually belongs_to (because it can belong to only one).
But as soon as Each Program has ONE Subcategory and Each Subcategory has ONE Category program's subcategory's category will be the ONLY program's category. You can have p.program_category by defining a method on a Program class:
def program_category
program_subcategory.program_category
end
Now for the part of
I should be able to retrieve and also EDIT these:
p.program_category
Imagine you have a Program in subcategory Comedy from category Movies.
You say you want to be able to EDIT programs category directly (if I understood correctly), like this:
p.program_category = ProgramCategory.find_by_name("Sports")
But what you expect to be program's subcategory then? As soon as Sports have many subcategories? Do you expect it to be blank?
So in this design the only way to change program's category is to change program's subcategory:
p.program_subcategory = ProgramSubcategory.find_by_name("Tennis")
And this will manke program's category == Sports, because Tennis belongs to Sports.
Note: If you really want sometimes to change program's category directly, leaving its subcategory blank it requires another design of course. I do not think it is very difficult but it requires more work with less help from Rails AR Associations magic.

Related

Relations not working as expected

This will be fairly quick and easy for most of you...I have a table called types, and another called projects. A project can only have one type, but a type can have many projects. For instance a community garden project and a playground project can both have the type of 'greenspace'. So I have set up a has_many association. In my types model I have this:
has_many :projects
and in my projects model I don't have anything (I previously had has_one in it but upon looking at the docs it seemed incorrect). In the projects#show view I would like the name of the type to display. The parks project's view should say 'greenspace'. but I am getting the error
undefined method `type' for #<Project:0x007ffdd14fcde8>
I am trying to access that name using:
<h3>Type: <%= #project.type.project_type %> </h3>
i have also tried:
<h3>Type: <%= #project.type_id.project_type %> </h3>
but of course type_id gives a number, and there is no project_type for a number. project_type being the name of the column which holds the string data 'greenspace'. Am I accessing it wrong? Or have I set it up incorrectly?
Also in my schema, projects looks like this:
create_table "projects", force: :cascade do |t|
t.string "type_id"
t.text "description"
t.integer "money_needed"
t.integer "money_raised"
t.float "interest_offered"
t.datetime "end_date"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
t.text "url"
end
Project can belong_to both. Like this
#app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :type
belongs_to :user
#...
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :projects
#...
end
#app/models/type.rb
class Type < ActiveRecord::Base
has_many :projects
#...
end
In the Project model you should state:
belongs_to => :type
In general, for most associations there is going to be an inverse. Not always, as you might have multiple associations in Type for Project. For example as well as your current has_many :projects, you might have others to return only projects that are unfinished, and such an association would not need an inverse.
Bear in mind that when you state: #project.type Rails is going to look for a method on #project. The association is what provides this method, and effectively the result is then the Type object that is referenced by the Project. It's important to realise that #project.type only returns a Type because the association tells it to -- the magic does not extent to just inferring that that is what is wanted.

Model style for Ruby on Rails application

So currently my application has a model called Vendors. I want each Vendor to have one or many owners, one or many members, and then people who track or follow the Vendor. I'm trying to decide the best layout for this and I've messed around with a few things. All of the Owners, members, and followers would also be coming from my User model. Therefore I'm thinking of making one association table with booleans as to whether the User is a follower, owner, or member.
For clarification - Owner and member would be people who work at the vendor
schema.db
create_table "vendor_relationships", force: true do |t|
t.integer "user_id"
t.integer "vendor_id"
t.boolean "owner"
t.boolean "member"
t.boolean "follower"
t.datetime "created_at"
t.datetime "updated_at"
end
but I'm wondering if this is the best way to go about this when it comes to speed and/or quality of code. Previously I had created separate tables for vendor_owners, vendor_members, and vendor_followers. Then I associated them with a has_many :through relationship, but I'm stuck onto which way is the best.
They would all have separate logic, or rather separate permissions as to what they were allowed to view and edit with respect to the vendor. I looking at cancan and pundit for role based authorization, but I didn't think it really applied for this situation.
A role based authorization could work if I build the schema like this
create_table "vendor_relationships", force: true do |t|
t.integer "user_id"
t.integer "vendor_id"
t.string "role"
t.datetime "created_at"
t.datetime "updated_at"
end
And then check for possible user roles against the string but I'm not sure which of these is the better option.
EDIT 1:
I'm going with the t.string "role" route, but I'm wondering how I can write the code into my model so that the associations with role = "owner" would be accessible by doing Vendor.owners... etc with Vendor.members and Vendor.followers.
This is what my code looks like currently.
has_many :owners, through: :vendor_relationships, class_name: "User", -> { where role: owner }
This for each variation - owners, members, followers. I've also tried using
has_many :owners, through: :vendor_relationships, class_name: "User", conditions: => ['vendor_relationship.role = "owner"']
but everything is giving me syntax errors here. Would appreciate some help thanks.
I think you are on the right track. I would do the following:
Model your owners, members, and followers all as User, but then give them each a role. Start with a single role per user to keep it simple. See: https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
In the many-to-many joins table, I would remove the booleans and instead let your association handle that logic.
has_many :owners, through: :vendor_members, class_name: "User", conditions: {"users.role = 'owner'"}
Then you can just interact with owners/members/followers like so: owners = vendor.owners
I would also consider changing the name of the vendor_members table since one of the types is also members. Maybe something like vendor_relationships

Custom foreign_key in model gives PG::Error column does not exist - Rails

I have a VideoCollection model that will contain many records from another model (called VideoWork), using the has_many relationship. The VideoCollection model inherits from the Collection model using single table inheritance, while the VideoWork model inherits from the Work model.
I'm having a problem when I try to call up the video_works that belong to a video_collection.
In my video_collection#show action, I use the following to try to display a collection's works:
def show
#video_collection = VideoCollection.find(params[:id])
#collections = #video_collection.children
#works = #video_collection.video_works
end
But when I try to use #works in the show view, I get the following:
PG::Error: ERROR: column works.video_collection_id does not exist
SELECT "works".* FROM "works" WHERE "works"."type" IN ('VideoWork') AND "works"."video_collection_id" = $1
##(Error occurs in the line that contains <% #works.each do |work| %>)
My model files:
#----app/models/video_collection.rb----
class VideoCollection < Collection
has_many :video_works
end
#----app/models/video_work.rb----
class VideoWork < Work
belongs_to :folder, class_name: "VideoCollection", foreign_key: "folder_id"
end
The "parent" models:
#----app/models/collection.rb - (VideoCollection inherits from this)
class Collection < ActiveRecord::Base
end
#----app/models/work.rb - (VideoWork inherits from this)
class Work < ActiveRecord::Base
end
The Schema file:
#----db/schema.rb----
create_table "works", force: true do |t|
t.string "header"
t.string "description"
t.string "type"
t.string "folder_id"
end
create_table "collections", force: true do |t|
t.string "type"
t.datetime "created_at"
t.datetime "updated_at"
t.text "ancestry"
t.string "name"
t.string "tile_image_link"
end
My Question
I assume that since I have a folder_id column in the works table that I should be able to set up the belongs_to relationship properly, but it seems that Rails still wants me to have a video_collection_id column instead. I would prefer not use something specific like video_collection_id as a foreign key in the works table since I need to set up other relationships (e.g.: photo_collection has_many photo_works, etc).
What am I doing wrong here?
I don't really use has_many and belongs_to with different foreign keys than the standard, but according to the docs I would do this:
class VideoCollection < Collection
has_many :video_works, foreign_key: "folder_id"
end
class VideoWork < Work
belongs_to :folder, class_name: "VideoCollection", foreign_key: "folder_id"
end
Your Pg error says that the association is looking for 'video_collection_id' instead of 'folder_id'
Guides (chapter 4.3.2.5)

Table Relationship Reference Ruby on Rails

I am currently working on a web application where a contractor (electrician, roofer, plumber) etc can make a proposal online. Pictures, youtube videos of the project, and a text description will be provided to the contractor from the customer.
So far I am working on the pictures feature using carrierwave
This is the table of this model in my schema
create_table "project_pictures", force: true do |t|
t.string "name"
t.string "picture"
t.datetime "created_at"
t.datetime "updated_at"
end
Here are my two records in my rails console
ProjectPicture Load (0.4ms) SELECT "project_pictures".* FROM "project_pictures"
=> #<ActiveRecord::Relation [#<ProjectPicture id: 2, name: "Request for Siding Quote Customer A", picture: "siding.jpg", created_at: "2013-08-15 16:10:22", updated_at: "2013-08-15 16:47:02">, #<ProjectPicture id: 1, name: "Request for Siding Quote Customer A", picture: "sidingrequest.jpg", created_at: "2013-08-14 01:54:27", updated_at: "2013-08-15 16:47:39">]>
The thing is I am trying to link multiple pictures to one customer. Lets say the above two pictures belong to one customer and there are two rows because there are two pictures.
How do I reference that in the table, lets say I have one customer and thats me "judy"
both the record should reference judy's id?
and then eventually in the view, I can draw both pictures out using an image tag that belong to the customer id 1 - with name = "judy" or just customer id = 1?
If I am not making things clear please let me know, I am not that familiar with tables relationships and which relationship will help me the most.
create table :projects do |t|
t.references :clients
t.timestamps
end
create_table :pictures do |t|
t.string :name, null:false
t.string :location, null:false
t.references :projects, null:false
t.timestamps
end
... meanwhile.. back in the model layer
class Project < ActiveRecord::Model
has_many :pictures, :dependent => destroy
end
class Picture < ActiveRecord::Model
belongs_to :project
validates_presence_of :project
end
To get all Judy's pictures
Client.where("name like ?", "Judy").projects.first.pictures
I would have set it up like this
user has_many project_pictures
project_pictures belongs_to user
your project picture table should have a row for the user_id.

Ruby on Rails - Monthly top vote getter

I need some advice on a voting system in rails that recognizes the top vote getter on a monthly basis. I have a system that works but being new to rails, I'm sure there are more efficient methods available. Below is a simplified version of my current setup(controller code omitted):
class Charity < ActiveRecord::Base
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :charity
end
My schema is as follows:
ActiveRecord::Schema.define(:version => 20130310015627) do
create_table "charities", :force => true do |t|
t.string "name"
t.text "description"
t.date "last_win"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "votes", :force => true do |t|
t.integer "charity_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end
I'll be using the 'whenever' gem to run a cron job to determine the monthly winner and update the 'last_win' column of the charities table.
The following code is where I'm questioning my efficiency:
vote_counts = Vote.count(:group => "charity_id")
most_votes = vote_counts.values.max
winning_ids = vote_counts.map{|k,v| v == most_votes ? k :nil }.compact
charities = Charity.find(winning_ids)
charities.each {|charity| charity.update_attributes(:last_win => Date.today)}
I'm sure there are many ways to do this better and would appreciate some suggestions. If you have suggestions on better ways to set up the votes table / associations, that would be appreciated too.
Thanks in advance,
CRS
Something like this:
If there was only one winner, this would work I think
winner_id = Vote.group(:charity_id).order("count(*) desc").pluck(:charity_id).first
Charity.find(winner)id).update_attribute!(:last_win => Date.today)
You could modify it for ties:
most_votes = Vote.group(:charity_id).order("count(*) desc").count.first[1]
winners = Vote.group(:charity_id).having("count(*) = ?", most_votes).pluck(:charity_id)
Charity.where(:id => winners).update_all(:last_win => Date.today)
Make sure everything is indexed correctly in your database,
You can probably streamline it more, but the SQL is going to get more complicated.
The last two lines could be:
Charity.where(id:winning_ids).update_all(last_win:Date.today)
Which would translate into a single SQL update command, instead of issuing an update command for each winning charity.
The first part where you identify the winning charities looks okay, and since you're running it as a cron job you probably don't care if it takes a few minutes.
However, if you'd like to show the values in real time, you could add an after_create hook on Vote to update a counter for its owner charity (possibly in another table):
class Vote < ActiveRecord::Base
belongs_to :charity
after_create :increment_vote_count
CharityVote.where(year:Time.now.year, month:Time.now.month,
charity_id:self.charity_id).first_or_create.increment!(:counter)
end

Resources