has_and_belongs_to_many association confusion - ruby-on-rails

So I have an application that has a blog model,and a user model.
Now users can subscribe to many different blogs, and users can also create many of their own blogs.
What would the association look like?
Right now my models look like the following:
Blog.rb:
class Blog < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :posts
end
User.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:validatable
has_and_belongs_to_many :blogs
validates :email, uniqueness: true, presence: true
validates_presence_of :password, on: :create
end
the user table has a blog_id:integer field, and the blog has a user_id:integer field.
Is this right?
And how would the commands work? I.E:
u = User.last
b = u.blogs.build(title: "bla")
b.user (shows the owner of the blog)
b.users (shows the users that have subscribed to the blog)
Ultimately, I'd like to allow users to subscribe to other peoples blogs, and create their own.

You are going to want to add a third model 'Subscriptions'. Then you are going to want to use the 'has_many_through:' association. Please read this section of the rails guides for a detailed example. http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association.
After you create the associations you are going to want to do something along these lines:
1) make sure you nest the 'subscriptions' routes underneath the blog route properly.
resources :blogs, only: [] do
resources :subscriptions, only: [:create, :destroy]
2) Create a _subscription.html.erb partial in app/views/subscriptions
3) Render the partial in blogs#show
<%= render partial: 'subscriptions/_subscription, locals: {blog: #blog}
4) Add the ability to add (create) a subscription in the partial: (this is only to add subscription, you will want to also add ability to remove)
<%= link_to [blog, Subscription.new], class: 'btn btn-primary', method: :post do %>
<i class="glyphicon glyphicon-star"> </i> Subscribe
<% end %>
5) Add 'create method' to subscriptions_controller.rb
def create
#blog = Blog.find(params[:blog_id])
subscription = current_user.subscriptions.build(blog: #blog)
if subscription.save
# Add code
else
# Add code
end
end
This should be enough direction to get you to the finish line. Good luck :)

Related

Can't add Company (from separate model) to Devise User

First-time poster, many-time finder-of-answers on the site (thank you!). I'm using Rails 5.2.3, ruby-2.6.2 and Devise gem 4.6.2. I have not been able to get an answer to work, even though there are plenty somewhat related questions here, here, here and here.
When a new User signs up, I want them to select their Company from a dropdown list (already created) in the sign-up form. (Eventually, this will be an admin role, but that's beyond the scope of this question.)
I created a registrations controller and added code per a number of the previous posts. Update, I was not extending Devise as I should have as indicated here: Extending Devise Registration Controller. This is my new Registrations controller.
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
def new
#companies = Company.all
super
end
def create
#companies = Company.all
super
end
protected
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:company_id])
end
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:company_id])
end
end
And created new files in views/registrations with new.html.erb and edit.html.erb that I copied the exact code from the devise/registrations views.
I updated my routes.rb file to include:
devise_for :users, :controllers => { registrations: 'users/registrations', sessions: 'users/sessions' }
My User model is:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
belongs_to :company
accepts_nested_attributes_for :company
end
My Company model is:
class Company < ApplicationRecord
has_many :users
end
In the new user registration form, this works to provide the dropdown, but when I try to create the new user, it says: 1 error prohibited this user from being saved: Company must exist.
<%= f.collection_select :company, #companies, :id, :name, prompt: true %>
I thought this post would have the answer, but that appears to use Rails 3 and attr_accessible, which was deprecated in Rails 4.
I don't really understand what accept_nested_attributes_for :company does. The only thing in the Company model is the name.
Thank you in advance!
Welcome to StackOverflow.
In order to add more parameters to devise's sign up form, you'll need to sanitize the corresponding parameters using devise's sanitizer.
You should do that like this:
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:company_id])
end
end
You can find more information about parameter sanitizing and adding custom fields in this section of devise's readme
If you also want to add a select field including all the existing companies, you should add a collection select:
<%= f.collection_select :company_id, Company.all, :id, :name %>
Got it!
To extend the Devise controller, follow the help here: Extending Devise Registration Controller
The User models must also be updated to include the optional: true because here https://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
belongs_to :company, optional: true
accepts_nested_attributes_for :company
end

Rails - Devise - Add profile information to separate table

I am using Devise to build a registration/authentication system into my application.
Having looked at quite a few resources for adding information to the devise model (e.g. username, biography, avatar URL, et cetera..) [resources include Jaco Pretorius' website, this (badly formed) SO question, and this SO question.
That's all fine and well -- it works. But my problem is that it's saving to the User model, which, according to database normalizations (also referencing this SO question), it should in fact be saving to a sub-model of User which is connected via has_one and belongs_to.
Thus far, I have created a User model via Devise. I have also created a UserProfile model via the rails generate script.
user.rb (for reference)
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable
has_one :user_profile, dependent: :destroy
end
user_profile.rb
class UserProfile < ActiveRecord::Base
belongs_to :user
end
timestamp_create_user_profiles.rb
class CreateUserProfiles < ActiveRecord::Migration
def change
create_table :user_profiles do |t|
t.string :username, null: false
t.string :biography, default: ""
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
add_index :user_profiles, [:user_id, :username]
end
end
My question, now, is, how does one collect the information for both of these models and ensure, via the devise registration form, that it all ends up in the right places?
I've seen resources about creating state machines (AASM, and the answer to this SO question. I've also seen information about creating a wizard with WICKED, and an article on the same topic.
These all seem too complicated for my use-case. Is there some way to simply separate the inputs with devise and make sure the end up in the right place?
I think, instead of simply commenting on an answer that led me to the final answer, I'll archive the answer here in case someone in the future is trying to also find this answer:
I will be assuming that you have some sort of setup as I do above.
First step is you need to modify your User controller to accept_nested_attributes_for the profile reference as well as add a utility method to the model so when requested in code, the application can either retrieve the built profile model or build one.
The user model ends up looking like so:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable
has_one :user_profile, dependent: :destroy
accepts_nested_attributes_for :user_profile
def user_profile
super || build_user_profile
end
end
Secondly, you will need to modify your sign up/account_update form to be able to pass the attributes for this secondary model into the controller and eventually to be able to build the profile for the parent model.
You can do this by using f.fields_for.
Add something like this to your form:
<%= f.fields_for :user_profile do |user_profile_form| %>
<%= user_profile_form.text_field :attribute %>
<% end %>
An example of this in my specific case is:
<%= f.fields_for :user_profile do |user_profile_form| %>
<div class="form-group">
<%= user_profile_form.text_field :username, class: "form-control", placeholder: "Username" %>
</div>
<% end %>
Finally, you will need to tell Devise that it should accept this new hash of arguments and pass it to the model.
If you have created your own RegistrationsController and extended Devise's, it should look similar to this:
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit(:email, :password, user_profile_attributes: :username)
end
end
(Of course, make the proper changes for your specific use-case.)
If you have simply added the Devise sanitization methods to your application controller, it should look similar to this:
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) {|u|
u.permit(:email, :password, user_profile_attributes: :username)}
end
end
(Again, make the proper changes for your specific use-case.)
A small note on user_profile_attributes: :username:
Note this is a hash, of course. If you have more than one attribute you are passing in, say, as an account_update (hint hint), you will need to pass them like so user_profile_attributes: [:attribute_1, :attribute_2, :attribute_3].
Please check out the RailsCasts.com web-site.
There are a couple of interesting railscasts about nested model forms:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
http://railscasts.com/episodes/196-nested-model-form-revised
Also check out accepts_nested_attributes_for
Or check out this question:
Profile model for Devise users?
Also note that for Devise 4.2 the '.for' method for the devise_parameter_sanitizer is deprecated in favor of '.permit'
From the documentation:
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_in) do |user_params|
user_params.permit(:username, :email)
end
end

Devise Current User Can't Get Nested Data to Show in View

I'm trying to make an example app to learn about namespace/scopes/modules
Normally I would user current_user helper but I have Client::Addresses nested in behind and would like to grab say the user's city and just display it on their edit page (devise registration/edit screen)
<%= current_user.?? %>
Using the line below. I also added inverse_of as my understanding it'll reverse the relationship as well but no avail.
<%= #user.addresses.cacity %>
I think this is pretty close #user.id works but adding the rest error reads. Looks like I also dealt with strong params just not sure. I'm doing this to practice namespacing:scopes/modules:
undefined method `cacity' for #<ActiveRecord::Associations::CollectionProxy []>
It would be great to do something like.
<%= current_user.addresses.cacity %>
Here's some additional information with what I got so far, let me know if additional info is needed.
routes.rb
Rails.application.routes.draw do
namespace :client do
resources :subscriptions
end
# Security Devise Setup
devise_for :admins
devise_for :users
# Main Pages
root 'website/page#index'
# Client Sections
resources :users do
scope module: "client" do
root :to => 'dashboard#index'
resources :addresses
end
end
namespace :admin do
root :to => 'panel#index'
end
end
user.rb
class User < ActiveRecord::Base
include Gravtastic
gravtastic
# Devise Settings
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
# Model Relationships
has_many :addresses, :inverse_of => :user, class_name: 'Client::Address'
end
client/address.rb
class Client::Address < ActiveRecord::Base
belongs_to :user, :inverse_of => :addresses
end
#user.addresses
is a collection, and you send a (instance) method cacity to a collection, which, as errors states, do not respond to it.
#user.addresses.first.cacity would work.
You could limit the relation to has_one:
has_one :address #...
Which will allow you to use the following:
#user.address.cacity

Couldn't find User without an ID - Ruby 2.1.4 / Rails 4.1.6

I am facing an error while trying to link the :username in my User table and my Room table. I made a custom auth with devise and added :username.
I would like the username to be the link between the User table from devise and my Room table.
I am trying to build this app to recreate a kind of airbnb but mainly as an exercise as I started programming in ruby few months ago.
I get the error:
ActiveRecord::RecordNotFound in RoomsController#new
Couldn't find User without an ID
line #19 #room.username = User.find(params[:username])
Thank you very much for your help. I am stuck in here for hours now :-(
rooms_controller
def new
#room = Room.new
#room.username = User.find(params[:username]) #error seems to come from here
end
routes.rb
Rails.application.routes.draw do
devise_for :users
get "home/info"
root :to => "home#info"
resources :rooms
resources :users do
resources :rooms
end
room.rb
class Room < ActiveRecord::Base
mount_uploader :photo, PictureUploader
belongs_to :user
validates_presence_of :username, :location, :description, :capacity, :price_day, :photo
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates_presence_of :username
validates_uniqueness_of :username
has_many :rooms
end
It should be something like this
def new
#room = Room.new
#room.username = User.find_by_username(params[:username])
end
If you just use .find() it expects the id of the user. Also see http://guides.rubyonrails.org/active_record_querying.html#dynamic-finders
There is a logic error in that you are saving #room.username to the User Object. You should be setting #room.user = User.find_by(...) OR #room.user_id = User.find_by(...).id
Active record will automagically make a method for you that will be #room.user.username if you want to get the username.
Now, here are the ways to find a user.
#room = Room.new #Then either of the following
#room.user = User.find_by(username: params[:username]) #Returns only one value
#room.user = User.find_by_username(params[:username]) #Returns only one value
#room.user = User.where(username: params[:username]) #Returns all users which meet condition.
As already mentioned in the answers, User.find() takes an ID. One thing to know is that all methods that start with .find for active record return a single record even if many meet the condition.
If you are having any problems still, then show us your Database Schema, and we can help further.
I found a solution. The Room is created with the the right :username and nothing is seen by the user.
In my Rooms controller, I kept
def new
#room = Room.new end
And I added this line in the "def create" part :
def create
#room = Room.new(room_params)
#room.username = current_user.username
Thank you for your help, this help me to understand better the relations in rails.
Have a nice day !

Creating comments that belong to their respective microposts and users (in an app using Devise)

Right now, I have two models: User and Micropost.
The User model is working with Devise.
Example of the files involved:
user_controller.html.erb:
class PagesController < ApplicationController
def index
#user = current_user
#microposts = #user.microposts
end
end
index.html.erb:
<h2>Pages index</h2>
<p>email <%= #user.email %></p>
<p>microposts <%= render #microposts %></p>
microposts/_micropost.html.erb
<p><%= micropost.content %></p>
micropost.rb:
class Micropost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
end
user.rg:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :microposts
end
Now I want to create comments for the microposts:
Each comment should belong to its respective micropost and user (commenter). Not sure how to do this (is it a good situation to use polymorphic associations?).
An user should have many microposts and comments (not sure how to co this either).
I have no idea how to make it so that the comment is made the the user who is currently signed in (I think I have to do something with Devise's current_user).
Any suggestions to accomplish this? (Sorry, I'm a Rails beginner)
No, nothing you've said suggests that you need polymorphic associations. What you need is a comments model with a schema something like the following:
create_table :comments do |t|
t.text :comment, :null => false
t.references :microposts
t.references :user
t.timestamps
end
And then
# user.rb
has_many :microposts
has_many :comments
# microposts.rb
has_many :comments
You will probably want nested routes for your comments. So, in your routes.rb you'll have something like
#routes.rb
resources :microposts do
resources :comments
end
.. and in your comments controller, yes, you'll assign the value of comment.user something like the following...
# comments_controller.rb
def create
#comment = Comment.new(params[:comment])
#comment.user = current_user
#comment.save ....
end
You might want to look at the Beginning Rails 3 book, which would walk you through this.

Resources