Associating Two Models in Rails (user and profile) - ruby-on-rails

I'm new to Rails. I'm building an app that has a user model and a profile model.
I want to associate these models such that:
- After the user creates an account, he is automatically sent to the "create profile" page, and the profile he creates is connected to only that particular user.
- Only the user who owns the profile can edit it.
I generated the user model using nifty_generators. When the user hits submit for the account creation, I redirect him to the "new profile" view to create a profile. I did this by editing the redirect path in the user controller. The user controller looks like this:
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
flash[:notice] = "Thank you for signing up! You are now logged in."
redirect_to new_profile_path
else
render :action => 'new'
end
end
This was working, but the problem was that the app didn't seem to recognize that the profile was connected to that particular user. I was able to create profiles, but there didn't seem to be a relationship between the profile and the user.
My Profile model lists: belongs_to :user
My User model lists: has _one :profile
My routes.rb file lists the following:
map.resources :users, :has_one => :profile
map.resources :profiles
I have a user_id foreign key in the profiles table. My schema looks like this:
create_table "profiles", :force => true do |t|
t.integer "user_id"
t.string "name"
t.string "address1"
t.string "address2"
t.string "city"
t.string "state"
t.string "zip"
t.string "phone"
t.string "email"
t.string "website"
t.text "description"
t.string "category"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
t.string "username"
t.string "email"
t.string "password_hash"
t.string "password_salt"
t.datetime "created_at"
t.datetime "updated_at"
end
To try to connect the profile to the user, I updated the profiles_controller.rb file with the following, which I basically extrapolated from the Rails Getting Started Guide. My thinking is that in my app, profiles connect to users in the same way that in the Rails Getting Started app, comments connect to posts. Here's the relevant parts of my profiles controller. I can provide the whole thing if it will help:
def new
#user = User.find(params[:user_id])
#profile = #user.profile.build
end
def create
#user = User.find(params[:user_id])
#profile = #user.profile.build(params[:profile])
if #profile.save
flash[:notice] = 'Profile was successfully created.'
redirect_to(#profile)
else
flash[:notice] = 'Error. Something went wrong.'
render :action => "new"
end
end
After making these updates to the profiles controller, now when I submit on the account creation screen, I'm redirected to an error page that says:
ActiveRecord::RecordNotFound in ProfilesController#new
Couldn't find User without an ID
This all seems like a pretty straight-forward Rails use case, but I'm not sure which pieces are wrong. Thanks in advance for your help!

When a user is created, the client is redirected to the new action in the ProfileController without an id. You need to explictly pass the user's id in the parameters. It's an idiom to pass the reference to the entire object, not just the id.
# app/controllers/users_controller.rb
def create
# ...
redirect_to new_user_profile_path(:user_id => #user)
# ...
end
Notice that I'm using new_user_profile_path and not new_profile_path. The former is the nested resource that you defined in routes.rb. You should delete map.resources :profiles because a profile cannot exist without a user.

For usability sake the profile should be part of the user model, or you should create your account and at least the base of your profile all in one go, as a user I i registered and then had to fill in another form I think I would be pretty annoyed.
The upside of this is that either way you can do this with one form. I'd look at the railscasts on complex forms and if you can handle a very modest financial outlay then the pragmatic programmers series on Mastering Rails Forms is a total winner.

Because user_id doesn't exist or is not created in the profile database. When creating user

Related

How to catch ids getting created and compare their attributes in rails active record

I have a Question model and a PossibleAnswer model the tables are as follows.
possible_answers
create_table "possible_answers", force: :cascade do |t|
t.integer "question_id"
t.string "title"
end
questions
create_table "questions", force: :cascade do |t|
t.string "title"
t.string "right_answer"
end
The association is (you could have guessed already but anyway)
Question.rb
class Question < ActiveRecord::Base
has_many :possible_answers
end
PossibleAnswer.rb
class PossibleAnswer < ActiveRecord::Base
belongs_to :question
end
Now I have a form where all the questions are posted with their respective choices. I have a controller to submit the answers by selecting the choices.
class RepliesController < ApplicationController
def create
#quiz = Quiz.find(params[:quiz_id])
#reply = #quiz.replies.build reply_params
if #reply.save
redirect_to #quiz, notice: "Thank you for taking the quiz"
end
end
def reply_params
params.require(:reply).permit({ answers_attributes: [ :value, :question_id, :possible_answer_id ] })
end
end
Now this create method is working fine. Once the user submits the quiz, the following data is created.
{"answers_attributes"=>{"0"=>{"possible_answer_id"=>"10", "question_id"=>"6"}}, "commit"=>"Finish quiz", "quiz_id"=>"1"}
Here the possible_answer_id is the option which user selected and the possible answer belongs to the question_id.
My question is, how can I compare the title from this possible_answer_id to the right_answer of the question_id.
I tried using find params and passing the id but didnt work.
Can anybody please help me here. I have a come a long way building this app and stuck here now.
Have you thought about adding a :correct, :boolean, default: :false to your possible_answers table. That way when the user selects the answer you're able to find it and see if it's correct.
Having the answer string in both question and possible_answers isn't the most efficient solution.
Alternatively (If you want to keep the answer in the questions table) you could change right_answer to possible_answer_id and have a has_one :possible_answer, as: :right_answer. So you can do:
question.possible_answers # returns list
question.right_answer # returns just the single record of the correct answer

Rails 4: conditional update action working intermittently

In my Rails 4 app, I have the following models:
class Post < ActiveRecord::Base
has_one :metadatum
end
class Metadatum < ActiveRecord::Base
belongs_to :post
end
Here is the schema of the Metadatum model:
create_table "metadata", force: :cascade do |t|
t.integer "post_id"
t.string "title"
t.text "description"
t.string "host"
t.string "image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "link"
end
In the create action, I do the following to get data with the MetaInspector gem and store them in the Metadatum associated with the right Post record:
def create
[...]
if #post.format == "Link"
#metadatum = #post.build_metadatum
#link = MetaInspector.new(facebook_copy_link(#post.copy)) unless facebook_copy_link(#post.copy).blank?
if #link
#metadatum.title = #link.title
#metadatum.description = #link.meta_tags["name"]["description"].to_s.tr('[""]', '')
#metadatum.host = #link.host
#metadatum.image = #link.images.best
#metadatum.save
end
end
respond_to do |format|
if #post.save
[...]
end
end
end
Now, I need to implement a similar process in the edit action, taking into account that we have two cases:
The current Post :format was not set to "Link" prior to this edit, is now set to "Link", and therefore the Metadatum record associated with the Post is empty.
The current Post format was already set to "Link" prior to this edit, and we simply need to update the attributes of the Metadatum record associated with the Post.
Based on this reasoning, I have implemented the following in Posts#Update:
def update
[...]
if #post.format == "Link"
if #post.metadatum
#metadatum = #post.metadatum
else
#metadatum = #post.build_metadatum
end
#link = MetaInspector.new(facebook_copy_link(#post.copy)) unless facebook_copy_link(#post.copy).blank?
if #link
#metadatum.update!(title: #link.title,
description: #link.meta_tags["name"]["description"].to_s.tr('[""]', ''),
host: #link.host,
image: #link.images.best)
end
end
respond_to do |format|
if #post.update(post_params)
[...]
end
end
end
end
This only partially works and I can't understand why:
let's say I have Link A saved in the post, change it to Link B and update the post, then nothing changes at all, neither in the console nor in the Posts show view.
but, if I try and edit the same post again, keep Link B and update the post, then all right data appears, both in the console and in the Posts show view.
Any idea what is wrong here and how to fix it?

Restrict all access to a model based on information from another model (rails)

I initially asked a similar question in Restricting any access to a model in rails - but this hasn't covered my complete needs.
I have a Dataapi model with many entries:
create_table "dataapis", force: :cascade do |t|
t.string "device_id"
t.datetime "start_time"
t.datetime "end_time"
end
I have a Sandbox model with information on how I want to restrict the access (the Sandbox entries are defined in my admin panel).
create_table "sandboxes", force: :cascade do |t|
t.integer "device_id"
t.integer "user_id"
t.datetime "start_date"
t.datetime "end_date"
end
Effectively, I only want users to have access to the dataapi if they have an appropriate entry in the Sandbox, which restricts access by:
- User
- Start DateTime
- End DateTime
- Access to the device that sent the dataapi (already handled that part in previous question).
I can't seem to find a way to do this though - models don't have access to #user, so I can't check that in the default scope. Any recommendations? I've tried looking at gems (CanCanCan, Pundit, etc) but they only do controller-based authorization. I want this restriction to apply to all queries to dataapi, regardless of what controller called it.
You might want to try the Declarative Authorization gem which provides authorization at the model level.
https://github.com/stffn/declarative_authorization
It gives you a query method that filters based on user's permissions...
Employee.with_permissions_to(:read).find(:all, :conditions => ...)
Another alternative is you can store the current_user in the Dataapi model and populate it with an around_filter.
In the model...
class Dataapi < ActiveRecord::Base
def self.current_user
Thread.current[:current_user]
end
def self.current_user=(some_user)
Thread.current[:current_user] = some_user
end
end
In the application controller...
class ApplicationController < ActionController::Base
around_filter :store_current_user
def store_current_user
Dataapi.current_user = User.find(session[:user_id])
yield
ensure
Dataapi.current_user = nil
end
end
You can then reference the current_user in the Dataapi model using Dataapi.current_user

Write a custom validation method to prevent bots

I'm trying to add a validation to my User model.
I want to prevent people which mail is present in a "Bot" table to register.
The Bot table structure is:
create_table "bots", force: :cascade do |t|
t.string "banned_domains"
t.datetime "created_at"
t.datetime "updated_at"
end
I'm using Devise for registration, and I want to add a validation that check if "email domain is present in the bot table" the record is not valid.
The method to validate is this:
def is_a_bot?(user_email)
Bot.where("banned_domains LIKE (?)", "%#{user_email}%").present?
end
How can I add a validation using this method of the "email" attribute of my user model?
Add this validation to your User class
#in User
before_create :check_not_a_bot
def check_not_a_bot
if Bot.find_by_banned_domains(self.email.split("#").last)
self.errors.add(:email, "domain exists in list of banned domains")
end
end
Define method in the Bot model like that
self.is_a_bot?(user_email)
Bot.where("banned_domains LIKE (?)", "%#{user_email}%").present?
end
then in the User model you can use validates or before_create method (for example)
before_create :check_bot_email
def check_bot_email
if Bot.is_a_bot?(self.email)
errors.add(:base, "The user is a bot")
end
end

Populate foreign key in belongs_to association

Basically, all I need is a template_id field in my business table to be assigned correctly so that if I did Business.first.template it would return the result of the current assigned template for that business. At the moment I get I am just getting 'nil'
In my project a Business belongs_to a template (I believe this puts the primary key in the template table and the foreign key in the business table).
class Business < ActiveRecord::Base
belongs_to :template
class Template < ActiveRecord::Base
has_many :businesses
When the user fills out a form for a 'new business' they select the template they wish to use. The templates table is already filled with 3 templates, template_id, 0, 1, 2 (so I cant really work out if anything needs to be 'created'). The user is limited through the form to select only one of 3 templates (radio buttons).
When submitting the form and creating the business the link between the business and the template is currently not created. I don't have anything about the template creation in my business class because I cant work out what would need to be created, the template records already exist in the template table and are static.
Business Controller
def new
#business = current_user.businesses.build
#business.addresses.build
end
# POST /businesses
def create
#business = Business.new(business_params)
#business.users << current_user
if #business.save
redirect_to #business, notice: 'Business was successfully created.'
else
render action: 'new'
end
end
def business_params
params.require(:business).permit(:name, :email, :template_id, addresses_attributes [:number, :street, :suburb, :state, :country], template_attributes: [:name])
I am not sure if I should be assigning template_id myself or doing something with 'build_template'
Schema
create_table "businesses", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
t.string "description"
t.string "sub_heading"
t.string "email"
t.integer "template_id"
end
create_table "templates", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
t.integer "cost"
end
I am not sure if I should be assigning the value as either 0, 1 or 2 directly from the form submitted by the user to template_id in the business table or if I should be allowing nested attributes as I did with the addresses table.
Any help is appreciated.
Thanks
The foreign key to template id will be fine though. It is what ties an instance of Business to a and instance of Template.
You aren't creating a template, you are selecting one already from a list of created templates. You can access a business's template should be as simple as Business.find(id).template where Id is the id of the business you want knowledge about.

Resources