I think this is a pretty simple question, but I have an app that has User, Product, and Review models.
When a user creates a new review for a product, I currently have this in my create action for Review:
#review = #product.reviews.new(params[:review])
This works fine, but the review is associated with the product only and I also want to associate it with the creating user. However, if I do something like this:
#review = #product.reviews.new(params[:review])
#review = current_user.reviews.new(params[:review])
Would it create two instances of the review that are separate records? How could I do multiple associations like this with only one #review variable?
Just to note, I am not using polymorphic associations because as I understand it, I don't need to allow #review to belong to EITHER a product or user, as I always want to associate it with both.
Thus, in my Review model I have:
t.integer :product_id
t.integer :user_id
and my models look this:
Product:
has_many :reviews
User:
has_many :reviews
Review:
belongs_to :user
belongs_to :product
First, is this setup correct? Am I right in not using polymorphic associations? If so, how can I write my controller code to do what I want?
See Rails Associations
You probably want something like this:
#review = #product.reviews.new(params[:review])
current_user.reviews << #review
Related
To illustrate my question, i'll use the following association as listed on ruby guides for polymorphic associations:
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
Now in our :pictures table, we will have 2 different columns, namely imageable_id and imageable_type.
The first thing that I am unclear of is, what exactly goes into imageable_type ? Is that something that the "rails magic" would automatically fill up when imageable_id is declared?
Moving on to my next point (which will probably indirectly answer my uncertainty above), how do I assign either a Product or Employee to my Picture.new form?
#create new picture form
#assume pre_created_array holds the :id for both Employee & Product
<%= form_for #picture do |f| %>
#relevant fields ontop
#choosing parent field is the one im unsure of
#this is what i assumed might be correct(?)
<%= f.select :imageable_id, options_for_select(pre_created_array) %>
<% end %>
Now does this form actually work? Is the building of the association something that has to be handled in the controller action instead? I am actually not very sure because usually in a regular association the parent can be declared before .save, such as doing #post.employee = #find an employee. So are we suppose to read the :imageable_id?
#pictures controller
def create
#picture = Picture.new(picture_params)
#manipulate :imageable_id here???
if #picture.save
#blahblah other code
end
So i am actually quite unsure about this, whether it is supposed to be the form or controller handling the building of association. Which is why i'm bringing up this 2 "uncertainties".
A polymorphic association is cool because it allows a single table to belong_to multiple, as you know. So given a picture, we don't have to know whether it belongs to an employee or a product, we can just call picture.imageable and get the parent.
So if we don't have to know where the parent is, that must means Rails has to know. How does it know? imageable_type! imageable_type is the name of the class which it belongs to. In this case, either 'Employee' or 'Product'. That way, given the imageable_id, it knows what table in which to search.
image.imageable actually calls image.imageable_type.constantize.find(image.imageable_id)
Rails will do this "magic" for you if you simply assign objects instead of IDs. image.update(imageable: Product.first) will assign both for you.
So in your form, you should be able work with a collection of objects and let Rails do the rest for you.
I'm still newbie in Rails, but got confused with the initialization of a HABTM association. Reading its documentation, it says
When initializing a new has_one or belongs_to association you must use the build_ prefix to build the association, rather than the association.build method that would be used for has_many or has_and_belongs_to_many associations.
So, basically, let's suppose we have two models:
class User < ApplicationRecord
has_and_belongs_to_many :organizations
end
class Organization < ApplicationRecord
has_and_belongs_to_many :users
end
Inside organization_controller, since I'm using Devise, my create method should have something like this:
#organization = current_user.organizations.build(organization_params)
#organization.save
However, it is not working. With byebug, I checked that for the current_user.organizations, the new organization was there, but, if I call #organization.users, there's an empty array. Looks like it's required to run current_user.save as well, is it correct? I was able to associate both models with this code:
#organization = Organization.new(organization_params)
#organization.users << current_user
#organization.save
You should highly consider using has_many, :through as that's the preferred way to do these kinds of relationships now in Rails.
having said that if you want to use has_and_belongs_to_many associations yes its get stored in join table on main object save.
I’ve setup a has_many :through association between a User and Organisation model, using a Membership model as the join.
class Organisation < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class User < ActiveRecord::Base
. . .
has_many :memberships
has_many :organisations, :through => memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :organisation
end
When a User creates an organisation, I want a membership to automatically be created linking the user to that organisation.
Where is the best place to attack this?
Options I’ve been investigating:
Use an after_create callback on the organisation
Move this process into a separate Ruby class.
In the organisations Controller, create action.
?
How would you recommend I go about it?
Is there somewhere in the Rails Guides where it outlines best practices for this kind of thing?
Rails 4.2.5.
#config/routes.rb
resources :organizations #-> url.com/organizations/new
#app/controllers/organizations_controller.rb
class OrganizationsController < ApplicationController
before_action :authenticate_user!
def new
#organization = current_user.organizations.new
end
def create
#organization = current_user.organizations.new organization_params
#organization.save
end
private
def organization_params
params.require(:organization).permit(:x, :y, :z) #-> membership automatically created
end
end
The above will automatically create the associated membership; assuming you're using Devise & have access to the current_user method.
--
The best practice is the most succinct; there is no way you're "meant" to do it.
One of the biggest fallacies I see in Rails is people trying to find the most acceptable way to do something (as if there's a rulebook). The best thing you can do is get it working then refactor the code.
As you progress through your app, you'll find that certain patterns can be changed, some removed and many combined. The more "DRY" you make your code, the better it is (as a rule).
My idea is way 3. Normaly, when set up many - many association in models, we should do creating temp table record auto through controller.
For example in controller you can write:
#organisation = current_user.organisations.build organisation_params
if #organisation.save
....
So that if #organisation is save then after that memberships record auto generate.
You can see this tutorial to see that:
http://blog.teamtreehouse.com/what-is-a-has_many-through-association-in-ruby-on-rails-treehouse-quick-tip
I think you should just be able to do something like:
org = Organisation.new
org.otherstuff = "populate other stuff"
org.users = [user_who_created]
org.save
After that the two should be related...? If you wanted to encapsulate this behavior you could do something like have a class method on Organization like create_org_for_user(name, user) and then do this logic in there, or you could do it in the controller action that handles the creation.
If there isn't any additional logic other than creating an organization and membership, I would just do #3. However, if you are planning on adding more logic for creating a new organization in the future, I would create a new service (#2).
Where is the best place to attack this?
I would like to say you should write this in OrganisationsController's create action to make a DRY on update action as well(use strong parameter) .Because form attribute that you are getting is from outer worlds and it is best to use permit method on required params using Strong Parametre concept.
def create
#organisation = Organisation.new(organisation_params)
...
end
def organisation_params
# here you could write all the params which you want to permit from outer worlds
end
Nore more info about Strong Parameters
Rails 3.1
I'll simplify my application to get at my question.
I have two tables: Items and Reviews
Items has a column "Average_Rating" and Reviews has a column "Item_ID" and "Rating"
For each item, I'd like to store the average rating for its corresponding reviews. Although I can't figure it out, I feel like what I want to do is add something to the create and update methods in the Reviews Controller along the lines of:
#review = Review.find(params[:id])
#item = Item.find(#review.Item_ID)
reviews_to_sum = Reviews.find_by_item_id(#item.id)
#item.Average_Rating = reviews_to_sum.Rating.sum/reviews_to_sum.count
I recognize, however, that the above probably isn't close to correct... I'm a beginner and I'm stuck.
And I do want to store the Average_Rating in the database, as opposed to calculating it when I need it, for a variety of reasons.
class Item < ActiveRecord::Base
has_many :reviews
end
class Review < ActiveRecord::Base
belongs_to :item
after_save do
item.update_attributes average_rating: item.reviews.average(:rating)
end
end
Hi (huge Rails newbie here), I have the following models:
class Shop < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :title, :user_id, :message => "is already being used"
end
and
class User < ActiveRecord::Base
has_one :shop, :dependent => :destroy
end
When I'm about to create a new shop, I get the following error:
private method `create' called for nil:NilClass
This is my controller:
#user = current_user
#shop = #user.shop.create(params[:shop])
I've tried different variations by reading guides and tutorials here and there, but I'm more confused than before and can't get it to work. Any help would be greatly appreciated.
A more concise way to do this is with:
#user.create_shop(params[:shop])
See methods added by has_one in the Ruby on Rails guides.
First of all, here is how to do what you want:
#user = current_user
#shop = Shop.create(params[:shop])
#user.shop = #shop
Now here's why your version did not work:
You probably thought that this might work because if User had a has_many relation to Shop, #user.shops.create(params[:shop]) would work. However there is a big difference between has_many relations and has_one relations:
With a has_many relation, shops returns an ActiveRecord collection object, which has methods that you can use to add and remove shops to/from a user. One of those methods is create, which creates a new shop and adds it to the user.
With a has_one relation, you don't get back such a collection object, but simply the Shop object that belongs to the user - or nil if the user doesn't have a shop yet. Since neither Shop objects nor nil have a create method, you can't use create this way with has_one relations.
Two more ways if you want save instead of create:
shop = #user.build_shop
shop.save
shop = Show.new
shop.user = #user
shop.save
Just to add to above answers -
#user.create_shop(params[:shop])
Above syntax creates new record but it subsequently deletes similar existing record.
Alternatively, if you do not want to trigger delete callback
Shop.create(user_id: user.id, title: 'Some unique title')
This thread might be helpful. Click here