Associating a shipping address model with an order model Rails 4 - ruby-on-rails

I'm trying to associate a shipping address with an order in my application but cannot figure out a good way to do this.
This is the association I have:
class ShippingAddress < ActiveRecord::Base
belongs_to :order
end
and
class Order < ActiveRecord::Base
has_one :shipping_address
end
I see a lot of examples assuming there is a current_user, but I am trying to make the checkout process so that a user does not have to be signed in.
Here is what my Shipping Addresses controller looks like:
class ShippingAddressesController < ApplicationController
def new
#shipping_address = ShippingAddress.new
end
def create
#shipping_address = ShippingAddress.new(shipping_address_params)
#shipping_address.save
redirect_to root_url
end
private
def shipping_address_params
params.require(:shipping_address).permit(:shipping_name, :address_line1, :address_line2, :address_city,
:address_state, :address_zip, :address_country, :user_id, :order_id)
end
end
This is what the order controller looks like:
class OrdersController < ApplicationController
def create
#order = Order.find_or_initialize_by(item_id: params[:order][:item_id], order_completed: false, user_id: params[:order][:user_id])
#order.update_attribute(:quantity_requested, params[:order][:quantity_requested])
#order.save
redirect_to #order
end
private
def order_params
params.require(:order).permit(:user_id, :item_id, :quantity_requested, :quantity,
:order_completed, :sold, :shipping_address_id)
end
end
Can someone please let me know if I'm thinking about this the right way and how I can make the order aware of the shipping address?
Thanks in advance and sorry if I'm missing something obvious.

Not sure if you really need a :user_id in parameters. If user is logged in, you can get it via current_user. If not, you'll never know his id anyway.
Order is aware of the shipping address when you create it like order.create_shipping_address or ShippingAddress.create order: order (or populate shipping_addresses.order_id field any other way you like). And then you can use order.shipping_address and shipping_address.order to find related objects.
Also, usually managers(or site) have to communicate with customer anyway, at least send some emails regarding order status, so I don't require user to log in, but if he is not logged in, I create a new one on checkout to store his contacts(and for some other useful stuff :) )

Related

Creating model and nested model (1:n) at once with ActiveRecord

My Rails5 application has an organization model and a user model (1:n relationship). The workflow of creating an organization should include the creation of the organization's first user as well. I thought this would be able with ActiveRecord through nested models, however the create action fails with the error message "Users organization must exist".
class Organization < ApplicationRecord
has_many :users, dependent: :destroy
accepts_nested_attributes_for :users
end
class User < ApplicationRecord
belongs_to :organization
end
class OrganizationsController < ApplicationController
def new
#organization = Organization.new
#organization.users.build
end
def create
#organization = Organization.new(organization_params)
if #organization.save
redirect_to #organization
else
render 'new'
end
end
def organization_params
params.require(:organization).permit(:name, users_attributes: [:name, :email, :password, :password_confirmation])
end
end
In the view I use the <%= f.fields_for :users do |user_form| %> helper.
Is this a bug on my side, or isn't this supported by ActiveRecord at all? Couldn't find anything about it in the rails guides. After all, this should be (theoretically) possible: First do the INSERT for the organization, then the INSERT of the user (the order matters, to know the id of the organization for the foreign key of the user).
As described in https://github.com/rails/rails/issues/18233, Rails5 requires integrity checks. Because I didn't like a wishy-washy solution like disabling the integrity checks, I followed DHH's advice from the issue linked above:
I like aggregation through regular Ruby objects. For example, we have a Signup model that's just a Ruby object orchestrating the build process. So I'd give that a go!
I wrote a ruby class called Signup which encapsulates the organization and user model and offers a save/create interface like an ActiveRecord model would. Furthermore, by including ActiveModel::Model, useful stuff comes in to the class for free (attribute hash constructor etc., see http://guides.rubyonrails.org/active_model_basics.html#model).
# The Signup model encapsulates an organization and a user model.
# It's used in the signup process and helps persisting a new organization
# and a referenced user (the owner of the organization).
class Signup
include ActiveModel::Model
attr_accessor :organization_name, :user_name, :user_email, :user_password, :user_password_confirmation
# A save method that acts like ActiveRecord's save method.
def save
#organization = build_organization
return false unless #organization.save
#user = build_user
#user.save
end
# Checks validity of the model.
def valid?
#organization = build_organization
#user = build_user
#organization.valid? and #user.valid?
end
# A create method that acts like ActiveRecord's create method.
# This builds the object from an attributes hash and saves it.
def self.create(attributes = {})
signup = Signup.new(attributes)
signup.save
end
private
# Build an organization object from the attributes.
def build_organization
#organization = Organization.new(name: #organization_name)
end
# Build a user object from the attributes. For integritiy reasons,
# a organization object must already exist.
def build_user
#user = User.new(name: #user_name, email: #user_email, password: #user_password, password_confirmation: #user_password_confirmation, organization: #organization)
end
end
Special thanks to #engineersmnky for pointing me to the corresponding github issue.
You're looking for "Association Callbacks". Once you send those params to your organization model you have access to them inside that model. If everytime an organization is created there will be a new user assigned to it you can just do the following in your Organization Model:
has_many :users, dependent: :destroy, after_add: :create_orgs_first_user
attr_accessor: :username #create virtual atts for all the user params and then assign them as if they were organizational attributes in the controller. This means changing your `organization_params` method to not nest user attributes inside the array `users_attributes`
def create_orgs_first_user
User.create(name: self.username, organization_id: self.id, etc.) # You can probably do self.users.create(params here) but I didn't try it that way.
end
The "Users organization must exist" error should not occur. ActiveRecord is "smart," in that it should execute two INSERTs. First, it will save the model on the has_many side, so that it has an id, and then it will save the model on the belongs_to side, populating the foreign key value. The problem is actually caused by a bug in accepts_nested_attributes_for in Rails 5 versions prior to 5.1.1. See https://github.com/rails/rails/issues/25198 and Trouble with accepts_nested_attributes_for in Rails 5.0.0.beta3, -api option.
The solution is to use the inverse_of: option or, better yet, upgrade to Rails 5.1.1.
You can prove that this is true by removing the accepts_nested_attributes_for in your Organization model and, in the Rails console, creating a new Organization model and a new User model, associating them (eg myorg.users << myuser) and trying a save (eg myorg.save). You'll find that it will work as expected.

How to validate the number of children records?

I have Rails 4 app with two models.
class User
has_many :bids
end
class Bid
belongs_to :user
end
A User can only create one bid per week, so I added the following to the Bid table
add_column :bids, :expiry, :datetime, default: DateTime.current.end_of_week
and the following scopes to the Bid model
scope :default, -> { order('bids.created_at DESC') }
scope :active, -> { default.where('expiry > ?', Date.today ) }
I can now prevent a User creating multiple Bids at the controller level like so:
class BidsController
def new
if current_user.bids.active.any?
flash[:notice] = "You already have an active Bid. You can edit it here."
redirect_to edit_bid_path(current_user.bids.active.last)
else
#bid = Bid.new
respond_with(#bid)
end
end
end
But what is the best approach for validating this at the model level?
I've been trying to set up a custom validation, but am struggling to see the best way to set this up so that the current_user is available to the method. Also, am I adding errors to the correct object?
class Bid
validate :validates_number_of_active_bids
def validates_number_of_active_bids
if Bid.active.where(user_id: current_user).any?
errors.add(:bid, "too much")
end
end
end
In order to maintain separation of concerns, keep the current_user knowledge out of the model layer. Your Bid model already has a user_id attribute. Also, I'd add an error like so since the validation is not checking a "bid" attribute on Bid, but rather the entire Bid may be invalid.
class Bid
validate :validates_number_of_active_bids
def validates_number_of_active_bids
if Bid.where(user_id: user_id).active.any?
errors[:base] << "A new bid cannot be created until the current one expires"
end
end
end
This seems like it should be in a collaborator service object. Create a new class that is named appropriately (something like ActiveBid, maybe think a little on the name) That class will be initialized with a current_user and either return the active bid or false.
This limits the logic for this limitation into a single place (maybe some plans in the future can have 2, etc.
Then in the controller do a before_action that enforces this logic.
before_action :enforce_bid_limits, only: [:new, create]
private
def enforce_bid_limits
active_bid = ActiveBid.new(current_user).call
if active_bid #returns false or the record of the current_bid
flash[:notice] = "You already have an active Bid. You can edit it here."
redirect_to edit_bid_path(bid)
end
end
Later on if you end up needing this logic in several places throw this stuff in a module and then you can just include it in the controllers that you want.

Rails 4 STI inheritance

I'm using single table inheritance successfully like so:
class Transaction < ActiveRecord::Base
belongs_to :order
end
class Purchase < Transaction
end
class Refund < Transaction
end
The abbreviated/simplified PurchaseController looks like this:
class PurchaseController < TransactionController
def new
#transaction = #order.purchases.new(type: type)
end
def create
#transaction = #order.purchases.new secure_params
if #transaction.save
redirect_to #order
else
render :new
end
end
end
The abbreviated/simplified Purchase model looks like this:
class Purchase < Transaction
attr_accessor :cc_number, :cc_expiry, :cc_csv
end
What I'm trying to do is have different variations of a purchase, for instance a cash purchase & a cheque purchase. The issue is I'm not sure how to call the model for that variation.
For example:
class Cash < Purchase
attr_accessor :receipt_no
end
class CashController < TransactionController
def new
# This will use the Purchase.rb model so that's no good because I need the Cash.rb model attributes
#transaction = #order.purchases.new(type: type)
# This gives me the following error:
# ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: Purchase is not a subclass of Cash
#transaction = Cash.new(type: 'Purchase', order: #order.id)
end
end
I'm not sure why it doesn't work for you, this works fine for me:
#order.purchases.new(type: "Cash") # returns a new Cash instance
You can also push a new Cash on to the association if you are ready to save it:
#order.purchases << Cash.new
Or you can define a separate association in Order:
class Order < ActiveRecord::Base
has_many :cashes
end
#order.cashes.new # returns a new Cash instance
Class
Maybe I'm being obtuse, but perhaps you'll be willing to not make the Purchase type an inherited class?
The problem I see is that you're calling Cash.new, when really you may be better to include all the functionality you require in the Purchase model, which will then be able to be re-factored afterwards.
Specifically, why don't you just include your own type attribute in your Purchase model, which you'll then be able to use with the following setup:
#app/controllers/cash_controller.rb
class CashController < ApplicationController
def new
#transaction = Purchase.new
end
def create
#transaction = Purchase.new transaction_params
#transaction.type ||= "cash"
end
private
def cash_params
params.require(:transaction).permit(:x, :y, :z)
end
end
The only downside to this would be that if you wanted to include different business logic for each type of purchase, you'll still want to use your inherited model. However, you could simply split the functionality in the before_create callback:
#app/models/puchase.rb
class Purchase < Transaction
before_create :set_type
private
def set_type
if type =="cash"
# do something here
else
# do something here
end
end
end
As such, right now, I think your use of two separate models (Cash and Cheque) will likely be causing much more of an issue than is present. Although I'd love to see how you could inherit from an inherited Model, what I've provided is something you also may wish to look into

Hide a field in Ruby on Rails

I've a field in my database called IP where I put the user IP (in #create method) when he send a message in my blog built in Rails.
But the field is visible when I want to see the articles in another format (JSON).
How can I hide the field IP?
You can do it in a format block in your controller like this:
respond_to do |format|
format.json { render :json => #user, :except=> [:ip] } # or without format block: #user.to_json(:except => :ip)
end
If you want to generally exclude specific fields, just overwrite the to_json method in your user model:
class User < ActiveRecord::Base
def to_json(options={})
options[:except] ||= [:ip]
super(options)
end
end
Update: In Rails 6, the method became as_json:
class User < ApplicationRecord
def as_json(options={})
options[:except] ||= [:ip]
super(options)
end
end
While this is not quite the right solution for passwords or for what is specifically asked, this is what comes up when you google for hiding columns in ActiveRecord, so I'm going to put this here.
Rails5 introduced new API, ignored_columns, that can make activerecord ignore that a column exists entirely. Which is what I actually wanted, and many others arriving here via Google probably do too.
I haven't tried it yet myself.
class User < ApplicationRecord
self.ignored_columns = %w(employee_email)
end
https://blog.bigbinary.com/2016/05/24/rails-5-adds-active-record-ignored-columns.html

Is there a plugin or gem that can help me do "invite a friend" capability in rails?

I want to add the ability for users to invite a friend.
The email should be generated so that, if someone clicks on the link and register, that person is automatically a friend.
Not sure what the options are, but wanted some ideas and strategies as an alternative to building it from scratch.
I'm not aware of any gems that handle the entire process (user >> email >> signup). If you're just looking to create the relationship when a user comes from a specific link, create a special invitation route (the separate controller isn't necessary but just to make it clear):
# routes.rb
match '/invite/:friend_id' => 'public#invite', :as => :invite
# PublicController
def invite
session[:referring_friend] = params[:friend_id]
redirect_to root_path
end
# UsersController
def create
#user = User.new(params[:user])
if #user.save
#user.create_friendship(session[:referring_friend]) if session[:referring_friend]
...
else
...
end
end
If you want to track conversion metrics, I'd recommend creating a link model and using that to track clicks and signups:
class Link < ActiveRecord::Base
belongs_to :user
attr_accessible :user, :user_id, :clicks, :conversions
def click!
self.class.increment_count(:clicks, self.id)
end
def convert!
self.class.increment_count(:conversions, self.id)
end
end
# routes.rb
match '/invite/:link_id' => 'links#hit', :as => :invite
# LinksController
def hit
link = Link.find(params[:link_id])
link.click!
session[:referring_link_id] = link.id
redirect_to root_path # or whatever path (maybe provided by link...)
end
# UsersController
def create
#user = User.new(params[:user])
if #user.save
if session[:referring_link_id]
link = Link.find(session[:referring_link_id])
link.convert!
#user.create_friendship(link.user_id)
end
...
else
...
end
end
Which method you choose depends on what you'll want to track down the road.
I don't know gem for rails. But there's an extension for Spree, rails based e-commerce project. Check it out & probably you can refer how it's implemented.
https://github.com/spree/spree_email_to_friend
I don't know about some gem to support this, but solution should be rather trivial. I guess you need Friendship model, you can place some status in it like 'waiting_for_approvment' and send in mail link with that Friendship model id. When user accepts either way you just change status to 'approved' or even 'rejected' if you want to track that too.
Start by defining the relationship:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends, :class_name => "User", :join_table => "friends_users"
end
So really, User relates to itself with a different name. Then you can use something along the lines of:
#current_user.friends << #selected_user
in your controller.

Resources