Delete an element from ActiveResource::Collection relationship - ruby-on-rails-4

I have many-to-many relationship (HABTM) in a file service:
class Album < ActiveRecord::Base
has_and_belongs_to_many :media, join_table: 'albums_media'
end
class Medium < ActiveRecord::Base
has_and_belongs_to_many :albums, join_table: 'albums_media'
end
We have a gem containing the ActiveResource interface classes into that service:
class Medium < ActiveResource::Base
...
def albums
Album.where(id: album_ids)
end
end
I can easily create a Medium that belongs to a given Album by passing album_ids:
album = Album.create(...)
medium = Medium.create(album_ids: [album.id])
I'd like to be able to delete a certain medium from a given album, but ActiveResource doesn't really support this directly.
# not supported...
medium.albums.destroy(...)
And I'd rather not use has_many :through as I don't really need to manage the join directly other than doing this kind of thing.
Any insight is appreciated.

FYI: I ended up adding the extra "join" resource in the file service itself, and then adding the corresponding ActiveResource class to the gem. Now we can simply manage the joins directly using the API.
It was more work than I wanted to do but AR is appropriately lite weight and doesn't support many-to-many relationships. The point is, it can be done.

Related

NoMethodError - new since upgrading to Rails 4

I'm at my wit's end. I upgraded to Rails 4.2.10, and everything is terrible.
Here is the relevant part of /models/product.rb
class Product < ActiveRecord::Base
delegate_attributes :price, :is_master, :to => :master
And here is /models/variant.rb:
class Variant < ActiveRecord::Base
belongs_to :product
The variants table has fields for "price" and "is_master". Products table does not.
It used to be the case that one could access Product.price and it would get/set the price for the master variant (there's really only one variant per product, the way things are currently set up).
Now it complains that:
NoMethodError: undefined method `price=' for #<Product:0x0000000d63b980>
It's true. There's no method called price=. But why wasn't this an issue before, and what on earth should I put in that method if I create it?
Here's the code to generate a product in db/seeds.rb:
product = Product.create!({
name: "Product_#{i}",
description: Faker::Lorem.sentence,
store_id: u.store.id,
master_attributes: {
listing_folder_id: uuids[i],
version_folder_id: uuids[i]
}
})
product.price = 10
product.save!
end
delegate_attributes isn't a Rails method and looks like it comes from a gem (or gems) that aren't actively maintained?
If there's a new version of whatever gem you're using that might help, because the short answer is that part of the "delegating" of an attribute would involve getting and setting the attribute, so it would generate #price= for you.
If you want to define it yourself, this should do it (within your Product class):
def price=(*args)
master.price=(*args)
end
or if you want to be more explicit:
def price=(amount)
master.price = amount
end

How do I get a set of objects belonging to another set of objects in Rails 4 ActiveRecord?

If I have two models, say, an user Model and a Company model like those defined below. How do I get all of the companies of a set of users?
class User < ActiveRecord::Base
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :users
end
users = User.where(:state => "Florida")
From this, I need a variable that holds all of the companies belonging to those users
companies = users.?
Do I need to loop through each of the users and add them to an array? I'm guessing there is a better way to do this. Any help is appreciated.
You can do this
Company.includes(:users).where(users: { state: 'Florida' })
BUT Taking into account http://guides.rubyonrails.org/ recommendations
It will be more correct to do
Company.joins(:users).where(users: { state: 'Florida'})
This does INNER JOIN instead of LEFT OUTER JOIN

Where should I place bestseller? method - in model or somewhere else?

I've got some simple model:
class Product < ActiveRecord::Base
has_many :categories
end
Now I would like to check in some service, if product is a bestseller and do other action for it:
class ProductService
def remind
Product.all.each do |product|
puts product unless bestseller?
end
end
end
So now what is the best place to put the bestseller? method - inside model or in the service as private method?
In future it may be used in some other services or actions.
Do you think the model is right place to put this method there?
Example of bestsellers method (bestsellers are picked manualy by adding to category 'bestsellers':
def bestseller?(product)
product.categories.include?(BESTSELLER_CATEGORY_ID)
end
or
def bestseller?(product_id)
Category.find(BESTSELLER_CATEGORY_ID).products.include?(product_id)
end
I still haven't decided which one is better (both do the same)

param is missing or the value is empty

I have two models: Boards and Topics. I want to be able to add Topics to Boards. My nested resources are:
resources :boards do
resources :topics
end
My 'boards#show' action:
def show
#board = Board.find(params[:id])
#new_topics = Topic.all
end
which lists all posts and has a link_to:
<ul>
<%#new_topics.each do |i|%>
<li><%=i.title%> <%=link_to "Add", board_topic_path(#board,i), :method=> :put%></li>
<%end%>
</ul>
I'm also using strong_params for my Boards and Topics controller as follows:
boards_controller:
def update
#board = Board.find(params[:board_id])
#topic = Topic.find(params[:id])
if #board.update(board_params)
flash[:notice] = "Added!"
#board.topics << #topic
redirect_to boards_path
else
flash[:alert] = "Problem!"
redirect_to boards_path
end
end
...
private
def board_params
params.require(:board).permit(:name,:description)
end
topics_controller:
...
private
def topic_params
params.require(:topic).permit(:title,:body,:user_id)
end
the error message I'm getting: param is missing or the value is empty: topic.
I believe that your design is wrong.
Starting from the beginning, I would say that you have a business model Board that references one or more Topics and a Topic that is referenced by one or more Boards. So, logically you have something like this:
So, these are two independent resources, that they have a many-to-many relationship.
My model with Rails would have been:
# routes
resources :boards
resources :topics
In other words, topics are not nested resource of boards. If it were, this would mean that the topics of a board would die when the board would die. Which is not your case here, as far as I understand.
Now, since the relationship is many-to-many, then you will need a 3rd table to hold your associations (table boards and table topics are not enough). Read this on Rails Guides.
Briefly:
class Board
has_and_belongs_to_many :topics
end
class Topic
has_and_belongs_to_many :boards
end
Now, if you want to add topics to boards on your UI, then you need to have a form to edit the board. This form, besides the others, needs to have a multiple select box with the topics that would be added to the board. Then on your boards_controller#update method the param[:board] would have an attribute topic_ids[] which will automatically be used to associate the particular/selected topics to the board that you are editing. Rails does that automatically.
Note I am not inclined to be using has_and_belongs_to_many Rails association. It has a lot of limitations. You can always design your own table that will hold the many-to-many association and other extra attributes that your business model will require. For example, for each topic that is attached to a board, you might want to hold the subject, or the author. I do not know. In that case a more custom model might be needed:
class Board
has_many :board_topics
has_many :topics, through: :board_topics
end
class Topic
has_many :board_topics
has_many :boards, through: :board_topics
end
class BoardTopic
belongs_to :topic, inverse_of: :board_topics
belongs_to :board, inverse_of: :board_topics
.... add other attributes that give real business value to this association ....
end
In a RESTful situation as yours, with that link you should be hitting the update action of TopicsController with two params: board_id and id.
Try this instead:
# boards_controller.rb
def update
#board = Board.find(params[:id])
#topic = Topic.find(params[:topic_id])
if #board.update(board_params)
flash[:notice] = "Added!"
#board.topics << #topic
redirect_to boards_path
else
flash[:alert] = "Problem!"
redirect_to boards_path
end
end
# In the view
<%=link_to "Add", board_path(#board, topic_id: i.id), :method=> :put%>
Still, this is still off from any convention, as you are not updating a whole topic. You probably want to use an extra action to add a topic to a board, using the PATCH verb.

Store multiple tasks for a job in Rails 4

I'm developing an application using Ruby on Rails 4 that has a list of pre-defined tasks that can be added into a job. The job can have one or more tasks assigned to it. How do I store the tasks in the job object? Would this be through an array of some sort? What would that look like?
I imagine this would work like a tokenfield or even a selectable list. Maybe checkboxes (but that may get unwieldy).
app/models/job.rb
class Job < ActiveRecord::Base
has_paper_trail
has_many :tasks
end
app/models/task.rb
class Task < ActiveRecord::Base
has_paper_trail
belongs_to :job
end
Tasks Table - just a sample
id | name | description
1 | Clean Room | Pick up toys
2 | Dust Shelf | Use dusting rag
It depends on whether you need to be able to assign the same task to multiple jobs. This will mean either a has_many :tasks or a has_many :tasks, through: :task_assignments association on the Job model. If you need further guidance on using these associations, have a look at the Rails Guides Association Basics guide.
As for the controller and view components, I suggest you have a look at the Rails Guides Form Helpers guide, and perhaps also watch Railscasts episodes on handlign nested forms: #196 - Part 1 and #197 - Part 2.