Rails acts_as_soft_deletable with has_and_belongs_to_many - ruby-on-rails

I have a problem with the acts_as_soft_deletable plugin and a has_and_belongs_to_many relationship.
I have a model "Place" which has a couple of Categories (like restaurant, hotel, etc). This means that a table "places_categories" is created in the database, containing two columns "place", and "category".
When I destroy a place, it is placed in the table "deleted_places" by acts_as_soft_deletable. Then I try to restore it and the application crashes because a place cannot exist without categories. The entries in "places_categories" that stored which categories the place belonged to are deleted as the place is deleted.
How can I make sure that "places_categories" does not remove the relations when a place is moved to the "deleted_places" table?

Since there is no option to explicitly preserve those entries - you could do something crazy and stupid and just override the delete_sql option to an empty String or somethings thats not going to fail on the "database-side" like so:
class Place
has_and_belongs_to_many :categories, :delete_sql => "select true"
end
This is untested ! Just an idea.
You can read about all available options here.

Related

Rails Cookies to manipulate database entries

I am trying to create a Rails app and I have a database consisting of author and a quotation by that author.
Now different users can choose to destroy or kill quotations from the database however it must only be deleted for that particular user i.e other users should still be able to see quotes that they didn't delete even if another user did.
I know that I would need to implement cookies but other than that I am unsure how to proceed. Can anyone point me to a tutorial or give me some pointers to get started on this complex task?
You surely have a User model in your application - one 'Rails-like' way to go about this would be to add a has_and_belongs_to_many relationship between User and Quotation.
This creates a relationship between each individual user and 'their' quotations. This relationship can be deleted without actually deleting a quotation, so all quotations would still be available to other users. If you want each user to be able to see all quotations by default, you would need to set up the relationship in advance.
Assuming you are using Devise to log your users in, all you'd need to do then is to replace Quotation.all with current_user.quotations in whichever controller you are using to display quotations.
The Rails guide linked above is quite helpful but basically you just need to add something like the following:
class User
has_and_belongs_to_many :quotations
before_create :add_quotations
def add_quotations
self.quotations << Quotation.all
end
#etc...
end
class Quotation
has_and_belongs_to_many :users
#etc...
end
and then run a migration adding a new table called users_quotations with the columns user_id and quotation_id.
EDIT
As #Yule pointed out this wouldn't let users see any quotations that were created after they were, and it would be quite annoying to have to set up the join tables in advance, so a more efficient way would be to have an excluded_quotations join table instead. So users can see all quotations except the ones that they have excluded.

How do I associate a Clone to it's Parent in Rails?

I have a model of say Apps and each App contains a bunch of users, and a reference to the store type, and has the properties, name, app_id, version, and price.
I want to be able to create clones of this app, which has all the same values, save for version, and store type.
I'd like to do this by adding a property/column to my app called "Is_clone", and I'd like a second table called AppToClone, which has two columns App and Clone.
In each row, I'll have say App:1 , Clone:[2,5,7,26]
This way I can edit each clone as its own app, but at the same time I can apply any changes in an app to all its clones.
I've set up the database for AppToClone but I'm stuck on how to create this association within the App Model itself. Examples I've found so far such as the gem rails_lookup_tables seems to only allow a single value rather than an array in the AppToClone table.
Currently in my App model I have the following line:
:has_many :apps, through: :appstoclones
But this doesn't feel correct to me, and I'm not sure how to build that database over time.
Feeling kinda stupid here, but I'm stuck.
So the has_many and belongs_to association stores the data in a different way than the question indicates. I would use single table inheritance, having a Clone model inherit from the App model.
class App < ActiveRecord::Base
has_many :clones
end
class Clone < App
belongs_to :app
end
You would need to have an app_id and type on your app table (strange I know), so your clone can be associated. What you would be able to do then is query your clone as an App.
Clone.last.is_a? App
-> true
What I think is closest to what you actually want though, is outlined in section 2.10 Self Joins of the Ruby on Rails Guides.
class App < ActiveRecord::Base
has_many :clones, class_name: "App",
foreign_key: "parent_app_id"
belongs_to :parent_app, class_name: "App"
end
You need to create the foreign_key in a migration.
To implement the is_clone:
def is_clone?
!!self.parent_app_id
end
To implement the AppToClone:
def clone_ids
self.clones.all.map(&id)
end
# Check it in the console:
App.last.clone_ids
-> [2,5,7,26]
The biggest concern here is thinking that a table will have a row with one column holding an array. That is not normalized! Normal associations work by having the foreign keys as seen in these examples, one per row. If you have a complex has_and_belongs_to_many relationship, the table that holds keys has two columns, each with one id.
Per the request in the comment below (self join update):
def clone_me
self.clones << self.clone
end
> new_cloned_app = App.last.clone_me
The reference for using has_many is found: In the rails guide here. Feel free to use a different helper function. Also, the returned value must be .saveed
I would say you are looking for a has-many belongs-to relationship.
For the has-many part: http://guides.rubyonrails.org/association_basics.html#the-has-many-association
For the belongs-to part: http://guides.rubyonrails.org/association_basics.html#the-belongs-to-association
One model would be the app, the other clone, where the clone table would store the the same fields an app has, as well as the additional fields needed for a clone instance. By inheriting the clone model from the app model which inherits from ActiveRecord, one can use a clone in the code where an app is used.

Interlinking 2 scaffolds

I am having a problem with some understanding of inter-connection with 2 scaffolds.
I have made one scaffold and used an external xml file to parse the data (nokogiri) and then i have placed that data in a scaffold to auto generate each page for the different items i have (there are only about 50 items).
I have another file with things that are meant to go inside each of those files but they are all stored in 1 xml file. But they are alphabetized so the top one will always remain the top and the bottom one will always remain at the bottom.
Parsing the data will be fine but how would the inter connection go??
You can use the model element to interlinks scaffolds together. You would need a primary and foreign key to be able to conduct that.
has_many :screenings
has_many :films, :through => :screenings
has_one :location
This is a very abstract question (without any models and such). So a very abstract reply.
First you import the 50 items into your scaffolded model. Those 50 items will get new ids.
When trying to import the related items, you will need to find the correct parent-item. Using the ID will not work anymore. So then I see two options:
in a first pass you will have to change your second XML and make sure it can use another unique field (e.g. a name) to find the parent model again
OR you add a new field to your first model, call that something like original_id and use that to build the relation correctly.
Hope this helps.

Rails Associations: HABTM?

Hey guys, I'm at a deadlock here after thinking about this for too long.
Context: Given the following models:
User
Item
Lock
Here's the scenario: A lock is basically like a 'hold'. A user can place a 'lock' on any given item to signal to the system that the item should not be deleted. Items wont be deleted until the lock is cleared.
Here's the tricky part. The lock is its own model because I want multiple users to be able to lock any given item. So let's say Bob locks an item, one didn't already exist so it creates a lock for that item, and information stating that Bob is currently associated with that lock. John comes and locks the same item, but a lock already exists, so John is simply 'added under' the same lock. The lock won't be removed until all users choose to 'unlock', or disassociate themselves with that lock.
My confusion is how I should model these relationships. A user can of course have many locks, each associated with a different item (since any given item can have at most one lock). The locks themselves can have many users. From the point of view of the item, each item can have one lock associated with many users.
So in other words, I would like to access the information a little something like this:
item.lock.users # get the users 'locking' the item
user.locks # get the items the user is currently 'locking
Perhaps the separate Lock model isn't required, but I figured it would be in order to signify that multiple users can be locking a particular item.
I think what further complicates things is that items are added by users, so I would want to have a way to access the items by a user for example user.items or item.user.
Right now I have:
user has and belongs to many locks
lock has and belongs to many users
user has many items
item belongs to user
item has one lock
lock belongs to item
Does this seem correct?
I think what you're doing will work though you may not have to use the habtm. What if an item can have many locks and can only be deleted when it has no locks. That way you could add a date/reason/comment for each lock by user.
user
has_many :locks
has_many :items
lock
belongs_to :user
belongs_to :item
item
belongs_to :user
has_many :locks
This will still allow you to do user.locks though item.lock.users won't work, but by looking at each lock you'll easily be able to get the users.
item.locks.each do |lock|
puts lock.user
end
You don't need the lock model. You can simply set up a habtm relationship between users and items:
class User < ActiveRecord::Base
has_many :items
has_and_belongs_to_many :locks, :class_name => "Item"
end
User.first.items # => [<#Item>, <#Item>, ...] # Items created by user
User.first.locks # => [<#Item>, <#Item>, ...] # Items locked by user
class Item < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :lock_users, :class_name => "User"
end
Item.first.user # => <#User> # Creator of the item
Item.first.lock_users # => [<#User>, <#User>, ...] # "Lockers" of the item
You'll have to create a join table, of course, and be mindful of what Rails expect the join table to be named. You may be better off specifying the :join_table option for the habtm.
The key here is that relationships in Rails are very flexible. You can have multiple relationships between the two tables; you can have both the 'created by' and the lock relationships, independently of each other. All you have to do is use different names for the relationships.
I can relate to "thinking about this for too long". When that thought creeps into my mind I back away and work on other parts of the code. Relationships seem to reveal themselves over time as they're really a convenience to spare us writing a bunch of code. They're not essential, so, at least during the development phase, we can postpone the declaration of the relationships and see what we need a bit later.
Yeah, we're supposed to always know ahead of time according to the pragmatists, but in real life we're often having to use our common sense and experience and build a working prototype, then fine tune it. It's during that fine-tuning stage I tweak my relationships that weren't exactly clear before, and adjust my code.
Sniff... sniff... jeez, now my bosses know I'm not perfect... sniff....
Back to the question at hand: Normally, for locks to prevent accidental (or on purpose) deletion, I create a boolean field in my main table and if that record should be purged set it to true. For what you're doing I'd probably get rid of the flag field altogether and have a separate table that is the IDs of the records to lock, along with the user's IDs who want to keep the record. Delete those user's records if/when they think it's time to delete the record. When it's time to do some purging I'd check against that table. Something similar to:
delete from table1 where id not in (select distinct(table1_id) from table2)
The thing I don't like about it is there's potential to have another table full of "keep this record" records, and that's when I add another table for users to terminate who can't decide what things need to be deleted.

Rails Single Table Inheritance without "type" column

I'm porting some functionality to Rails, and I'm working with an existing table which is for comments.
Basically, there are two types of comments - profile comments (photo_id column is null) and photo comments (photo_id column is set to photo's ID)
I got single table inheritance working just fine by adding a type field to the table, but I'm wondering if there's a way to get my single table inheritance working without the type field. According to the Rails API documentation, "If you don‘t have a type column defined in your table, single-table inheritance won‘t be triggered. In that case, it‘ll work just like normal subclasses with no special magic for differentiating between them or reloading the right type with find."
I'm wondering if there's a way that I can customize my models to determine type based on photo_id being nil or having an integer value, rather than using the database column (which I'd rather not add if I don't have to.) Any ideas?
If comments models doesn't differ much, I wouldn't bother with single table inheritance at all. Just add:
# to Comment model
belongs_to :photo
belongs_to :profile
# to Profile model
has_many :comments
# to Photo model
has_many :comments
Then:
#photo.comments # will return comments associated with photos
#profile.comments # will return comments associated with profiles
There may be problem if you had both photo_id and profile_id set (I suppose it may happen when you comment a photo that is associated with profile), so you can change in Profile model:
has_many :comments, :conditions => "photo_id is not null"
Another approach (I think better) it to you polymorphic associations but you will need to modify you sql tables.
I suspect you cannot do this trivially. However, one possibility is to trick active record into using a view rather than a table, and write some database functions to set this magic attribute based on which id is set.
However, in the end, I suspect it would be far, far easier to just add the column.

Resources