I have a User model, and a Goal model. The Goal model has a name, and a type column, which has the following three records right now.
name | type
------------------------
Read Novels | Reading
Read Newspaper | Reading
Write Reviews | Writing
Now I want to provide each user, an interface to fill in the number of hours they spend on this activity every month (hours_per_month), and the number of different days they do it (days_per_month).
Ideally, I'd want to work with the data like
current_user = User.find(params[:id])
current_user.goals.each do |goal|
puts goal.name
puts goal.type
puts goal.hours_per_month
puts goal.days_per_month
end
How should I create this new table/model, that joins the Goals to User, and have access to the static Goal attributes, and the user filled values, like the code sample above?
You could create an Activity model and give it a table definition like:
activities:
user_id
goal_id
hours_per_month
days_per_month
Then add to:
User
has_many :activities
has_many :goals, :through => :activity
Goal
has_many :activities
has_many :users, :through => :activity
Activity
belongs_to :user
belongs_to :goal
You can use a generator to do most of the work iirc.
Table goals_users
column: user_id
column: goal_id
column: hours_worked
Models:
User:
has_and_belongs_to_many :goals
Goals:
has_and_belongs_to_many :users
def hours_per_month
#calculate
end
def hours_per_day
#calculate
end
Obviously set up the additional columns outside the join however you like.
Additional
In any model using AR joins you'll need to access the intermediary record directly.
Related
I have been racking my brain all day and can't get this to work. I am very new to ruby and rails so my apologies for any silly errors.
My problem is I am joining 3 tables together to get a #students object. This works but if I call for example #student.name then 'name' doesn't exist.
Below is my code:
Controller
note I have tried using .includes and .join and the same problem happens.
class MyprojectController < ApplicationController
def show
#project = Project.find(params[:id])
#dateformat = '%b %e - %H:%M'
#user = Student.includes("INNER JOIN researchers ON students.researcher_id = researchers.id
INNER JOIN users ON researchers.user_id = users.id").where('user_id = ?', current_user.id)
end
User Model
class User < ApplicationRecord
include EpiCas::DeviseHelper
has_many :event_registrations
has_many :events, through: :event_registrations
belongs_to :project
has_many :researchers
#has_many :students, :through => :researchers
#has_many :supervisors, :through => :researchers
# def self.authenticate(username)
# where(username: username).first
# end
end
Researcher Model
class Researcher < ApplicationRecord
#belongs_to :project
belongs_to :user
has_many :supervisor
has_many :students
end
Student Model
class Student < ApplicationRecord
#Must have the following
validates :name, :email, :surname, :email, :supervisor, :registration_number, presence: true
#ensures unique email addresses
validates :email, uniqueness: true
#assosiations
belongs_to :researcher
end
So every student has a researcher_id and every researcher has a user_id. So the joins should go student->researcher->user and then I want to be able to use all the attributes from all tables in an #user object.
I tried using Student.join(:researcher, :user) but that tried to do a join from the Student table to the researchers table and then tried to join the user table by using a user_id from the student table (but of the user_id is in the researcher table). So i have just done the query myself.
All the data seems to be there but as 'raw attributes'.
Any help would be greatly appreciated!
-James
Rather than try and join things into one return (like you would in sql) use includes so that you can access all your data in fewer queries but you still have access to your data in objects. The point of using an ORM like ActiveRecord is to be able to access your data using objects. The downside of using an ORM is that sometimes it's not as efficient at getting you the exact data you want, because the data is pushing into objects. Using includes provides a sort of middle ground where you can access the data you require in objects and you don't necessarily have to run queries for each association.
Try something like (depending on how you're getting your user id -- I'm assuming from project):
#user = User.includes(researcher: :student).find(project.user_id)
And then you can access things through the normal rails associations:
researcher = #user.researcher
student = researcher.student
I hope that helps and best of luck!
What I have (pseudo code):
model Document
column :title
HABTM :users
model User
column :name
HABTM :documents
Document has users (being approvers for document, either approve or not), and in this context join table should have extra column approved for each user.
jointable
user_id, document_id, approved
1 , 1 , true
2 , 1 , false
What I want is basically:
contract.approvers => returns users but with possibility to =>
contract.approvers.first.approve(:true) => and it updates JOINtable approve column to TRUE.
Answer right for this situation is optional, will appreciate advises on schema too (or maybe i should use other type of relation?).
HABTM has been deprecated a while ago, I think it is just a reference to has many through now.
Either way
join table name = DocumentReview
Document
has_many :document_reviews
has_many :users, through: :document_reviews
User
has_many :document_reviews
has_many :documents, through: :document_reviews
I don't understand how contract fits into this, i think you are saying that a document is a contract?
I would put the approve method in a separate class
class DocumentSignOff
def initialize(user, document)
#document_review = DocumentReview.find_by(user: user,document: document)
end
def approve!
#maybe more logic and such
#document_review.udpate(approved: true)
end
end
end
I have the following ActiveRecord models, Widget, Merchant, Store, StoresWidget (many-to-many association)
class Merchant < ActiveRecord::Base
has_many :stores
has_many :widgets
end
class Widget < ActiveRecord::Base
has_many :stores_widgets
has_many :stores, :through => :stores_widgets
belongs_to :merchant
end
class Store < ActiveRecord::Base
has_many :stores_widgets
has_many :widgets, :through => :stores_widgets
belongs_to :merchant
end
class StoresWidget < ActiveRecord::Base
belongs_to :widget
belongs_to :store
end
So the corresponding tables are widgets, merchants, stores and stores_widgets, where widgets and stores each has a id column and stores_widgets has two columns store_id and widget_id. A Widget can be available in 1 or more stores and a store can have many widgets available. Some widgets are available in all stores some are only available in a sub-set of stores. If a Widget is restricted to a subset of stores, the column restricted is true
When a new store is added to a merchant I want to update all the unrestricted widgets to be associated with that store. Ideally I would like to have code like this in my StoresController#create
class StoresController < ApplicationController
def create
# build new store...
Store.transaction do
store.save!
Widget.update_all_unrestricted_widgets_with_store(store)
end
render :show
end
end
Where update_all_unrestricted_widgets_with_store ends up executing SQL like:
INSERT INTO stores_widgets (store_id, widget_id)
(SELECT #{store.id}, widgets.id
FROM widgets
WHERE widgets.merchant_id = #{store.merchant_id}
AND widgets.restricted = FALSE)
So if a merchant has 100000 widgets that are unrestricted a 100000 new stores_widgets rows are created in one INSERT rather than 100000 distinct INSERTS.
Preferably I would like to get ActiveRecord to build an insert like this. Is that possible? If not would I be able to do this with ARel? I want to avoid executing a SQL string to achieve this if possible, so that I can maintain a level if separation between the code and the database SQL syntax.
You can use activerecord-import implements AR#import
activerecord-import is a library for bulk inserting data using ActiveRecord.
see how it works:
books = []
10.times do |i|
books << Book.new(:name => "book #{i}")
end
Book.import books
Reference - https://github.com/zdennis/activerecord-import
I am new in RoR and I am trying to write a query on a join table that retrieve all the data I need
class User < ActiveRecord::Base
has_many :forms, :through => :user_forms
end
class Form < ActiveRecord::Base
has_many :users, :through => :user_forms
end
In my controller I can successfully retrieve all the forms of a user like this :
User.find(params[:u]).forms
Which gives me all the Form objects
But, I would like to add a new column in my join table (user_forms) that tells the status of the form (close, already filled, etc).
Is it possible to modify my query so that it can also retrieve columns from the user_forms table ?
it is possible. Once you've added the status column to user_forms, try the following
>> user = User.first
>> closed_forms = user.forms.where(user_forms: { status: 'closed' })
Take note that you don't need to add a joins because that's taken care of when you called user.forms.
UPDATE: to add an attribute from the user_forms table to the forms, try the following
>> closed_forms = user.forms.select('forms.*, user_forms.status as status')
>> closed_forms.first.status # should return the status of the form that is in the user_forms table
It is possible to do this using find_by_sql and literal sql. I do not know of a way to properly chain together rails query methods to create the same query, however.
But here's a modified example that I put together for a friend previously:
#user = User.find(params[:u])
#forms = #user.forms.find_by_sql("SELECT forms.*, user_forms.status as status FROM forms INNER JOIN user_forms ON forms.id = user_forms.form_id WHERE (user_forms.user_id = #{#user.id});")
And then you'll be able to do
#forms.first.status
and it'll act like status is just an attribute of the Form model.
First, I think you made a mistake.
When you have 2 models having has_many relations, you should set an has_and_belongs_to_many relation.
In most cases, 2 models are joined by
has_many - belongs_to
has_one - belongs_to
has_and_belongs_to_many - has_and_belongs_to_many
has_and_belongs_to_many is one of the solutions. But, if you choose it, you must create a join table named forms_users. Choose an has_and_belongs_to_many implies you can not set a status on the join table.
For it, you have to add a join table, with a form_id, a user_id and a status.
class User < ActiveRecord::Base
has_many :user_forms
has_many :forms, :through => :user_forms
end
class UserForm < ActiveRecord::Base
belongs_to :user
belongs_to :form
end
class Form < ActiveRecord::Base
has_many :user_forms
has_many :users, :through => :user_forms
end
Then, you can get
User.find(params[:u]).user_forms
Or
UserForm.find(:all,
:conditions => ["user_forms.user_id = ? AND user_forms.status = ?",
params[:u],
'close'
)
)
Given that status is really a property of Form, you probably want to add the status to the Forms table rather than the join table.
Then when you retrieve forms using your query, they will already have the status information retrieved with them i.e.
User.find(params[:u]).forms.each{ |form| puts form.status }
Additionally, if you wanted to find all the forms for a given user with a particular status, you can use queries like:
User.find(params[:u]).forms.where(status: 'closed')
I have a weird design question. I have a model called Article, which has a bunch of attributes. I also have an article search which does something like this:
Article.project_active.pending.search(params)
where search builds a query based on certain params. I'd like to be able to limit results based on a user, that is, to have some articles have only a subset of users which can see them.
For instance, I have an article A that I assign to writers 1,2,3,4. I want them to be able to see A, but if User 5 searches, I don't want that user to see. Also, I'd like to be able to assign some articles to ALL users.
Not sure if that was clear, but I'm looking for the best way to do this. Should I just store a serialized array with a list of user_id's and have -1 in there if it's available to All?
Thanks!
I would create a join table between Users and Articles called view_permissions to indicate that a user has permission to view a specific article.
class ViewPermission
belongs_to :article
belongs_to :user
end
class User
has_many :view_permissions
end
class Article
has_many :view_permissions
end
For example, if you wanted User 1 to be able to view Article 3 you would do the following:
ViewPermission.create(:user_id => 1, :article_id => 3)
You could then scope your articles based on the view permissions and a user:
class Article
scope :viewable_by, lambda{ |user| joins(:view_permissions).where('view_permissions.user_id = ?', user.id) }
end
To search for articles viewable by a specific user, say with id 1, you could do this:
Article.viewable_by(User.find(1)).project_active.pending.search(params)
Finally, if you want to assign an article to all users, you should add an viewable_by_all boolean attribute to articles table that when set to true allows an article to be viewable by all users. Then modify your scope to take that into account:
class Article
scope :viewable_by, lambda{ |user|
joins('LEFT JOIN view_permissions on view_permissions.article_id = articles.id')
.where('articles.viewable_by_all = true OR view_permissions.user_id = ?', user.id)
.group('articles.id')
}
end
If an Article can be assigned to multiple Writers and a Writer can be assigned to multiple Articles, I would create an Assignment model:
class Assignment < AR::Base
belongs_to :writer
belongs_to :article
end
Then you can use has_many :through:
class Article < AR::Base
has_many :assignments
has_many :writers, :through => :assignments
end
class Writer < AR::Base
has_many :assignments
has_many :articles, :through => :assignments
end