Creating associations by using checkboxes - ruby-on-rails

A User can only have two types of Subscriptions: DailySubscription and WeeklySubscription. When the user is at the new and edit action, I'd like them to check off either of the subscriptions they would like to get.
I'm comfortable using nested fields (as per Ryan Bates' screencast here) but I think when I add inheritance, it really complicating matters. Is there a better way?
class User < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
# type field is defined in the migration for Single Table Inheritance
end
class DailySubscription < Subscription
# Business logic here
end
class WeeklySubscription < Subscription
# Different business logic here
end
My initial efforts with the controller are wacky:
class UsersController < ApplicationController
def new
#user = User.new
# I can't use #user. subscriptions.build as Rails doesn't
# know what type of model to add!
#user.subscriptions = [DailySubscription.new, WeeklySubscription.new]
end
...
end
I think I am conceptually really missing something here but I can't figure it out. Help!

Judging from your description, your user has only two possible subscription choices: daily and/or weekly. Therefore you dont need to have a has_many association because two has_ones would suffice(note polymorphic subscribeable below:
class User < ActiveRecord::Base
has_one :daily_subscription, :as => :subscribeable
has_one :weekly_subscription, :as => :subscribeable
end
class Subscription < ActiveRecord::Base
belongs_to :subscribeable, :polymorphic => true
# type field is defined in the migration for Single Table Inheritance
end
class DailySubscription < Subscription
# Business logic here
end
class WeeklySubscription < Subscription
# Different business logic here
end
furthermore for the controller you just need to initialize User. Upon initialization, #user.daily_subscription and weekly_subscription will be null as determined by .blank? method. When you go ahead and create the user in the create method, you will need to populate these fields with instances of corresponding subscriptions.
class UsersController < ApplicationController
def new
#user = User.new
# bam -- youre done.
end
...
end

Related

Set variable in application_controller and access in model

In my Application Controller I set up a currency variable based on geolocation:
class ApplicationController < ActionController::Base
before_action :currency
protected
def currency
cookies[:country] ||= request.location.country
case cookies[:country]
when "MY"
c = "MYR"
when "SG"
c = "SGD"
else
c = "USD"
end
#currency = Currency.find_by(name: c)
end
end
I have Model Product with price method and many currencies, many prices, ie: one product can have multiple currencies with custom pricing.
class Product < ApplicationRecord
has_many :prices
has_many :currencies, through: :prices
def price
# how to access #currency?
end
end
class Price < ApplicationRecord
belongs_to :product
belongs_to :currency
end
class Currency < ApplicationRecord
has_many :prices
has_many :products, through: :prices
end
What is the best way to access #currency in the Model Product.price? Or how can I tell the method to return the price only in the #currency? This is probably not the best way to deal it so please advice.
You have things a little backwards so you're trying to solve the wrong problem. Models shouldn't be trying to get information out of the controller layer, the controller should be sending that information into the model:
class Product < ApplicationRecord
#...
def price_in(currency)
# Access the associations however you need to and handle missing
# information however fits your application in here...
end
end
and then in your controller or view:
price = product.price_in(#currency)
You should be able to call methods on your models from anywhere (controllers, rake tasks, jobs, the console, ...) without having to worry about all the request-specific state.
You shouldn't. You're violating a lot of design principles by doing so, and you'll only frustrate yourself down the road.
Your model shouldn't care about the context of your controller. It should only care about data as it relates to itself.
What you can do, though, is use the ActiveModel::Attributes API.
In your model:
class Product < ApplicationRecord
has_many :prices
has_many :currencies, through: :prices
attribute :currency
def price
self.currency
end
end
In your controller:
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
#product.currency = #currency
end
end
There's a lot more you can do with the ActiveModel::Attributes API, like set defaults, run validations, and even set what type of object it is (boolean/true/false, integer, string, etc.) — it behaves just like your regular attributes do on a model, they just aren't backed by your database.
More info on this great API https://apidock.com/rails/ActiveRecord/Attributes/ClassMethods/attribute
Take a look at this answer for info on how to access cookies in a model. But you should consider moving this method out of the controller and into the Currency class, which seems like a more logical place for it. Then you can call the method from your Product class as Currency.get_currency, for example.

Linking two controllers in Rails

I have a signed in user profile and each profile has its own phone-book that no other user can access. The question is how should i implement it. Considering User as one controller and phone-book as another i'm not able to establish a relation between the two for a specific user login.
What should be my approach?
I have a sparate model for User and separate model for phone-book and have established a relation between them using has_many and belongs_to macro.
Let's start with the models. You say that each User has only one PhoneBook so I would say that the right models should rather be:
class User < ActiveRecord::Base
has_one :phone_book
end
class PhoneBook < ActiveRecord::Base
belongs_to :user
end
Now, about the controllers.
When you have a signed in User you will eventually have a "session thing" going on.
Let's say you're using devise, then you will have a variable current_user that references the logged in user. So the PhoneBooksController will be something like:
class PhoneBooksController < ApplicationController
def index
#phone_book = current_user.phone_book
end
end
Of course if your users can have more than one PhoneBook we go back to the has_many association:
class User < ActiveRecord::Base
has_many :phone_book
end
class PhoneBook < ActiveRecord::Base
belongs_to :user
end
and the controller becomes:
class PhoneBooksController < ApplicationController
def index
#phone_books = current_user.phone_books
end
def show
#phone_book = PhoneBook.find_by_id(params[:id])
end
end
At last, if you want these phone books to be publicly readable I suggest you stick with a REST kind of URI
/phone_books/:id <-- good
/users/:id/phone_books/:phone_book_id <-- too complex
Hope I could help
You might want to place the page in /users/:user_id/phone_books/:id.
To achieve that,
You have to configure the paths in config/routes.rb:
resources :users do
resources :phone_books
end
And in app/controllers/phone_books_controller.rb, find the user and their address book:
class PhoneBooksController < ApplicationController
before_action :find_user
def show
#address_book = #user.address_books.find(params[:id])
end
private
def find_user
#user = User.find(params[:user_id])
end
end
For more information about nested resources, please see the Getting Started with Rails guide.

Rails 4.1 nested model form fields

Booking -< Orders -< Transactions
class Booking < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :booking
has_many :transactions
end
class Transaction < ActiveRecord::Base
belongs_to :order
end
I need to be able to create a Transaction without an Order or Booking existing.
I'm trying to achieve the following:
When a Transaction is created an Order and a Booking is automatically created. The transaction form can take a Booking.booking_number which will be saved to the above automatically created Booking.
I'm very new to rails and have tried a combination of accepts_nested_attributes_for, Ryan Bates' nested model form part1 screencast and form_fields_for without success.
Some guidance, not necessarily code, would be much appreciated.
My routes look like:
I need to be able to create a Transaction without an Order or Booking
existing.
Bad system design - surely a transaction would follow an order or booking?
From your question, I'd highly recommend creating a booking or order first. This will allow you to create a transaction as a bolt-on to the order or booking:
#app/controllers/bookings_controller.rb
Class BookingsController < ApplicationController
def create
booking = Booking.new(booking_params)
booking.save
end
end
#app/models/booking.rb
Class Booking < ActiveRecord::Base
before_create :build_transaction #-> creates a blank transaction which can be populated later
end
Nonetheless, there's nothing stopping you creating a transaction & assigning an order later
You can do this:
#app/controllers/transactions_controller.rb
def create
Transaction.new(transaction_params)
end
#app/models/transaction.rb
Class Transaction < ActiveRecord::Base
after_create :order
def order
self.order.create!([order_details?])
end
end
If you tell me some more about what you're building, I'll be able to create a more refined response!
Try this it may be work.
In your model
accepts_nested_attributes_for :order, :allow_destroy => true
change whether true/false depending on your form

Design considerations for creating associated records on Devise User object on registration

I'm using Devise, and for each User account created I want to generate a relationship where:
class User < ActiveRecord::Base
belongs_to :business
end
class Business < ActiveRecord::Base
has_many :users
has_one :apt_setting
has_many :hours, :as => :hourable
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
end
So upon registration an associated Business object is created, and with each Business object an associated ApptSettings and BusinessHour object is created.
I currently have this implemented like this:
class Admin
before_create :create_associated_records
def create_associated_records
# create the associated business object
business = Business.create(:business_name => business_name, :subdomain => subdomain, :initial_plan => initial_plan)
# retrieve the id of the new business object
self.business_id = business.id
# create the associated records
BusinessHour.default_values(business_id)
ApptSetting.default_values(business_id)
end
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
def self.default_values(business_id)
# ... create record with default values
end
end
class BusinessHour < Hour
belongs_to :hourable, :polymorphic => true
def self.default_values(business_id)
# ... create record with default values
end
end
This does work, but does it seem like the best design?
One alternative I'm considering is handling removing Admin -> create_associated_records, and instead do that work in Users::Accounts::RegistrationsController where I override the 'create' method. There I could build all the associated records, set :accepts_nested_attributes where appropriate, then call 'save' on the Business object, which should then cause all the associated records to be generated.
Thoughts on the best design, or any other ideas?
you don't need the default_values methods. In your create_associated_records you can change those calls to:
ApptSetting.create(:business_id => business_id)
Don't override the create method. before_create callbacks are a better way to go. In either case, If a business has many users, do you really want to create a new business every time a new user is created? How does a second user ever get added to a business? add something like,
def create_associated_records
return unless self.business_id.nil?
....
Also where are the business_name, subdomain, and initial_plan variables coming from in your method? Do you have them as attributes of the admin user? Seems like they should be only values of the business.
I think the biggest question here is, does a user really need a business in order to exist? Why can't the user just create their Business after they create their account?
** Edit: Being more clear / cleaner version using rails association methods:
class Admin
before_create :create_associated_records
private
def create_associated_records
return unless self.business_id.nil?
self.create_business
self.business.create_appt_setting
self.business.hours.create
end
end

How to associate a new model with existing models using has_and_belongs_to_many

I have two models with a many to many relationship using has_and_belongs_to_many. Like so:
class Competition < ActiveRecord::Base
has_and_belongs_to_many :teams
accepts_nested_attributes_for :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :competitions
accepts_nested_attributes_for :competitions
end
If we assume that I have already created several Competitions in the database, when I create a new Team, I would like to use a nested form to associate the new Team with any relevant Competitions.
It's at this point onwards that I really do need help (have been stuck on this for hours!) and I think my existing code has already gone about this the wrong way, but I'll show it just in case:
class TeamsController < ApplicationController
def new
#team = Team.new
#competitions.all
#competitions.size.times {#team.competitions.build}
end
def create
#team = Team.new params[:team]
if #team.save
# .. usual if logic on save
end
end
end
And the view... this is where I'm really stuck so I won't both posting my efforts so far. What I'd like it a list of checkboxes for each competition so that the user can just select which Competitions are appropriate, and leave unchecked those that aren't.
I'm really stuck with this one so appreciate any pointing in the right direction you can provide :)
The has_and_belongs_to_many method of joining models together is deprecated in favor of the new has_many ... :through approach. It is very difficult to manage the data stored in a has_and_belongs_to_many relationship, as there are no default methods provided by Rails, but the :through method is a first-class model and can be manipulated as such.
As it relates to your problem, you may want to solve it like this:
class Competition < ActiveRecord::Base
has_many :participating_teams
has_many :teams,
:through => :participating_teams,
:source => :team
end
class Team < ActiveRecord::Base
has_many :participating_teams
has_many :competitions,
:through => :participating_teams,
:source => :competition
end
class ParticipatingTeam < ActiveRecord::Base
belongs_to :competition
belongs_to :team
end
When it comes to creating the teams themselves, you should structure your form so that one of the parameters you receive is sent as an array. Typically this is done by specifying all the check-box fields to be the same name, such as 'competitions[]' and then set the value for each check-box to be the ID of the competition. Then the controller would look something like this:
class TeamsController < ApplicationController
before_filter :build_team, :only => [ :new, :create ]
def new
#competitions = Competitions.all
end
def create
#team.save!
# .. usual if logic on save
rescue ActiveRecord::RecordInvalid
new
render(:action => 'new')
end
protected
def build_team
# Set default empty hash if this is a new call, or a create call
# with missing params.
params[:team] ||= { }
# NOTE: HashWithIndifferentAccess requires keys to be deleted by String
# name not Symbol.
competition_ids = params[:team].delete('competitions')
#team = Team.new(params[:team])
#team.competitions = Competition.find_all_by_id(competition_ids)
end
end
Setting the status of checked or unchecked for each element in your check-box listing is done by something like:
checked = #team.competitions.include?(competition)
Where 'competition' is the one being iterated over.
You can easily add and remove items from your competitions listing, or simply re-assign the whole list and Rails will figure out the new relationships based on it. Your update method would not look that different from the new method, except that you'd be using update_attributes instead of new.

Resources