How to connect with different tables depending on parameter - ruby-on-rails

I have model Board and BoardController where i can find all boards of my project.
All records have filled "board_layout" column with enum.
Now i have show method in BoardController and i want to load there different data from different table depending on board_layout column value.
I could do it like this:
def show
#board = Board.find(params[:id])
if #board.board_layout == 1
#tasks = Car.where(board_id: #board.id)
elsif #board.board_layout == 2
#tasks = Truck.where(board_id: #board.id)
end
end
But it's not elegant + it is not DRY (i need to use some sort of if statement anytime when i want to do something with those 2 tables).
So i have tried to create Concern and create case statement inside, now it looks like:
def show
#board = Board.find(params[:id])
#tasks = get_class_name(#board.board_layout).where(board_id: #board.id)
end
# inside my concern
def get_class_name(scope)
case scope
when 1
Car
when 2
Truck
end
end
My Question:
Is there better way to do it? Is my solution safe and clear?
What is the best solution to resolve problem like this?
I would appreciate any help.

maybe you can abstract that out into a class, so you can define multiple layout and their correspondent classes, like:
class LayoutClassGetter
CLASS_BY_LAYOUT = { '1' => Car, '2' => Truck }.freeze
def initialize(layout_number)
#layout_number = layout_number
end
def layout_class
CLASS_BY_LAYOUT[#layout_number]
end
def self.layout_class(layout_number)
new(layout_number).layout
end
end
And then use it:
def show
#board = Board.find(params[:id])
#tasks = layout_class(#board.board_layout).where(board_id: #board.id)
end
def layout_class(scope)
LayoutClassGetter.layout_class(scope)
end

Related

Ruby add variables to an existing object?

How can I add variables to an existing obejct?
I have a list of chat rooms and I want to add a new variable for each chat to use at my view:
Example I want to add total users of chat
def index
chats_all = ChatRoom.all
#chats = Array.new
chats_all.each |chat|
chat.total_users = 10
#chats << chat
end
#chats
end
total_users is not an attribute of ChatRoom class.
[EDIT - explaim better after #jvillian great awnser]
I don't want total_users as an attribute of User class.
I just want to add as a variable to use at this one single page. For json rails already let my add new attributes to objects. Just need to use as_json().map and a merge()
Example:
def index
chats = chats.as_json().map {
|chat|
chat.merge(
total_users: 10
}
response = { chats: chats }
render json: response
end
Now I got an json with chats and each chat has total_users attribute.
I want to know if I can do something like this with objects, just add a temporary variable to use at index page.
Try
class ChatRoom < ActiveRecord::Base
attr_accessor :total_users
end
You can read more in the docs.
Then, index could look like:
def index
#chats = ChatRoom.all.map do |chat|
chat.total_users = 10
chat
end
end
Alternatively, I would be tempted to do something like:
class ChatRoom < ActiveRecord::Base
TOTAL_USERS = 10
attr_accessor :total_users
def total_users
#total_users || TOTAL_USERS
end
end
And then:
def index
#chats = ChatRoom.all
end
Now, you'll get
#chats.first.total_users
=> 10
You can set total_users to something else if you like, but it will default to 10.
Here's a potential approach using OpenStruct:
def index
#chats = ChatRoom.all.map do |chat|
OpenStruct.new(
chat.
attributes.
merge!(total_users: 10)
)
end
end
Now, you can do:
#chats.each do |chat|
puts chat.total_users
end
which will return 10.
BTW and TBH, I do something like that last sort of thing (using OpenStruct or custom decorators) all the time. In my more recent apps, views never have direct access to models.
Maybe you want to send the response to the view as an array and scan to show informations?
def index
#chats = ChatRoom.all.as_json().map { |chat| chat.merge("total_users" => 10) }
end
Then access #chats, which is actually an array of hashes, view:
<% #chats.each do |chat| %>
<p><%= chat["total_users"] %></p>
<% end %>
You can check how #chats is structured by <p><%= #chats %></p>
I maybe made some syntax error.
To create temporary custom Objects without add new attributes to database Struct solve my problem.
I can create a Struct with chat room info and total users
chat_info = Struct.new(:name, :total_users, :messages)
chat_temp = []
chats = ChatRoom.where(condition)
chats.each do |chat|
chat_temp << chat_info.new("nome", 100, messages)
end

Rails saving arrays to separate rows in the DB

Could someone take a look at my code and let me know if there is a better way to do this, or even correct where I'm going wrong please? I am trying to create a new row for each venue and variant.
Example:
venue_ids => ["1","2"], variant_ids=>["10"]
So, I would want to add in a row which has a venue_id of 1, with variant_id of 10. And a venue_id of 2, with variant_id of 10
I got this working, and it's now passing in my two arrays. I think I am almost there I'm not sure the .each is the right way to do it, but I think that I'm on the right track haha. I have it submitting, however, where would I put my #back_bar.save? because this might cause issues as it won't redirect
Thanks in advance.
def create
#back_bar = BackBar.new
#venues = params[:venue_ids]
#productid = params[:product_id]
#variants = params[:variant_ids]
# For each venue we have in the array, grab the ID.
#venues.each do |v|
#back_bar.venue_id = v
# Then for each variant we associate the variant ID with that venue.
#variants.each do |pv|
#back_bar.product_variant_id = pv
# Add in our product_id
#back_bar.product_id = #productid
# Save the venue and variant to the DB.
if #back_bar.save
flash[:success] = "#{#back_bar.product.name} has been added to #{#back_bar.venue.name}'s back bar."
# Redirect to the back bar page
redirect_to back_bars_path
else
flash[:alert] = "A selected variant for #{#back_bar.product.name} is already in #{#back_bar.venue.name}'s back bar."
# Redirect to the product page
redirect_to discoveries_product_path(#back_bar.product_id)
end
end # Variants end
end # Venues end
end
private
def back_bar_params
params.require(:back_bar).permit(:venue_id,
:product_id,
:product_variant_id)
end
as i said in comments
this is untested code and just showing you how it's possible to do with ease.
class BackBar
def self.add_set(vanue_ids, variant_ids)
values = vanue_ids.map{|ven|
variant_ids.map{|var|
"(#{ven},#{var})"
}
}.flatten.join(",")
ActiveRecord::Base.connection.execute("INSERT INTO back_bars VALUES #{values}")
end
end
def create
# use in controller
BackBar.add_set(params[:venue_ids], params[:variant_ids])
# ...
end

Call 'structure' to save in postgres using rails

I have a 'publication' data structure, and my table name in postgres is 'publications'. I get a tweet from streaming and parse it according in class TweetFetcher. How do I call publication controller to save it to the database? PublicationController has the standard scrum abilities (new, create, show, edit...), where create is:
def create
#publication = Publications.new(params[:publication])
if #publication.save
redirect_to :action => 'list'
else
#subjects = Subject.find(:all)
render :action => 'new'
end
end
and parte of my twitter code is
class TweetFetcher
def saveTweet(parsedTweet)
pT = JSON.parse(parsedTweet)
#save here. like this?
#PublicationController.create(parsedTweet)
end
end
You shouldn't be using your controller in this situation, instead you can just build your record like this:
class TweetFetcher
def saveTweet(parsedTweet)
pT = JSON.parse(parsedTweet)
publication = Publications.new
publication.sample_field = pT.sample_field
... # set additional attributes
publication.save
end
end
Additionally, here is some advice/information that is not directly related to your question, but are things you should know:
The class name of your models should be singular, so instead of Publications, it should be Publication.
Method and variable names should be in snake case rather than camel case (i.e. save_tweet instead of saveTweet or parsed_tweet instead of parsedTweet).
The new hash syntax is prefered, unless of course you are using a Ruby version below 1.9. This looks like { key: value } instead of { key => value }.
Indentation for Ruby code is typically 2 spaces.
That being said, I would change your code like this:
PublicationsController#create
def create
#publication = Publication.new(params[:publication])
if #publication.save
redirect_to action: 'list'
else
#subjects = Subject.find(:all)
render action: 'new'
end
end
TweeFetcher
class TweetFetcher
def save_tweet(tweet)
parsed_tweet = JSON.parse(tweet)
publication = Publication.new
publication.sample_field = parsed_tweet.sample_field
... # set additional attributes
publication.save
end
end

Trying to relate two lists

I have two Model level functions that populate two arrays, my_favorite_brands and my_favorites. They are defined as follows:
def self.my_favorite_brands
brand_list = Array.new
Favorite.my_favorites.each do |favorite|
brand_list.push(favorite.style.shoe.brand)
end
brand_list.uniq
end
And here is my_favorites:
def self.my_favorites
Favorite.where(:user => current_user)
end
I want to print out each Brand in my_favorite_brands and while doing so, for each Brand print out all of it's associated Favorites in my_favorites. The relation between the two models Brand and Favorite is the following. Brand has many Shoes which has many Styles. Favorite belongs to Style and it belongs to User. Here is some probably non-functional pseudo-ish (in that it doesn't really work) code that emulates what I want to do.
#Controller stuff
#fav_brands = Brand.my_favorite_brands
#fav_favorites = Favorite.my_favorites
#in the view
favorites_by_brand = Array.new
#fav_brands.each do |brand|
favorites_by_brand = #fav_favorites.map do |favorite|
unless favorite.style.shoe.brand == brand
#fav_favorites.delete("favorite")
end
favorites_by_brand.each do |favorite|
puts favorite.style
end
end
I am trying to create a complete list of favorites where favorite.style.shoe.brand is equal to the current brand I am iterating over, so that I can display the styles by Brand.
I figured out a way to accomplish what I want to accomplish, although it's probably not Ideal .
styles_of_favorites = #fav_favorites.map {|favorite| favorite.style}
#fav_brands.each do |brand|
brand.shoes.each do |shoe|
shoe.style.each do | style|
if styles_of_favorites.include?(style)
puts style
end
end
end
end

How do I pass a var from one model's method to another?

Here is my one model..
CardSignup.rb
def credit_status_on_create
Organization.find(self.organization_id).update_credits
end
And here's my other model. As you can see what I wrote here is an incorrect way to pass the var
def update_credits
#organization = Organization.find(params[:id])
credit_count = #organization.card_signups.select { |c| c.credit_status == true}.count
end
If it can't be done by (params[:id]), what can it be done by?
Thanks!
Ideally the data accessible to the controller should be passed as parameter to model methods. So I advise you to see if it is possible to rewrite your code. But here are two possible solutions to your problem. I prefer the later approach as it is generic.
Approach 1: Declare a virtual attribute
class CardSignup
attr_accessor call_context
def call_context
#call_context || {}
end
end
In your controller code:
def create
cs = CardSignup.new(...)
cs.call_context = params
if cs.save
# success
else
# error
end
end
In your CardSignup model:
def credit_status_on_create
Organization.find(self.organization_id).update_credits(call_context)
end
Update the Organization model. Note the change to your count logic.
def update_credits
#organization = Organization.find(call_context[:id])
credit_count = #organization.card_signups.count(:conditions =>
{:credit_status => true})
end
Approach 2: Declare a thread local variable accessible to all models
Your controller code:
def create
Thread.local[:call_context] = params
cs = CardSignup.new(...)
if cs.save
# success
else
# error
end
end
Update the Organization model. Note the change to your count logic.
def update_credits
#organization = Organization.find((Thread.local[:call_context] ||{})[:id])
credit_count = #organization.card_signups.count(:conditions =>
{:credit_status => true})
end
Use an attr_accessor.
E.g.,
class << self
#myvar = "something for all instances of model"
attr_accessor :myvar
end
#myothervar = "something for initialized instances"
attr_accessor :myothervar
then you can access them as ModelName.myvar and ModelName.new.myvar respectively.
You don't say whether you're using Rails 2 or 3 but let's assume Rails 2 for this purpose (Rails 3 provides the a new DSL for constructing queries).
You could consider creating a named scope for in your Organization model as follows:
named_scope :update_credits,
lambda { |id| { :include => :card_signup, :conditions => [ "id = ? AND card_signups.credit_status = TRUE", id ] } }
And then use it as follows:
def credit_status_on_create
Organization.update_credits(self.organization_id)
end
Admittedly I don't quite understand the role of the counter in your logic but I'm sure you could craft that back into this suggestion if you adopt it.

Resources