I'm seeking brainstorming input for a Rails design issue I've run across.
I have simple Book reviews feature. There's a Book class, a User class, and a UserBook class (a.k.a., reviews and ratings).
class User < ActiveRecord::Base
has_many :user_books
end
# (book_id, user_id, review data...)
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
In the corresponding book controller for the "show" book action, I need to load the book data along with the set of book reviews. I also need to find out whether the current user (if there is one) has contributed to those reviews.
I'm currently running two queries, Book.where(...) and UserBook.where(...), and placing the results into two separate objects passed on to the view. Now, while I could run a third query to find whether the user is among those reviews (on UserBook), I'd prefer to pull that from the #reviews result set. But do I do that in the controller, or in the view?
Also worth noting is that in the view I have to draw Add vs Update review buttons accordingly, with their corresponding ajax URLs. So I'd prefer to know it before I start looping through a result set.
If I detect this in the controller though, I'll need three instance variables passed in, which I understand is considered distasteful in Rails land. Not sure how to avoid this.
Suggestions appreciated.
This smells like a case for has_many through, which is designed for cases where you want to access the data of a third table through an intermediate table (in this case, UserBook)
Great explanation of has_many :through here
Might look something like this:
class User < ActiveRecord::Base
has_many :user_books
has_many :users, through: :books
end
Then you can simply call
#user = User.find(x)
#user.user_books` # (perhaps aliased as `User.find(x).reviews`)
and
#user.books
to get a list of all books associated with the User.
This way, you can gain access to all of the information you need for a particular user with a single #user instance variable.
PS - You'll want to take a look at the concept of Eager loading, which will prevent you from making extraneous database calls while fetching all of this information.
Related
I have multiple models that in practice are created and deleted together.
Basically I have an Article model and an Authorship model. Authorships link the many to many relation between Users and Articles. When an Article is created, the corresponding Authorships are also created. Right now, this is being achieved by POSTing multiple times.
However, say only part of my request works. For instance, I'm on bad wifi and only the create article request makes it through. Then my data is in a malformed half created, half not state.
To solve this, I want to send all the data at once, then have Rails split up the data into the corresponding controllers. I've thought of a couple ways to do this. The first way is having controllers handle each request in turn, sort of chaining them together. This would require the controllers to call the next one in the chain. However, this seems sorta rigid because if I decide to compose the controllers in a different way, I'll have to actually modify the controller code itself.
The second way splits up the data first, then calls the controller actions with each bit of data. This way seems more clean to me, but it requires some logic either in the routing or in a layer independent of the controllers. I'm not really clear where this logic should go (another controller? Router? Middleware?)
Has anybody had experience with either method? Is there an even better way?
Thanks,
Nicholas
Typically you want to do stuff like this -- creating associated records on object creation -- all in the same transaction. I would definitely not consider breaking up the creation of an Authorship and Article if creating an Authorship is automatic on Article creation. You want a single request that takes in all needed parameters to create an Article and its associated Authorship, then you create both in the same transaction. One way would be to do something like this in the controller:
class Authorship
belongs_to :user
belongs_to :article
end
class Article
has_many :authorships
has_many :users, through: :authorships
end
class ArticlesController
def create
#article = Article.new({title: params[:title], stuff: [:stuff]...})
#article.authorships.build(article: #article, user_id: params[:user_id])
if #article.save
then do stuff...
end
end
end
This way when you hit #article.save, the processing of both the Article and the Authorship are part of the same transaction. So if something fails anywhere, then the whole thing fails, and you don't end up with stray/disparate/inconsistent data.
If you want to assign multiple authorships on the endpoint (i.e. you take in multiple user id params) then the last bit could become something like:
class ArticlesController
def create
#article = Article.new({title: params[:title], stuff: [:stuff]...})
params[:user_ids].each do |id|
#article.authorships.build(article: #article, user_id: id)
end
if #article.save
then do stuff...
end
end
end
You can also offload this kind of associated object creation into the model via a virtual attribute and a before_save or before_create callback, which would also be transactional. But the above idiom seems more typical.
I would handle this in the model with one request. If you have a has_many relationship between Article and Author, you may be able to use accept_nested_attributes_for on your Article model. Then you can pass Authorship attributes along with your Article attributes in one request.
I have not seen your code, but you can do something like this:
model/article.rb
class Article < ActiveRecord::Base
has_many :authors, through: :authorship # you may also need a class_name: param
accepts_nested_attributes_for: :authors
end
You can then pass Author attributes to the Article model and Rails will create/update the Authors as required.
Here is a good blog post on accepts_nested_attributes_for. You can read about it in the official Rails documentation.
I would recommend taking advantage of nested attributes and the association methods Rails gives you to handle of this with one web request inside one controller action.
I'm very new to web-development (I feel like all my posts lately have started that way) and becoming, with time, less new to rails. I'm at a point where I can do a sizeable amount of the things required for my job but there's one nagging problem I keep running into:
How do I decide if which action I should use for a given task? index, show, new, edit, create, update or destroy?
destroy is pretty obvious and I can loosely divide the rest into two buckets with index/show in one and new/edit/create in the other. But how do I decide which one to use or if I should build one of my own?
Some general guidelines or links to further reading would be very beneficial for me.
Here is how I think of these 7 RESTful Controller actions. Take, for example, a Person resource. The corresponding PeopleController would contain the following actions:
index: List a set of people (maybe with some optional conditions).
show: Load a single, previously created Person with the intention of viewing. The corresponding View is usually "read-only."
new: Setup or build an new instance of a Person. It hasn't been saved yet, just setup. The corresponding View is usually some type of form where the user can enter attribute values for this new Person. When this form is submitted, Rails sends it to the "create" action.
create: Save the Person that was setup using the "new" action.
edit: Retrieve a previously created Person with the intention of changing its attributes. The changes have not been made or submitted yet. The corresponding View is usually a form that Rails will submit to the "update" action.
update: Save the changes made when editing a previously created Person.
destroy: Well, as you guessed, destroy or delete a previously created Person.
Of course there is some debate as to whether these 7 actions are sufficient for all controllers, but in my experience they tend to do the job with few exceptions. Adding other actions is usually a sign of needing an additional type of resource.
For example, say you have an HR application full of Person resources you are just dying to hire. In order to accomplish this, you may be tempted to create a "hire" action (i.e., /people/456/hire). However, a more RESTful approach would instead consider this the "creation" of an Employment resource. Something like the following:
class Person < ActiveRecord::Base
has_many :employments
has_many :employers, :class_name => 'Company', :through => :employments, :source => :company
end
class Employement < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :employments
has_many :employees, :class_name => 'Person', :through => :employments, :source => :person
end
The EmploymentsController's create action would then be used.
Okay, this is getting long. Don't be afraid to setup a lot of different resources (and you probably won't use all 7 Controller actions for each of these). It pays off in the long run and helps you stick to these 7 basic RESTful actions.
You can name your actions whatever you want. Generally, by Rails convention, index is the default one, show shows one item, list shows many, new and edit start editing a new or old item, and create and update will save them, respectively. destroy will kill an item, as you guessed. But all these are just conventions: you can name your action yellowtail if that's what you want to do.
You have a set of related models created through a scaffold e.g. a house, which has many rooms, which each have many windows, which each has a selection of locks.
These resources are already full of data i.e. someone has entered all the information, such as: a room called 'kitchen' has various windows associated with it and these windows each have five different locks associated with them.
Someone comes along and says:
Can you create a form that lets someone create a new project where they can select the different rooms, windows and then specify the locks that they would like for that project? (these are already in the system, nothing new to add, just the associations to a new project)
This sounds like a nested form but I have wasted a lot of time trying to solve this - there are many levels of nesting, which make this tricky. Any suggestions?
session based solution
With such deeply nested models select box on the front end wouldn't be enough...
Assuming this, you may want to create a current_house who's id live in a session (just like current_user works).
Once you have your current_house add different items by navigating to your list of items view and clicking on the add_to link :
# house_controller.rb
def add_to
current_house.polymorphic_items << Kitchen.find(params[:id])
redirect_to :back
end
But there are many approaches to this session based solution which sort of implements a cart/order system. You may want to add a current_item to add stuff in each leaf of your tree aka room of your house.
E.G after clicking on the kitchen you just added :
before_filter :set_current_item
def add_to
current_item.windows << Window.find(id)
end
current_item beeing polymorphic : a living room, a bathroom etc.
But how you implement that precisely depends on your Domain Model....
As a rule of thumb regarding nested forms I'd follow rails guidance for routes : don't go deeper than one level or you'll end up in a mess.
Yes this is a nested form. Railscasts nested forms is a great place to start.
If everything is already in the system you probably just want select boxes so they can select what they want. Also check out the .build method. If you have multiple levels of nesting you can also manually set the association by passing in the foreign key yourself.
I think you can model this with a single level of nested attributes, given the models below (based on Windows/Locks pre-existing and a room just needing to mix and match them into a set of windows with given locks):
class House < ActiveRecord::Base
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :house
has_many :window_configs
end
class WindowConfig < ActiveRecord::Base
belongs_to :room
belongs_to :window
belongs_to :lock
end
class Lock < ActiveRecord::Base
has_many :window_configs
end
class Window < ActiveRecord::Base
has_many :window_configs
end
... based on that model setup, you could have a single house form that you dynamically add child 'room' definitions to that each have a name and a collection of window_configs which have two select boxes for each one (choose a window definition and then a lock definition). Because you're dynamically adding multiple rooms with multiple windows, you'd need some JS to populate new form elements, but it could all live in a single nested form.
form_for :house do |form|
# Dynamically add a Room form for each room you want with js
form.fields_for :room do |room_attributes|
room_attributes.text_field :name
# Dynamically add window_config forms on Room w/ JS
room_attributes.fields_for :window_config do |window_attributes|
window_attributes.select :window_id, Window.all
window_attributes.select :lock_id, Lock.all
I have run into an issue regarding how to identify which user owns particular resources so that I can prevent inappropriate access to them.
I have the following nested associations:
User has many
Profiles has one
SamplePage has many
Subjects
Once they become nested this deep it's become very unwieldy to access the user object via the associations and then compare that to current user e.g.:
#subject.sample_page.profile.user == current_user
I've read that a better way of restricting access is to scope the retrieval of a model to the current user. e.g:
#profile = current_user.profiles.find(params[:id])
That makes a lot of sense to me but how would I do a similar thing to get a Subject back? I've not found any examples that used nested associations.
not sure to understand what you want to do, and not sure i can help you since i'm a huge noob, but i would try something like this (assumed that current_user returns a User):
class Profile < ActiveRecord::Base
has_many :subjects, :through => :sample_pages
end
and in your controller:
#subject = current_user.profiles.subjects.find(params[:id])
more handy this way:
class User < ActiveRecord::Base
def subjects
profiles.subjects
end
end
#subject = current_user.subjects.find(params[:id])
all of this should be lazy loaded, as explained here : http://asciicasts.com/episodes/202-active-record-queries-in-rails-3
however, if it is a frequent operation, you may want to redesign things a bit, as long chains of associations mean heavy queries (lots of joins).
I am trying to make an application wherein Users have many Items, and each Item they have through Possession is an entity in its own right. The idea behind this is if I have a MacBook item, eg, and a user adds it to their inventory, they may apply attributes (photos, comments, tags, etc) to it without directly affecting them Item itself, only their Possession.
The Item will in turn aggregate attributes from its corresponding Possessions (if you were to go to /item/MacBook, rather than /user/101/possession/5). I have the following models setup (ignoring attributes like photos for now).
class User
has_many :possessions
has_many :items, :through => :possessions
end
class Item
has_many :possessions
has_many :users, :through => possessions
end
class Possession
belongs_to :user
belongs_to :item
end
My first question is, am I doing this right at all. Is has_many :through the right tool here?
If so, how would I deal with class inheritance here? I might not be stating this right, but what I mean is, if I were to do something like
#possession = Possession.find(params[:id])
#photos = #possession.photos.all
and there were no photos available, how could it fall back to the corresponding Item and search for photos belonging to it?
Your initial data structure seems appropriate.
As for the second part, with the "fall back" to a corresponding item, I don't think there would be a direct Active Record way of doing this. This behavior seems pretty specific, and may be confusing to future developers working on your app unless you have a clear method for this.
You could create a method inside Possession like:
def photos_with_fallback
return self.photos if self.photos.size > 0
self.item.photos
end
There is a huge consequence to doing this. If you have a method like this, you won't be able to do any write activities down the wrode like #photos.build or #photos.create because you won't know where you're putting them. They could be linked to the Item or the Posession.
I think you're better of pushing the conditional logic out to your controller and checking for photos on the Posession first and then on the Item.
#In the controller
#photos = #posession.photos
#photos = #posession.item.photos if #photos.size == 0
This will be more clear when you go to maintain your code later, and it will allow you to make other decisions down the road.