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.
Related
I've got a portfolio model with following fields:
name: string (required)
status: string (required) one of: draft, active, funded
One of the requirement is that a newly created portfolio should have a status draft. I could set a default value inside of migration something like:
create_table :portfolios do |t|
t.string :name, null: false
t.string :status, null: false, default: 'draft'
t.timestamps
end
But I don't think it will easy to maintain. Of course I could set this status inside create method like:
Portfolio.create!(
name: params[:name],
status: 'draft'
)
Is there a better way to create such record? maybe some method inside of model?
class Portfolio < ApplicationRecord
after_initialize do
self.name = "draft"
end
end
I think it's better to do it using after_initialize because this callback will guarantee that the default value will be there from the very beginning of the life cycle of the object
Portfolio.new.name
#shoudl give you draft
I've got a Rails app where Users are able to keep track of airing shows and episodes.
To simplify the process of keeping track of (not yet) watched shows, users are able to synchronize their account with other services. They can, in their user settings page, choose which service they want to synchronize with.
To synchronize, I load their profile from the other service, and then run it through an algorithm which detects changes from the last synchronization, and updates the DB accordingly. In order to store the last synchronization status, for each Show ID, I create a "UsersSyncIdStatus" object which stores the show ID, as well as the current status for that show in the synchronized service.
Note that the services do not use the same Show IDs as my website, which means that I have a table which I can use to "convert" from their show IDs to my show IDs. Since the information each service provides is different, they must be stored in different tables.
Right now, this is (a simplified version of) how the DB schema is set up:
create_table "service1_ids", force: :cascade do |t|
t.integer "service_id", null: false
t.integer "show_id", null: false
[...]
end
create_table "service2_ids", force: :cascade do |t|
t.integer "service_id", null: false
t.integer "show_id", null: false
[...]
end
create_table "users_sync_id_statuses", force: :cascade do |t|
t.integer "user_id"
t.integer "service_id", null: false
t.integer "sync_status", default: 0, null: false
t.datetime "sync_date", null: false
[...]
end
create_table "users", force: :cascade do |t|
[...]
t.datetime "synced_at"
t.boolean "sync_enabled", default: false, null: false
t.integer "sync_method", default: 0, null: false
[...]
end
In particular, users.sync_method is an enum which stores the service the user has selected for synchronization:
SYNC_METHODS = {
0 => {
symbol: :service1,
name: 'Service1',
model_name: 'Service1Id',
show_scope: :service1_ids
}
1 => {
symbol: :service2,
name: 'Service2',
model_name: 'Service2Id',
show_scope: :service2_ids
}
}
This means I can easily know the model name of the IDs of a specific user by just doing SyncHelper::SYNC_METHODS[current_user.sync_method][:model_name].
Now, the question is, how can I have a relationship between "users_sync_id_statuses" and "serviceN_ids"? In order to know which class the "service_id" column corresponds to, I have to 'ask' the user model.
I currently have it implemented as a method:
class User
def sync_method_hash
SyncHelper::SYNC_METHODS[self.sync_method]
end
def sync_method_model
self.sync_method_hash[:model_name].constantize
end
end
class UsersSyncIdStatus
def service_id_obj
self.user.sync_method_model.where(service_id: self.service_id).first
end
end
However, UsersSyncIdStatus.service_id_obj is a method, not a relationship, which means I cannot do all the fancy stuff a relationship allows. For example, I cannot easily grab the UsersSyncIdStatus for a specific user and show ID:
current_user.sync_id_statuses.where(service_id_obj: {show_id: 123}).first
I could turn it into a polymorphic relationship, but I really don't want to have to add a text column to contain the class name, when it is a "constant" from the point of view of each user (for a user to switch synchronization service, all UsersSyncIdStatuses for that user are destroyed, so a user never has more than 1 service type in their UsersSyncIdStatuses).
Any ideas? Thank you in advance!
I don't think vanilla Rails 5 supports what I want to do, someone please correct me if I'm wrong.
Still, after some research into how Rails implements polymorphic relationships, I was able to relatively easily monkey-patch Rails 5 to add this functionality:
config/initializers/belongs_to_polymorphic_type_send.rb:
# Modified from: rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
module ActiveRecord
# = Active Record Belongs To Polymorphic Association
module Associations
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
def klass
type = owner.send(reflection.foreign_type)
type.presence && (type.respond_to?(:constantize) ? type.constantize : type)
end
end
end
end
app/models/users_sync_id_status.rb:
class UsersSyncIdStatus
belongs_to :service_id_obj, polymorphic: true, foreign_key: :service_id, primary_key: :service_id
def service_id_obj_type
self.user.sync_method_model
end
end
With this monkey-patch, belongs_to polymorphic associations do not assume that the type field is a varchar column, but instead call it as a method on the object. This means you can very easily add your own dynamic behavior, without breaking any old behavior (AFAIK, didn't do intensive testing on that).
For my specific use-case, I have the sync_id_obj_type method query the user object for the class that should be used in the polymorphic association.
What I want is to be able to easily be able to find the team name associated with a membership without having to send another request to the database. My plan was to just add a team_name attribute to the Memberships table, but I can't think of a way to have this new attribute stay in sync with the name attribute in the Teams table. In other words, I want it to be the case that, if the owner of a team changes the team's name, then all memberships to that team will be updated (with the new team name) as well.
Here is what my setup looks like. I've fairly new to Rails.
/app/models/membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :team
end
/app/models/team.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :team
end
/app/db/schema.rb
ActiveRecord::Schema.define(version: 20161022002620) do
create_table "memberships", force: :cascade do |t|
t.integer "user_id"
t.integer "team_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "teams", force: :cascade do |t|
t.string "name"
t.integer "user_id"
end
end
If there is a better way to achieve what I am asking, then please let me know as well.
With this relational data your membership doesn't need a team name attribute - it is already available through the team association.
Generally there's no reason to keep data 'in sync' in this way unless you're performing some sort of computation. You don't need to store a name attribute on Membership - you can just use the existing one in Team.
I have seen people add duplicate database columns because they don't know how to traverse through associations. But unless you're using some noSql system, this isn't the 'right way' to do it - there is an underlying SQL API (through ActiveRecord) that performs lookups very efficiently.
in response to your comment. Do this:
class Membership < ActiveRecord::Base
def name
team.name
end
end
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?
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