Using rails associations for models (undefined method `has_one') - ruby-on-rails

I am trying to open up the registration page to create an account (which has a payment) and I get this error "undefined method `has_one' for Account:Class". I am not using a database so I am not using active record. Is there a way around this?
account.rb
class Account
include ActiveModel::Model
attr_accessor :company_name, :phone_number,
:card_number, :expiration_month, :expiration_year, :cvv
has_one :payment
end
payment.rb
class Payment
include ActiveModel::Model
attr_accessor :card_number, :expiration_month, :expiration_year, :cvv
belongs_to :account
validates :card_number, presence: true, allow_blank: false
validates :cvv, presence: true, allow_blank: false
end
account_controller.rb
class AccountController < ApplicationController
def register
#account = Account.new
end
end

has_one and belongs_to are not part of ActiveModel::Model. They are part of ActiveRecord as they specify how to get the objects from the relational database.
In your case I guess you should just have another attribute payment in Account model.
class Account
include ActiveModel::Model
attr_accessor :company_name, :phone_number,
:card_number, :expiration_month,
:expiration_year, :cvv,
:payment
end
and then in your controller do something like
class AccountController < ApplicationController
def register
#account = Account.new
#account.payment = Payment.new
end
end
or you could event initialize the payment in initializer of Account class. Also it seems like Payment does not need to know about Account.

Certainly this is a very old question, but I ran across it while trying to solve a similar problem and eventually found that solutions have arisen in the intervening years. So, given the only posted answer was never marked as "accepted," I thought I'd throw my hat in the ring.
Rails 5 introduced the ActiveRecord Attributes API, and by way of this post by Karol Galanciak describing it you may be interested to take a look at a gem created by the same author. If you took a look at the issues for that gem, you might be interested to read in issue #12 that the Attributes API functionality is now present in ActiveModel, though it is not publicly documented (the module Attributes is flagged with #:nodoc:, see attributes.rb) and perhaps should not be relied upon to be consistent from version to version, though the wind, soft as it may seem at times, certainly seems to be blowing in the direction of a "public" ActiveModel::Attributes API.
Nevertheless, if you were to throw caution to the wind and use the not-quite-public ActiveModel::Attributes you could do something like this (note: I cobbled this together for my own project and rewrote it a bit to fit your example, your needs may be different than mine):
class AccountPayment
include ActiveModel::Model
attr_accessor :card_number, :expiration_month, :expiration_year, :cvv
validates :card_number, presence: true, allow_blank: false
validates :cvv, presence: true, allow_blank: false
end
class AccountPaymentType < ActiveModel::Type::Value
def cast(value)
AccountPayment.new(value)
end
end
class Account
include ActiveModel::Model
include ActiveModel::Attributes #being bad and using private API!
attr_accessor :company_name, :phone_number, :card_number, :expiration_month, :expiration_year, :cvv
attribute :payment, :account_payment
end
And somewhere you must register the type - in rails it'd be in an initializer, but in my code I just stashed it at the top of the equivalent of your Account model:
ActiveModel::Type.register(:account_payment, AccountPaymentType)

Related

Using Concerns to Extend User Model With Roles

I'm experimenting in building a roles-based data structure in Rails using Concerns with role-specific methods. I've explored other solutions, like STI, polymorphic associations, but opted to try this method.
All attributes depicted are stored in one table, users.
module Client
extend ActiveSupport::Concern
def self.extended(target)
target.class_eval do
validates :emergency_contact, presence: true
end
end
end
module Doctor
def self.extended(target)
target.class_eval do
validates :credentials, presence: true
end
end
end
class User < ApplicationRecord
validates :role, allow_blank: true, inclusion: { in: roles }
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true
after_initialize { extend_role }
# Extend self with role-appropriate Concern
def extend_role
extend role.camelize.constantize if role
end
def role=(role)
super(role)
extend_role
end
end
My challenge is during the changing of a role, specifically, what can be done (if anything) to negate the previous role's Concern having extended the User?
Each user will have 1 role, so having the Client and Doctor concerns both mixed into the User would not be appropriate (at this time, at least).
In an ideal solution, the User instance would morph with the role change and retain all changed attributes.
So, calling this:
user.update_attributes({ first_name: 'Wily', last_name: 'McDoc', role: 'doctor' })
...would first handle the role change and then update the attributes.
guess could, in part, remove all methods:
Module.instance_methods.each{|m|
undef_method(m)
}
also, How can I reverse ruby's include function, in case that may be of interest

Virtual attributes Rails - Unknown Attribute Error

I'm making a Rails 4 application. Each incident belongs to a user. Each incident has many events. I want that I can create an Incident with current_user.incidents.new and pass it an attribute message which exists in the Event model. On creation, I wish to create a new event with the said message.
Here's my Incident model.
class Incident < ActiveRecord::Base
# The 'incident' model.
# Incidents are created by Users (belongs_to :user)
# Incidents may be public or private.
# Incidents have many events (identified, fixing, etc.)
belongs_to :user
has_many :events
validates :name, presence: true, length: {maximum: 256}
validates_presence_of :component
validates_presence_of :public
validates_presence_of :user_id
attr_accessor :message
validates_associated :message, presence: true
def message
Event.find_by_incident_id(self.id).message
end
def message=(value)
self.events.new(message: value, status: self.status)
end
private
def incident_params
params.require(:incident).permit(:name, :status, :user_id, :message)
end
end
But, when I run #i = Incident.new(message: 'something'), I get ActiveRecord::UnknownAttributeError: unknown attribute 'message' for Incident.
Please help me figure this out.
The problem is that you're passing the values to the default ActiveRecord::new method for Incident, which won't care about your getters, setters and accessors, it goes straight to the columns.
Some people override or adapt the build method that can implement clever logic like you require, without affecting the base initialize method.
Because your Incident model inherits from ActiveRecord::Base, it inherits the constructor. The ActiveRecord constructor only accepts model attributes (db columns) as params. You are trying to call an arbitrary instance method. You could create a class method that creates an instance then adds the message:
def self.create_with_message(msg)
i = Incident.new
i.message(msg)
return(i)
end

Rails - new User builds dependent Email and validates both?

I'm building a quick Rails project that allows users to manage their email addresses. Users can have many emails, but one (and only one) of those emails has to be marked as 'primary' (for login), and a user cannot exist without a primary email.
I've been struggling to get this to work right - it seems so circular to me. I need to build a User, and then the Email, but I don't want to save the User into the database unless the Email is valid, which it won't be until the User is saved (because of the validates :user, presence: true constraint).
Accepts nested resources for doesn't seem to work with .new (works fine with .create), and if my Email fails its validations, the User still shows as valid.
Been having a difficult time trying to find good resources (or SO questions) for building/validating multiple/dependent models from a single form.
What's the most Rails way to do this?
User
has_many :emails
has_one :primary_email, -> { where(primary: true) }, class_name: "Email"
accepts_nested_attributes_for :primary_email
validates :first_name, presence: true
validates :last_name, presence: true
validates :birthday, presence: true
validates :password_digest, presence: true
Email
belongs_to :user
validates :user, presence: true
validates :address, presence: true, uniqueness: {
case_sensitive: false
}
UsersController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
# do something
else
# show #user.errors
end
end
private
def user_params
params.require(:user).permit(
:first_name,
:last_name,
:birthday,
:password,
:password_confirmation,
:primary_email_attributes => [:address]
)
end
EDIT
The Email model also contains the following fields:
label = string, eg. 'Personal', 'Work', etc
primary = boolean, whether it's marked as primary email or not
confirmation_code = autogenerated on creation, used to confirm ownership
confirmed = boolean, whether it's been confirmed or not
class User
user has_many :emails
user has_one :primary_email, -> { where(primary: true) }, class_name: "Email", autosave: true
after_initialize {
build_primary_email if new_record?
}
end
class Email
# use gem https://github.com/balexand/email_validator
validates :my_email_attribute, :email => true
end
So after a user initialized its building a primary_email so that record is already associated, or at least it will be if it can be saved. the autosave is working pretty cool - if the primary-email can't be saved due validation error, the user can't neither. should work out of the box, im in a bus right now, can't check it. cheers
futher information: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
If validations for any of the associations fail, their error messages will be applied to the parent. That means, the Parent Model (in your case User) is having errors, and thats why the saving is not possible! that's what you are looking for.
I would store a primary email as a common field and additional emails some another way. I would prefer to store additional emails in another field too that is Array rather than in an associated table. You shouldn't store a primary email in another table. Just imagine, every time you need authorize user or just get his email you will perform an extra request to db.
Meant to post this months ago.
The solution, keeping users and emails normalized across different models without storing a primary email as an attribute on the user, is to use inverse_of:
User.rb
class User < ActiveRecord::Base
has_many :emails, inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :emails
validates :emails, presence: true
end
Email.rb
class Email < ActiveRecord::Base
belongs_to :user, inverse_of: :emails
validates :user, presence: true
end
This allows validations to be performed using in-memory objects, rather than via database calls (ie the object associations are being validated, rather than the presence of an id/record in the database). Therefore they both pass validation and can both be saved in the same transaction.
See: https://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations

Integration testing rails-api model using rspec and fixtures

I'm pretty new to Rails and I've finished (I think) my rails API, and now I'm up to test it.
Say I have this model:
class Child < ActiveRecord::Base
belongs_to :parents
validates :id, presence: true
validates :type, presence: true
validates :parent_id, presence: true
before_create :update_child_if_exists
private
def update_child_if_exists
conditions = {type: self.type, parent_id: self.parent_id}
if existing_measure = Child.find_by(conditions) # or Child.where(conditions).last
new_values = (existing_child.values || []) + [self.values]
existing_measure.update_attribute(:values, new_values)
end
end
end
This is meant to update a field in the child table if the record by type and parent_id already exists, else it would be created. How can I write a test for this using RSpec?
Any help would be much appreciated, I'm pretty lost here.
If any additional information is needed, please ask me and I will provide it.

Rails errors array for form_tag forms

Rails 4 app.
Using form_tag to build a form and wanted to know the typical way (if there is one) for handling and displaying errors when the form is not backed my a model?
All the examples I find relate to a model and the typical #model.errors.any? view conditional but this won't do for form_tag.
What you should do is :
first include ActiveModel::Model
then make accessor for your attributes
finally add validation to these attributes
For example if you have a contact model which you don't want to bind it with database
class Contact
include ActiveModel::Model
attr_accessor :name, :email, :message
validates :name, presence: true
validates :email, presence: true
validates :message, presence: true, length: { maximum: 300 }
end
then in your view you can loop through your errors like you are using an habitual activeRecord Model
if #model.errors.any?
# Loop and show errors....
end
I would recommend to include ActiveModel::Validations on a class which doesn't behave model but we need validations.For an example, consider a Ticket class
Rails 4
class Ticket
include ActiveModel::Model
attr_accessor :title, :description
validate_presence_of :title
validate_presence_of :description
end
In addition for more details,if you see Rails 4 activemodel/lib/active_model/model.rb code for better understanding why in rails 4 "include ActiveModel::Model" only enough to make a class to behave like model.
def self.included(base)
base.class_eval do
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Conversion
end
end
Rails 3
class Ticket
include ActiveModel::Conversion
include ActiveModel::Validations
extend ActiveModel::Naming
attr_accessor :title, :description
validate_presence_of :title
validate_presence_of :description
end
Your Ticket class behave like model that makes you to use these methods for error validations
Ticket.new(ticket_params)
#ticket.valid?
#ticket.errors
#ticket.to_param
I hope it may help you to solve your problems.

Resources